Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions crates/jerrycan/embedded/ai/00-designing.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,11 +324,12 @@ Every entity has an `id` primary key; you usually do NOT declare it:
entity RESPONSE shape is unchanged. Three drop reasons — a body can hit several at
once:
1. **Identity fk (guarded):** the body `belongs_to` the auth identity entity —
which MUST be named literally `User`, so the derived fk is `user_id` (see
10-auth.md: an identity named anything else gets NO owner-scoping and its fk
stays client-writable) — AND the endpoint is guarded → `user_id` is omitted; the
handler injects the session user's id. An unguarded endpoint keeps it (no session
to inject).
named by `auth.identity` (default `"User"`, so the derived fk is `user_id`;
set e.g. `auth.identity: "Account"` and the fk becomes `account_id`) — AND the
endpoint is guarded → the identity fk is omitted; the handler injects the
session principal's id. A non-`User` identity gets the SAME owner-scoping and
server-injected fk, derived from the name (see 10-auth.md). An unguarded
endpoint keeps it (no session to inject).
2. **`default` field:** any field with a `default` is omitted; the server applies
the declared value. Works on unguarded/public creates too — this is what lets
`POST /subscribers { "email": … }` succeed while `confirmed` and `status` default
Expand Down
2 changes: 1 addition & 1 deletion crates/jerrycan/embedded/ai/05-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ take no argument. `job_failed` carries code `JC0521` but responds with HTTP 500.
| JC0409 | 409 | `Error::conflict` — unique-key violation (a re-POSTed id), version conflict |
| JC0413 | 413 | Body over the limit (default 1 MiB) |
| JC0415 | 415 | `Error::unsupported_media_type` — wrong content type (e.g. multipart without a boundary) |
| JC0422 | 422 | JSON body failed to parse, or `Valid<T>` found violations (structured `details` array) |
| JC0422 | 422 | JSON body failed to parse, `Valid<T>` found violations (structured `details` array), or a foreign-key violation — a client-supplied fk referencing a nonexistent record (jerrycan-db) |
| JC0429 | 429 | `Error::too_many_requests` — rate limit exceeded (jerrycan::ratelimit) |
| JC0500 | 500 | `Error::internal` / response serialization failure |
| JC0503 | 503 | Handler exceeded its time budget (default 30s), or `Error::handler_timeout` |
Expand Down
8 changes: 5 additions & 3 deletions crates/jerrycan/embedded/ai/08-database.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,9 +357,11 @@ that surface.

