Files
homelab-frontend/tasks/8.3-request-response-validation.md
T

61 lines
3.2 KiB
Markdown
Raw Normal View History

# 8.3 — Request/response schema validation, KV+type DSL (GREEN)
Phase: 8 — ServiceAdapter CRD rollout
Stage: RED
Depends on: 8.1 (CRD types carry `requestSchema`/`responseSchema`), 8.2 (dispatcher calls this per request)
Design contract: [API_ROUTING_HYBRID_DESIGN.md](../API_ROUTING_HYBRID_DESIGN.md) §1
"Request/response validation" subsection — the full DSL including the array/nullable
extension. Read that subsection in full before implementing; do not reinvent the
shape from the checklist below alone.
- [ ] `internal/serviceadapter/validate.go` (new, no external dependency —
not a JSON Schema library): validates a parsed body (`map[string]interface{}`
or `[]interface{}` for array-typed schemas) against a `FieldSchema`
- [ ] Object schema: `required` fields present, each present field in `fields`
matches its declared Go runtime type (`string`→string, `number`→float64,
`boolean`→bool, `array``[]interface{}`, `object``map[string]interface{}`)
- [ ] `nullable: true` on a field accepts JSON `null` regardless of declared type;
a `null` on a non-nullable field is a `type_mismatch`; bare `field: string`
shorthand means `{type: string, nullable: false}`
- [ ] `strict: true` rejects body keys not listed in `fields`; default `false` is permissive
- [ ] Array schema (`type: array`): `items: string` validates every element is that
scalar type; `items: { fields: {...} }` validates every element as an object
schema, one level deep inside each item — no further nesting
- [ ] Compiled once per CR add/update in 8.1's informer callback, not per-request —
the parsed `FieldSchema` stored in the same registry entry as the route
- [ ] Request-side violation → 400, RFC 9457 `problem+json`,
`{type, title, detail, errors: [{field, reason}]}`, `reason` one of
`missing`, `type_mismatch: want X got Y`, `unknown_field`
- [ ] Response-side violation does **not** block the response — forwarded unchanged,
emits `serviceadapter_response_schema_mismatch{service,resource}` metric + log line
- [ ] GET requests with no body skip request-schema validation entirely (query-param
validation is out of scope for this task — schemas in this repo's adapters only
apply to POST/PATCH bodies per the CRs in §1/§3/§6)
## Verify
```bash
# missing required field
curl -s -X POST https://api.riotpiao.com/ \
-H 'Authorization: Bearer <jwt>' -H 'X-Service: postgres' -H 'X-Resource: query' \
-d '{"params": []}'
# expected: 400, errors: [{"field":"sql","reason":"missing"}]
# type mismatch
curl -s -X POST https://api.riotpiao.com/ \
-H 'Authorization: Bearer <jwt>' -H 'X-Service: postgres' -H 'X-Resource: query' \
-d '{"sql": 42}'
# expected: 400, errors: [{"field":"sql","reason":"type_mismatch: want string got number"}]
# nullable field accepted
curl -s -o /dev/null -w '%{http_code}\n' https://api.riotpiao.com/ \
-H 'Authorization: Bearer <jwt>' -H 'X-Service: memory' -H 'X-Resource: skill'
# expected: 200 even though generated_from is null in the response body — response
# validation must not reject/replace the body
# unit test, not curl:
go test ./internal/serviceadapter/... -run TestValidate -v
# expected: covers array-of-scalar, array-of-object, strict rejection, nullable
```