~samwhited/problem

ref: v0.0.2 problem/problem.go -rw-r--r-- 2.7 KiB
2cc20af4Sam Whited .builds: add builds.sr.ht CI config 1 year, 5 months ago
                                                                                
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
// Package problem implements errors similar to the ones described by RFC 7807.
package problem // import "code.soquee.net/problem"

import (
	"encoding/json"
	"fmt"
	"net/http"
)

// Status returns a problem for the provided HTTP status code that conveys no
// additional information.
func Status(status int) Problem {
	return Problem{Title: http.StatusText(status), Status: status}
}

// Problem respresents an unrecoverable error that occured during processing of
// an API request.
// Problems are designed to be embedded in application or endpoint specific
// error types.
// Consumers must use the "type" string as the primary identifier for the
// problem type.
type Problem struct {
	// A URI reference that identifies the problem type.
	Type string `json:"type,omitempty"`

	// A short, human-readable summary of the problem type.
	Title string `json:"title,omitempty"`

	// The HTTP status code generated by the origin server for this occurrence of
	// the problem.
	Status int `json:"status,omitempty"`

	// A human-readable explanation specific to this occurrence of the problem.
	Detail string `json:"detail,omitempty"`

	// A URI reference that identifies the specific occurrence of the problem.
	Instance string `json:"instance,omitempty"`
}

// HTTPStatus returns the HTTP status as set by the origin server before
// encoding the problem.
func (p Problem) HTTPStatus() int {
	return p.Status
}

// Error satisfies the error interface for Problem by returning the title.
func (p Problem) Error() string {
	return p.Title
}

// NewResponder returns a function that can be used to reply to HTTP requests
// with errors.
// If the error is a Problem or has an HTTPStatus method returning an int, its
// status code is used (or 500 if no status code was specified).
// If the HTTPStatus method returns 0, no status code is written and the user
// must write one elsewhere.
// If the error value is nil, a 200 is returned.
func NewResponder() func(http.ResponseWriter, *http.Request, error) error {
	return func(w http.ResponseWriter, req *http.Request, err error) error {
		if req.Method != "HEAD" {
			w.Header().Set("Content-Type", "application/json")
		}

		// TODO: We should think up a good name for this interface and expose it
		// somewhere.
		status, ok := err.(interface {
			HTTPStatus() int
		})
		switch {
		case ok && status.HTTPStatus() == -1:
		case ok && status.HTTPStatus() > 0:
			w.WriteHeader(status.HTTPStatus())
		case err == nil:
			w.WriteHeader(200)
			return nil
		default:
			w.WriteHeader(http.StatusInternalServerError)
		}

		if req.Method != "HEAD" {
			err = json.NewEncoder(w).Encode(err)
			if err != nil {
				return fmt.Errorf("error encoding JSON error: %q", err)
			}
		}
		return nil
	}
}