## Errors you'll hit
- A unique-key violation surfaces as `409 JC0409` (a re-POSTed id is the
client's fault); every other database failure is `500 JC0510`. Neither leaks
internals in the body — the real SeaORM/sqlx error goes to stderr for the
operator. Always `.map_err(db_error)?` so both codes happen for free.
client's fault); a foreign-key violation — a client-supplied fk pointing at a
row that does not exist — is `422 JC0422` (the request is well-formed but
references a nonexistent record); every other database failure is `500 JC0510`.
None leaks internals in the body — the real SeaORM/sqlx error goes to stderr for
the operator. Always `.map_err(db_error)?` so all three codes happen for free.
- A failing migration stops the run and is NOT recorded — fix it and rerun.

## Anti-patterns
Expand Down
13 changes: 12 additions & 1 deletion crates/jerrycan/embedded/ai/09-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,19 @@ assert_eq!(body["details"][0]["field"], "text");
- `OpenApi::new(include_str!("../../../openapi.json"))` as an extension serves the
platform-generated document at `GET /openapi.json` (generated apps wire this
when the design lists `"validate"`).
- **Declarative field constraints** cover the common range/length rules with no
`Validate` impl at all: `min`/`max` (inclusive integer bounds, integer fields)
and `min_len`/`max_len` (inclusive string length in Unicode code points, string
fields) are first-class design fields (feature #80 / `JC0552`, since 0.6.5). An
out-of-range value is rejected at the request boundary as `422 JC0422`
automatically, and the same bound emits into OpenAPI
(`minimum`/`maximum`/`minLength`/`maxLength`), a DDL `CHECK`, and out-of-range
testgen reject probes — so the constraint is enforced and documented from one
declaration. Hand-write the `Validate` trait only for the custom rules the
declarative constraints can't express (cross-field checks, the non-empty-after-
trim example above).
- Rule-attribute `derive(Validate)` is a contract-v1 candidate — today you
implement the trait by hand (the design schema has no field constraints yet).
implement the trait by hand for those custom rules.

## Errors you'll hit
- `422 JC0422` with `details: [{field, message}]` — exactly what `Valid<T>`
Expand Down
2 changes: 1 addition & 1 deletion crates/jerrycan/embedded/ai/13-error-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ the cause + fix for any of them.
| JC0409 | Conflict — the write violates a unique key (jerrycan-db) |
| JC0413 | Payload too large — body over the route limit (default 1 MiB), or a multipart part over the per-part cap (8 MiB), >256 parts, or part headers over 8 KiB |
| JC0415 | Unsupported media type — content type is not what the endpoint consumes (e.g. `Multipart` needs `multipart/form-data` with a boundary; a storage bucket upload must match the bucket's `allowed_mime` allowlist) |
| JC0422 | Unprocessable — bad JSON or validation violations |
| JC0422 | Unprocessable — bad JSON, validation violations, or a foreign-key violation (a client-supplied fk referencing a nonexistent record; jerrycan-db) |
| JC0429 | Too many requests — the client exceeded its rate limit for the current window (the rate-limit extension); the response carries a `Retry-After` header |
| JC0500 | Internal error (or handler panic) |
| JC0503 | Handler timeout (default 30s) |
Expand Down
2 changes: 1 addition & 1 deletion crates/jerrycan/embedded/ai/15-jobs.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ assert!(send_email(t.task_context()).await.is_ok());

## Variations
- **Wiring**: the generated `crates/jobs/src/lib.rs` builds the extension —
`Jobs::postgres(db).queue("email", 4).register("send_email", f).cron("expire_trials", "0 * * * *", "billing")`
`Jobs::postgres(db).queue("email", 2).register("send_email", f).cron("expire_trials", "0 * * * *", "billing")`
— and the app installs it with `app.extend(jobs(db))`. `cron(job, expr, queue)`
takes the job name, the 5-field cron expression, and the queue it enqueues onto
(it is a builder taking `self` and PANICS at build time on an invalid `expr`).
Expand Down
11 changes: 6 additions & 5 deletions docs/ai/00-designing.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,11 +324,12 @@ Every entity has an `id` primary key; you usually do NOT declare it:
entity RESPONSE shape is unchanged. Three drop reasons — a body can hit several at
once:
1. **Identity fk (guarded):** the body `belongs_to` the auth identity entity —
which MUST be named literally `User`, so the derived fk is `user_id` (see
10-auth.md: an identity named anything else gets NO owner-scoping and its fk
stays client-writable) — AND the endpoint is guarded → `user_id` is omitted; the
handler injects the session user's id. An unguarded endpoint keeps it (no session
to inject).
named by `auth.identity` (default `"User"`, so the derived fk is `user_id`;
set e.g. `auth.identity: "Account"` and the fk becomes `account_id`) — AND the
endpoint is guarded → the identity fk is omitted; the handler injects the
session principal's id. A non-`User` identity gets the SAME owner-scoping and
server-injected fk, derived from the name (see 10-auth.md). An unguarded
endpoint keeps it (no session to inject).
2. **`default` field:** any field with a `default` is omitted; the server applies
the declared value. Works on unguarded/public creates too — this is what lets
`POST /subscribers { "email": … }` succeed while `confirmed` and `status` default
Expand Down
2 changes: 1 addition & 1 deletion docs/ai/05-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ take no argument. `job_failed` carries code `JC0521` but responds with HTTP 500.
| JC0409 | 409 | `Error::conflict` — unique-key violation (a re-POSTed id), version conflict |
| JC0413 | 413 | Body over the limit (default 1 MiB) |
| JC0415 | 415 | `Error::unsupported_media_type` — wrong content type (e.g. multipart without a boundary) |
| JC0422 | 422 | JSON body failed to parse, or `Valid<T>` found violations (structured `details` array) |
| JC0422 | 422 | JSON body failed to parse, `Valid<T>` found violations (structured `details` array), or a foreign-key violation — a client-supplied fk referencing a nonexistent record (jerrycan-db) |
| JC0429 | 429 | `Error::too_many_requests` — rate limit exceeded (jerrycan::ratelimit) |
| JC0500 | 500 | `Error::internal` / response serialization failure |
| JC0503 | 503 | Handler exceeded its time budget (default 30s), or `Error::handler_timeout` |
Expand Down
8 changes: 5 additions & 3 deletions docs/ai/08-database.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,9 +357,11 @@ that surface.

## Errors you'll hit
- A unique-key violation surfaces as `409 JC0409` (a re-POSTed id is the
client's fault); every other database failure is `500 JC0510`. Neither leaks
internals in the body — the real SeaORM/sqlx error goes to stderr for the
operator. Always `.map_err(db_error)?` so both codes happen for free.
client's fault); a foreign-key violation — a client-supplied fk pointing at a
row that does not exist — is `422 JC0422` (the request is well-formed but
references a nonexistent record); every other database failure is `500 JC0510`.
None leaks internals in the body — the real SeaORM/sqlx error goes to stderr for
the operator. Always `.map_err(db_error)?` so all three codes happen for free.
- A failing migration stops the run and is NOT recorded — fix it and rerun.

## Anti-patterns
Expand Down
13 changes: 12 additions & 1 deletion docs/ai/09-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,19 @@ assert_eq!(body["details"][0]["field"], "text");
- `OpenApi::new(include_str!("../../../openapi.json"))` as an extension serves the
platform-generated document at `GET /openapi.json` (generated apps wire this
when the design lists `"validate"`).
- **Declarative field constraints** cover the common range/length rules with no
`Validate` impl at all: `min`/`max` (inclusive integer bounds, integer fields)
and `min_len`/`max_len` (inclusive string length in Unicode code points, string
fields) are first-class design fields (feature #80 / `JC0552`, since 0.6.5). An
out-of-range value is rejected at the request boundary as `422 JC0422`
automatically, and the same bound emits into OpenAPI
(`minimum`/`maximum`/`minLength`/`maxLength`), a DDL `CHECK`, and out-of-range
testgen reject probes — so the constraint is enforced and documented from one
declaration. Hand-write the `Validate` trait only for the custom rules the
declarative constraints can't express (cross-field checks, the non-empty-after-
trim example above).
- Rule-attribute `derive(Validate)` is a contract-v1 candidate — today you
implement the trait by hand (the design schema has no field constraints yet).
implement the trait by hand for those custom rules.

## Errors you'll hit
- `422 JC0422` with `details: [{field, message}]` — exactly what `Valid<T>`
Expand Down
2 changes: 1 addition & 1 deletion docs/ai/13-error-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ the cause + fix for any of them.
| JC0409 | Conflict — the write violates a unique key (jerrycan-db) |
| JC0413 | Payload too large — body over the route limit (default 1 MiB), or a multipart part over the per-part cap (8 MiB), >256 parts, or part headers over 8 KiB |
| JC0415 | Unsupported media type — content type is not what the endpoint consumes (e.g. `Multipart` needs `multipart/form-data` with a boundary; a storage bucket upload must match the bucket's `allowed_mime` allowlist) |
| JC0422 | Unprocessable — bad JSON or validation violations |
| JC0422 | Unprocessable — bad JSON, validation violations, or a foreign-key violation (a client-supplied fk referencing a nonexistent record; jerrycan-db) |
| JC0429 | Too many requests — the client exceeded its rate limit for the current window (the rate-limit extension); the response carries a `Retry-After` header |
| JC0500 | Internal error (or handler panic) |
| JC0503 | Handler timeout (default 30s) |
Expand Down
2 changes: 1 addition & 1 deletion docs/ai/15-jobs.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ assert!(send_email(t.task_context()).await.is_ok());

## Variations
- **Wiring**: the generated `crates/jobs/src/lib.rs` builds the extension —
`Jobs::postgres(db).queue("email", 4).register("send_email", f).cron("expire_trials", "0 * * * *", "billing")`
`Jobs::postgres(db).queue("email", 2).register("send_email", f).cron("expire_trials", "0 * * * *", "billing")`
— and the app installs it with `app.extend(jobs(db))`. `cron(job, expr, queue)`
takes the job name, the 5-field cron expression, and the queue it enqueues onto
(it is a builder taking `self` and PANICS at build time on an invalid `expr`).
Expand Down
Loading