diff --git a/pkg/models/error.go b/pkg/models/error.go index 92c11fd5f..bd15c2284 100644 --- a/pkg/models/error.go +++ b/pkg/models/error.go @@ -131,6 +131,13 @@ func (err ValidationHTTPError) GetHTTPCode() int { return err.HTTPCode } +// GetCode returns Vikunja's numeric domain error code. v2's translateDomainError +// reads it to keep the v1 `code` body contract, since this type does not +// implement web.HTTPErrorProcessor (the embedded field shadows the method name). +func (err ValidationHTTPError) GetCode() int { + return err.Code +} + func InvalidFieldError(fields []string) error { return InvalidFieldErrorWithMessage(fields, "Invalid Data") } diff --git a/pkg/routes/api/v2/errors.go b/pkg/routes/api/v2/errors.go index f3542024f..a11ca25f7 100644 --- a/pkg/routes/api/v2/errors.go +++ b/pkg/routes/api/v2/errors.go @@ -43,6 +43,21 @@ func authFromCtx(ctx context.Context) (web.Auth, error) { return a, nil } +// httpCodeGetter is satisfied by validation errors that carry an HTTP status +// without the HTTPErrorProcessor method — notably models.ValidationHTTPError, +// returned by InvalidFieldError. v1's error handler reads GetHTTPCode() off these; +// without the same branch here they'd fall through to a 500. +type httpCodeGetter interface { + GetHTTPCode() int +} + +// domainCodeGetter exposes Vikunja's numeric domain error code on errors that +// are not HTTPErrorProcessors (again, ValidationHTTPError) so v2 can keep the v1 +// `code` body contract. +type domainCodeGetter interface { + GetCode() int +} + // translateDomainError maps a Vikunja domain error (web.HTTPErrorProcessor) // onto Huma's status-error type so the response carries the right code // and an RFC 9457 body. Errors without HTTP semantics fall through, which @@ -67,6 +82,17 @@ func translateDomainError(err error) error { } return se } + var cg httpCodeGetter + if errors.As(err, &cg) { + se := huma.NewError(cg.GetHTTPCode(), err.Error()) + if vm, ok := se.(*vikunjaErrorModel); ok { + var dc domainCodeGetter + if errors.As(err, &dc) { + vm.Code = dc.GetCode() + } + } + return se + } return err }