# 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 ' -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 ' -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 ' -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 ```