Skip to content

Commit 72aedfa

Browse files
ezqyclaude
andcommitted
v0.2.2: HTTP method specification, cron triggers, test coverage
Add HTTP method field (GET/POST/PUT/PATCH/DELETE) to handlers, workflow steps, and parallel branches. GET requests omit the body. Add cron source type with schedule expressions and optional timezone. Fires cron.tick events on schedule, matching handlers and workflows. Improve test coverage: 14 gap-coverage tests added across config, verify, db, api, and alert modules. 4 redundant tests removed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e531851 commit 72aedfa

File tree

15 files changed

+1086
-28
lines changed

15 files changed

+1086
-28
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/).
66

7+
## [0.2.2] - 2026-03-10
8+
9+
### Added
10+
- **HTTP method specification**: `method` field on handlers, workflow steps, and parallel branches. Supports `GET`, `POST` (default), `PUT`, `PATCH`, `DELETE`. GET requests omit the body.
11+
- **Cron triggers**: New `cron` source type with `schedule` (cron expression) and optional `timezone`. Fires `cron.tick` events on schedule, matching handlers and workflows.
12+
13+
### Changed
14+
- Test coverage improvements: 14 gap-coverage tests added, 4 redundant tests removed (174 total unit tests).
15+
716
## [0.2.0] - 2026-03-09
817

918
### Added

CLAUDE.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,15 @@ src/
5858
lib.rs — Module exports (for Cloud version dependency)
5959
api.rs — HTTP handlers (webhook, event, sns, health)
6060
config.rs — YAML config parsing + env var expansion
61+
cron.rs — Cron scheduler (periodic event generation)
6162
db.rs — SQLite/Postgres via sqlx AnyPool
6263
metrics.rs — Prometheus metrics (atomic counters, no deps)
6364
queue.rs — Job worker (poll, deliver, retry, DLQ)
6465
grpc.rs — gRPC output (prost types, tonic client, no codegen)
6566
verify.rs — Signature verification (GitHub, Stripe, Shopify, HMAC, SNS X.509)
6667
cli.rs — CLI commands (start, init, validate, jobs, events)
6768
tests/
68-
e2e.sh — E2E tests (20 tests)
69+
e2e.sh — E2E tests (26 tests)
6970
e2e_sns.sh — SNS E2E tests with LocalStack (8 tests)
7071
mock_server.py — Python mock HTTP server for E2E
7172
bench.sh — Benchmark script (receive RPS + delivery throughput)
@@ -88,7 +89,7 @@ docs/ — GitHub Pages user guide (Jekyll, Cayman theme)
8889
## Conventions
8990

9091
- **Language**: Code, comments, and all documentation in English. Exception: `docs/private/` may be in Japanese. User communication in Japanese.
91-
- **Source type strings**: `webhook`, `event`, `sns` (in config YAML and source_type field).
92+
- **Source type strings**: `webhook`, `event`, `sns`, `cron` (in config YAML and source_type field).
9293
- **Event type extraction order**: CloudEvents `ce-type` header → structured mode `type` field → provider-specific logic.
9394
- **Database**: SQLite for dev/testing, Postgres for production. Both via sqlx AnyPool.
9495
- **IDs**: ULID for event_id and job_id.

Cargo.lock

Lines changed: 106 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "qhook"
3-
version = "0.2.1"
3+
version = "0.2.2"
44
edition = "2024"
55
rust-version = "1.85"
66
description = "Lightweight webhook gateway and workflow engine with queue, retry, and signature verification."
@@ -69,3 +69,4 @@ tracing = "0.1"
6969
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
7070
anyhow = "1"
7171
thiserror = "2"
72+
croner = "3.0.1"

docs/configuration.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,18 @@ sources:
5353
type: sns
5454
# skip_verify: true # for LocalStack testing
5555

56+
daily-report:
57+
type: cron
58+
schedule: "0 9 * * MON-FRI" # 9 AM on weekdays
59+
# timezone: "+09:00" # default: UTC
60+
5661
handlers:
5762
payment-success:
5863
source: stripe
5964
events: [checkout.session.completed, invoice.paid]
6065
url: http://backend:3000/jobs/payment
6166
type: http # http (default) or grpc
67+
method: POST # GET, POST (default), PUT, PATCH, DELETE
6268
retry: { max: 8 }
6369
timeout: 60s
6470
idempotency_key: "$.id"
@@ -157,10 +163,12 @@ Each source is a named entry under `sources:`.
157163

