Skip to content

Troubleshooting

wiki edited this page Sep 4, 2026 · 1 revision

Troubleshooting

My route is not being validated

  • Pointer receiver, value registration. func (r *CreateUser) RequestBody() requires app.RegisterRoute(&CreateUser{…}). A value registration fails the assertion and the route is passed through — silently, because "does not implement ValidatableRoute" is indistinguishable from "deliberately unvalidated".
  • RequestBody() returns nil. That means "accepts no body".
  • The extension is not registered. validation.WithValidation(nil).

The application will not start: a codec cannot reject unknown members

validation: codec "application/xml" cannot reject unknown members.
Implement StrictCodec.UnmarshalStrict, or enable WithAllowUnknownFields().

Unknown-field rejection is on by default and every registered codec has to be able to enforce it. Either implement UnmarshalStrict on the codec, or state explicitly that unknown members are allowed. Both are legitimate; silently decoding leniently while the configuration says otherwise is not.

Valid requests are rejected as 422 with "rule": "unknown"

The client is sending a field the schema does not declare. Check for a typo on either side.

If the extra field is intentional — an established client you cannot change — validation.WithAllowUnknownFields(). Understand what you are giving up: the next misspelled field will be dropped silently and the request will return 200.

A 422 names a Go type

It should not — the field name is taken from the decoder's JSON pointer precisely so the response carries the client's own term rather than api.CreateUserRequest. If you are constructing the error yourself, use validation.UnknownFieldName(err) rather than err.Error().

415 Unsupported Media Type on a request that looks fine

The Content-Type header does not match a registered codec. Check for:

  • a missing header entirely — some clients omit it on POST
  • text/json or application/json5 rather than application/json
  • a codec you meant to register and did not

406 Not Acceptable

The client's Accept header matches no registered codec. Accept: */* or an absent header uses the first registered codec, so this only happens when the client asked for something specific.

The response is not in the format I asked for

Two cases:

  • The route declares no schemas. Then there is no negotiated codec and the handler's own write goes out as-is.
  • It is an error response. Problem documents deliberately ignore Accept and are always application/problem+json.

Handlers return 500 after enabling strict responses

That is strict mode doing its job: the handler wrote a status not listed in Responses(). The log names which.

Document every status your handlers can produce, including error paths. rextension.Problem is the schema for all of them — see Response Validation for a helper that makes that terse.

Streaming endpoints break

Response validation buffers and re-decodes the body, which is wrong for server-sent events, long polling or a file download.

Return nil from Responses() on those routes — the route is then passed through untouched.

Errors changed shape after upgrading

Wire break. The 422 is now an RFC 9457 problem document served as application/problem+json:

// before
{"status":422,"message":"Validation failed",
 "errors":[{"field":"email","tag":"email","message":"..."}]}

// after
{"type":"urn:rex:problem:validation-failed","title":"Unprocessable Entity",
 "status":422,"detail":"the request body failed validation",
 "errors":[{"field":"email","rule":"email","message":"..."}]}

tag became rule — "tag" named the Go struct tag that produced the constraint rather than the constraint itself. validation.ValidationErrorResponse still resolves as a name; it is now an alias of rextension.Problem, so it does not keep the old shape.

GetRequestBody returns false

The type parameter does not match the type in the schema:

func (r *CreateUser) RequestBody() validation.BodySchema {
	return validation.Scalar(CreateUserRequest{})
}

body, ok := validation.GetRequestBody[CreateUserRequest](r) // ← must match

It also returns false when the route declares no request schema, or when validation did not run.

Nested struct rules are ignored

Nested structs validate automatically, but slices and maps need dive:

Tags []string `json:"tags" validate:"max=10,dive,min=1"`

Without dive, min=1 applies to the slice length rather than to each element.

Validation runs before authentication

It does not — validation is PriorityValidation (500) and authentication is PriorityAuth (400), so a body is never parsed for a request that was going to be refused. If you are seeing the opposite, something registered the validation middleware at a lower priority by hand.