mirror of
https://github.com/gin-gonic/gin.git
synced 2026-09-04 14:49:27 +08:00
ProblemDetails() renders errors attached via Context.Error as an RFC 9457 problem details JSON response when the handler chain finishes without writing a response. The response status code is kept when it is an error status, otherwise it defaults to 500 Internal Server Error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
86 lines
2.9 KiB
Go
86 lines
2.9 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 (
|
|
"net/http"
|
|
|
|
"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)
|
|
}
|
|
|
|
// ProblemDetails returns a middleware that renders errors attached to the
|
|
// Context (see Context.Error) as an RFC 9457 problem details JSON response.
|
|
// It does nothing if no errors were attached or if a response body or header
|
|
// was already written. The response status code is kept when it is an error
|
|
// status, otherwise it defaults to 500 Internal Server Error.
|
|
// WARNING: the last attached error's message is exposed to the client as the
|
|
// problem detail member. Do not attach errors whose messages must stay
|
|
// private, or write the response yourself instead.
|
|
func ProblemDetails() HandlerFunc {
|
|
return func(c *Context) {
|
|
c.Next()
|
|
|
|
if len(c.Errors) == 0 || c.Writer.Written() {
|
|
return
|
|
}
|
|
status := c.Writer.Status()
|
|
if status < http.StatusBadRequest {
|
|
status = http.StatusInternalServerError
|
|
}
|
|
c.ProblemJSON(status, Problem{Detail: c.Errors.Last().Error()})
|
|
}
|
|
}
|