Repository navigation
Expand file tree
/
Copy pathproblem_details.go
More file actions
115 lines (105 loc) · 2.87 KB
/
Copy pathproblem_details.go
File metadata and controls
115 lines (105 loc) · 2.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package httpsuite
import (
"bytes"
"encoding/json"
"net/http"
)
const BlankURL = "about:blank"
// ProblemDetails conforms to RFC 9457, providing a standard format for describing errors in HTTP APIs.
type ProblemDetails struct {
Type string `json:"type"`
Title string `json:"title"`
Status int `json:"status"`
Detail string `json:"detail,omitempty"`
Instance string `json:"instance,omitempty"`
// Extensions holds RFC 9457 extension members. MarshalJSON and
// UnmarshalJSON flatten them to the top level; there is no nested
// "extensions" object on the wire.
Extensions map[string]interface{} `json:"-"`
}
// ValidationErrorDetail provides structured details about a single validation error.
type ValidationErrorDetail struct {
Field string `json:"field"`
Message string `json:"message"`
}
// MarshalJSON serializes RFC 9457 extension members at the top level.
func (p ProblemDetails) MarshalJSON() ([]byte, error) {
payload := map[string]any{
"type": p.Type,
"title": p.Title,
"status": p.Status,
}
if p.Detail != "" {
payload["detail"] = p.Detail
}
if p.Instance != "" {
payload["instance"] = p.Instance
}
for key, value := range p.Extensions {
switch key {
case "", "type", "title", "status", "detail", "instance", "extensions":
continue
default:
payload[key] = value
}
}
return json.Marshal(payload)
}
// UnmarshalJSON reads the standard RFC 9457 members and collects every other
// top-level member into Extensions, mirroring MarshalJSON.
// A JSON null leaves p unchanged, matching encoding/json semantics.
func (p *ProblemDetails) UnmarshalJSON(data []byte) error {
if string(bytes.TrimSpace(data)) == "null" {
return nil
}
var known struct {
Type string `json:"type"`
Title string `json:"title"`
Status int `json:"status"`
Detail string `json:"detail"`
Instance string `json:"instance"`
}
if err := json.Unmarshal(data, &known); err != nil {
return err
}
var members map[string]any
if err := json.Unmarshal(data, &members); err != nil {
return err
}
for _, key := range []string{"type", "title", "status", "detail", "instance"} {
delete(members, key)
}
decoded := ProblemDetails{
Type: known.Type,
Title: known.Title,
Status: known.Status,
Detail: known.Detail,
Instance: known.Instance,
}
if len(members) > 0 {
decoded.Extensions = members
}
*p = decoded
return nil
}
// NewProblemDetails creates a ProblemDetails instance with standard fields.
func NewProblemDetails(status int, problemType, title, detail string) *ProblemDetails {
if status < 100 || status > 599 {
status = http.StatusInternalServerError
}
if problemType == "" {
problemType = BlankURL
}
if title == "" {
title = http.StatusText(status)
if title == "" {
title = "Unknown error"
}
}
return &ProblemDetails{
Type: problemType,
Title: title,
Status: status,
Detail: detail,
}
}