| package models |
|
|
| import ( |
| "bytes" |
| "context" |
| "encoding/json" |
| "fmt" |
| "net/http" |
| "strings" |
|
|
| "github.com/go-chi/chi/v5/middleware" |
| ) |
|
|
| |
| const ProblemContentType = "application/problem+json" |
|
|
| |
| |
| |
| type ProblemType string |
|
|
| const ( |
| |
| ProblemTypeDefault = ProblemType("about:blank") |
| |
| |
| ) |
|
|
| |
| type Problem interface { |
| Respond(w http.ResponseWriter) |
| Error() string |
| ProblemType() ProblemType |
| ProblemTitle() string |
| ProblemStatus() int |
| } |
|
|
| |
| type StatusProblem struct { |
| Err error `json:"-"` |
|
|
| |
| Type ProblemType `json:"type"` |
| |
| Title string `json:"title"` |
| |
| Status int `json:"status"` |
| |
| Detail string `json:"detail,omitempty"` |
| |
| Instance string `json:"instance,omitempty"` |
|
|
| |
| Extensions map[string]interface{} `json:"extensions,omitempty"` |
| } |
|
|
| var _ Problem = (*StatusProblem)(nil) |
|
|
| func (p *StatusProblem) Error() string { |
| if p.Err == nil { |
| return fmt.Sprintf("[%s] %s", p.Title, p.Detail) |
| } |
|
|
| return fmt.Sprintf("[%s] %s - %s", p.Title, p.Err.Error(), p.Detail) |
| } |
|
|
| func (p *StatusProblem) RawError() error { |
| return p.Err |
| } |
|
|
| func (p *StatusProblem) ProblemType() ProblemType { |
| return p.Type |
| } |
|
|
| func (p *StatusProblem) ProblemStatus() int { |
| return p.Status |
| } |
|
|
| func (p *StatusProblem) ProblemTitle() string { |
| return p.Title |
| } |
|
|
| |
| func (p *StatusProblem) Respond(w http.ResponseWriter) { |
| RespondProblem(p, w) |
| } |
|
|
| |
| func RespondProblem(problem Problem, w http.ResponseWriter) { |
| |
| buf := &bytes.Buffer{} |
| enc := json.NewEncoder(buf) |
| enc.SetEscapeHTML(true) |
| _ = enc.Encode(problem) |
|
|
| w.Header().Set("Content-Type", ProblemContentType) |
| w.WriteHeader(problem.ProblemStatus()) |
| _, _ = w.Write(buf.Bytes()) |
| } |
|
|
| |
| |
| |
| func NewStatusProblem(ctx context.Context, err error, status int) *StatusProblem { |
| var instance string |
| reqID := middleware.GetReqID(ctx) |
| if reqID != "" { |
| instance = fmt.Sprintf("urn:request:%s", reqID) |
| } |
|
|
| |
| |
| |
| |
| if err != nil && strings.Contains(err.Error(), "context canceled") { |
| status = http.StatusRequestTimeout |
| } |
|
|
| var detail string |
| if err != nil { |
| detail = err.Error() |
| } |
|
|
| if status == http.StatusInternalServerError { |
| detail = "" |
| } |
|
|
| return &StatusProblem{ |
| Err: err, |
| Type: ProblemTypeDefault, |
| Title: http.StatusText(status), |
| Status: status, |
| Detail: detail, |
| Instance: instance, |
| Extensions: map[string]interface{}{}, |
| } |
| } |
|
|