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

3.2 KiB

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 §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{}, objectmap[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

# 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