158164
| Field | Type | Default | Description |
159165
|-------|------|---------|-------------|
160-
| `type` | string | required | `webhook`, `event`, or `sns` |
166+
| `type` | string | required | `webhook`, `event`, `sns`, or `cron` |
161167
| `verify` | string | - | Signature verification: `github`, `stripe`, `shopify`, or `hmac` |
162168
| `secret` | string | - | Shared secret for signature verification. Required when `verify` is set |
163169
| `skip_verify` | boolean | `false` | Skip SNS X.509 verification (for testing with LocalStack) |
170+
| `schedule` | string | - | Cron expression (required for `cron` sources). 5-field standard or 6-field with seconds |
171+
| `timezone` | string | `UTC` | Timezone for cron evaluation. `UTC` or fixed offset like `+09:00` |
164172

165173
**Source types:**
166174

@@ -169,6 +177,7 @@ Each source is a named entry under `sources:`.
169177
| `webhook` | `POST /webhooks/{source}` | External webhooks with signature verification |
170178
| `event` | `POST /events/{event_type}` | Internal events with optional bearer token auth |
171179
| `sns` | `POST /sns/{source}` | AWS SNS with auto-confirmation and envelope unwrapping |
180+
| `cron` | *(internal)* | Time-based trigger. Fires `cron.tick` events on schedule |
172181

173182
### handlers
174183

@@ -180,6 +189,7 @@ Each handler is a named entry under `handlers:`.
180189
| `events` | list | `[]` (all) | Event types to match. Empty = all events from this source |
181190
| `url` | string | required | Delivery target URL |
182191
| `type` | string | `http` | Delivery protocol: `http` or `grpc` |
192+
| `method` | string | `POST` | HTTP method: `GET`, `POST`, `PUT`, `PATCH`, `DELETE` |
183193
| `retry` | object | - | Override `default_retry` for this handler |
184194
| `timeout` | duration | - | Override delivery timeout for this handler |
185195
| `idempotency_key` | string | - | JSONPath to dedup key in payload (e.g., `$.id`) |
@@ -223,6 +233,7 @@ Each workflow is a named entry under `workflows:`.
223233
| `name` | string | required | Unique step name (used for `goto` references) |
224234
| `type` | string | `http` | Step type: `http`, `choice`, `parallel`, `map`, `wait`, `callback` |
225235
| `url` | string | - | Delivery target URL (required for `http` steps). For `callback` steps, URL to notify with the callback token |
236+
| `method` | string | `POST` | HTTP method: `GET`, `POST`, `PUT`, `PATCH`, `DELETE` |
226237
| `headers` | map | `{}` | Custom HTTP headers to send with this step's request |
227238
| `retry` | object | - | Override retry config. Supports `errors` field for error type matching (`5xx`, `4xx`, `timeout`, `network`, `all`) |
228239
| `catch` | list | - | Error routing rules. Each entry has `errors` (list) and `goto` (step name) |
@@ -267,10 +278,12 @@ qhook validate -c /path/to/qhook.yaml
267278

268279
Checks performed:
269280
- YAML syntax
270-
- Source type is valid (`webhook`, `event`, `sns`)
281+
- Source type is valid (`webhook`, `event`, `sns`, `cron`)
271282
- Handler type is valid (`http`, `grpc`)
283+
- HTTP method is valid (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`)
272284
- Handler references an existing source
273285
- `verify` requires `secret` to be set (non-empty)
286+
- Cron sources have a valid `schedule` and `timezone`
274287
- Handler URLs use http/https scheme (private IPs trigger warnings)
275288
- Alert config has valid `on` events
276289
- Workflow references an existing source

src/alert.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,20 @@ mod tests {
222222
assert_eq!(embed["color"], 0xFFA500);
223223
}
224224

225+
#[test]
226+
fn test_format_payload_unknown_type_falls_back_to_generic() {
227+
let event = AlertEvent::Dlq {
228+
job_id: "j1".into(),
229+
handler: "h1".into(),
230+
attempts: 3,
231+
};
232+
let payload = format_payload("unknown_type", &event);
233+
// Should produce generic JSON (not Slack/Discord format)
234+
let parsed: serde_json::Value = serde_json::from_str(&payload).unwrap();
235+
assert_eq!(parsed["alert"], "dlq");
236+
assert_eq!(parsed["handler"], "h1");
237+
}
238+
225239
#[test]
226240
fn test_event_kind() {
227241
let dlq = AlertEvent::Dlq {

0 commit comments

Comments
 (0)