gin/problem.go
Miguel Quintero 7b9d081d7c feat(render): add RFC 9457 problem details JSON response support
Add first-class support for RFC 9457 "Problem Details for HTTP APIs"
error responses in JSON format:

- gin.Problem type with the standard members (type, title, status,
  detail, instance) and extension members serialized at the top level
- render.ProblemJSON renderer setting Content-Type to
  "application/problem+json; charset=utf-8"
- Context.ProblemJSON and Context.AbortWithProblemJSON helpers; when
  given a gin.Problem, a zero Status defaults to the response code and
  an empty Title defaults to http.StatusText

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 16:10:11 -04:00

61 lines
2.0 KiB
Go

// Copyright 2026 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package gin
import (
"github.com/gin-gonic/gin/codec/json"
)
// Problem represents an RFC 9457 problem details object.
// See https://www.rfc-editor.org/rfc/rfc9457 for the meaning of each member.
type Problem struct {
// Type is a URI reference that identifies the problem type.
// When empty it is omitted from the output, which consumers
// must interpret as "about:blank" (RFC 9457 section 3.1.1).
Type string `json:"type,omitempty"`
// Title is a short, human-readable summary of the problem type.
Title string `json:"title,omitempty"`
// Status is the HTTP status code generated by the origin server.
Status int `json:"status,omitempty"`
// Detail is a human-readable explanation specific to this occurrence.
Detail string `json:"detail,omitempty"`
// Instance is a URI reference that identifies the specific occurrence.
Instance string `json:"instance,omitempty"`
// Extensions holds problem type specific extension members
// (RFC 9457 section 3.2). They are serialized as top-level members
// of the problem details object. Extension keys that collide with
// a standard member name are ignored.
Extensions map[string]any `json:"-"`
}
// MarshalJSON implements the json.Marshaler interface, serializing
// Extensions as top-level members of the problem details object.
func (p Problem) MarshalJSON() ([]byte, error) {
type problem Problem // avoid infinite recursion
if len(p.Extensions) == 0 {
return json.API.Marshal(problem(p))
}
obj := make(map[string]any, len(p.Extensions)+5)
for k, v := range p.Extensions {
obj[k] = v
}
if p.Type != "" {
obj["type"] = p.Type
}
if p.Title != "" {
obj["title"] = p.Title
}
if p.Status != 0 {
obj["status"] = p.Status
}
if p.Detail != "" {
obj["detail"] = p.Detail
}
if p.Instance != "" {
obj["instance"] = p.Instance
}
return json.API.Marshal(obj)
}