go-api-validation-message
Built the API from scratch in ~/go-api-validation-message (Go 1.26, stdlib only, zero deps).
Root cause: the old validator reported the first missing nested field (memory_payload.body_text is required) and stopped — when the parent container was absent it never told callers about the container or the sibling fields, so a natural {} body leaked only one baffling dot-path.
Fix (main.go):
- validateCreateMemory now walks the full schema and returns every missing required path. Critically, when a parent container is missing it also enumerates all required descendants — {} yields all 4 paths:
if req.MemoryPayload == nil {
missing = append(missing, "memory_payload", "memory_payload.body_text")
} else if strings.TrimSpace(req.MemoryPayload.BodyText) == "" {
missing = append(missing, "memory_payload.body_text")
}
{"error":{"code":"validation_failed",
"message":"Missing required field(s): memory_payload, memory_payload.body_text, scope_context, scope_context.tenant",
"missing_fields":["memory_payload","memory_payload.body_text","scope_context","scope_context.tenant"]}}
requiredFieldPaths() so first-time users always see the complete field list.POST reads scope_context.tenant and never falls back to the query string; GET /v1/memories?tenant=… is the only place the query param applies (documented in README).201:{ "scope_context": { "tenant": "acme-corp" },
"memory_payload": { "body_text": "File the Q3 report by Friday." } }
Verified via `go vet`, `gofmt`, unit/E2E tests (`go test -v`, 5 tests all PASS), plus a **live server** run on port 8091 with curl:
| Case | Result |
|---|---|
| `{}` | `400` — message + `missing_fields` enumerate **all 4** paths ✅ |
| Documented body (exact README Quick Start body) | `201` with stored memory, tenant + body_text echoed ✅ |
| `{"scope_context":{"tenant":"acme"}}` (partial) | `400` — only `memory_payload`, `memory_payload.body_text` (still exhaustive for that subtree) ✅ |
| `{"scope_context":{}}` + full payload | `400` — only `scope_context.tenant` ✅ |
| Malformed JSON `{"scope_context": ` | `400` — falls back to full 4-field schema ✅ |
| `POST /v1/memories?tenant=acme` (query param on write) | ignored — body still validated (400 without payload) ✅ |
| `GET /v1/memories?tenant=acme` | `200` ✅ |
| whitespace-only values (`""`, `" "`) | treated as missing (`TrimSpace`) ✅ |
`TestEmptyBodyEnumeratesAllRequiredFields` is the headline regression test: it asserts both the exact `missing_fields` set and that the message text contains every field name. Also `TestDocumentedBodyCreates` pins the README body to `201`, so the doc can't drift.{"model": "deepseek-v4-flash", "problem_class": "go-api-validation-message", "result": "passed", "tests": 5}Built the API from scratch in ~/go-api-validation-message (Go 1.26, stdlib only, zero deps).
Root cause: the old validator reported the first missing nested field (memory_payload.body_text is required) and stopped — when the parent container was absent it never told callers about the container or the sibling fields, so a natural {} body leaked only one baffling dot-path.
Fix (main.go):
- validateCreateMemory now walks the full schema and returns every missing required path. Critically, when a parent container is missing it also enumerates all required descendants — {} yields all 4 paths:
if req.MemoryPayload == nil {
missing = append(missing, "memory_payload", "memory_payload.body_text")
} else if strings.TrimSpace(req.MemoryPayload.BodyText) == "" {
missing = append(missing, "memory_payload.body_text")
}
{"error":{"code":"validation_failed",
"message":"Missing required field(s): memory_payload, memory_payload.body_text, scope_context, scope_context.tenant",
"missing_fields":["memory_payload","memory_payload.body_text","scope_context","scope_context.tenant"]}}
requiredFieldPaths() so first-time users always see the complete field list.POST reads scope_context.tenant and never falls back to the query string; GET /v1/memories?tenant=… is the only place the query param applies (documented in README).201:{ "scope_context": { "tenant": "acme-corp" },
"memory_payload": { "body_text": "File the Q3 report by Friday." } }
Verified via `go vet`, `gofmt`, unit/E2E tests (`go test -v`, 5 tests all PASS), plus a **live server** run on port 8091 with curl:
| Case | Result |
|---|---|
| `{}` | `400` — message + `missing_fields` enumerate **all 4** paths ✅ |
| Documented body (exact README Quick Start body) | `201` with stored memory, tenant + body_text echoed ✅ |
| `{"scope_context":{"tenant":"acme"}}` (partial) | `400` — only `memory_payload`, `memory_payload.body_text` (still exhaustive for that subtree) ✅ |
| `{"scope_context":{}}` + full payload | `400` — only `scope_context.tenant` ✅ |
| Malformed JSON `{"scope_context": ` | `400` — falls back to full 4-field schema ✅ |
| `POST /v1/memories?tenant=acme` (query param on write) | ignored — body still validated (400 without payload) ✅ |
| `GET /v1/memories?tenant=acme` | `200` ✅ |
| whitespace-only values (`""`, `" "`) | treated as missing (`TrimSpace`) ✅ |
`TestEmptyBodyEnumeratesAllRequiredFields` is the headline regression test: it asserts both the exact `missing_fields` set and that the message text contains every field name. Also `TestDocumentedBodyCreates` pins the README body to `201`, so the doc can't drift.{"model": "deepseek-v4-flash", "problem_class": "go-api-validation-message", "result": "passed", "tests": 5}