File: render.go

package info (click to toggle)
golang-github-smallstep-certificates 0.20.0-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 23,144 kB
  • sloc: sh: 278; makefile: 170
file content (122 lines) | stat: -rw-r--r-- 2,853 bytes parent folder | download | duplicates (2)
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
116
117
118
119
120
121
122
// Package render implements functionality related to response rendering.
package render

import (
	"bytes"
	"encoding/json"
	"net/http"

	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/proto"

	"github.com/smallstep/certificates/api/log"
)

// JSON is shorthand for JSONStatus(w, v, http.StatusOK).
func JSON(w http.ResponseWriter, v interface{}) {
	JSONStatus(w, v, http.StatusOK)
}

// JSONStatus marshals v into w. It additionally sets the status code of
// w to the given one.
//
// JSONStatus sets the Content-Type of w to application/json unless one is
// specified.
func JSONStatus(w http.ResponseWriter, v interface{}, status int) {
	var b bytes.Buffer
	if err := json.NewEncoder(&b).Encode(v); err != nil {
		panic(err)
	}

	setContentTypeUnlessPresent(w, "application/json")
	w.WriteHeader(status)
	_, _ = b.WriteTo(w)

	log.EnabledResponse(w, v)
}

// ProtoJSON is shorthand for ProtoJSONStatus(w, m, http.StatusOK).
func ProtoJSON(w http.ResponseWriter, m proto.Message) {
	ProtoJSONStatus(w, m, http.StatusOK)
}

// ProtoJSONStatus writes the given value into the http.ResponseWriter and the
// given status is written as the status code of the response.
func ProtoJSONStatus(w http.ResponseWriter, m proto.Message, status int) {
	b, err := protojson.Marshal(m)
	if err != nil {
		panic(err)
	}

	setContentTypeUnlessPresent(w, "application/json")
	w.WriteHeader(status)
	_, _ = w.Write(b)
}

func setContentTypeUnlessPresent(w http.ResponseWriter, contentType string) {
	const header = "Content-Type"

	h := w.Header()
	if _, ok := h[header]; !ok {
		h.Set(header, contentType)
	}
}

// RenderableError is the set of errors that implement the basic Render method.
//
// Errors that implement this interface will use their own Render method when
// being rendered into responses.
type RenderableError interface {
	error

	Render(http.ResponseWriter)
}

// Error marshals the JSON representation of err to w. In case err implements
// RenderableError its own Render method will be called instead.
func Error(w http.ResponseWriter, err error) {
	log.Error(w, err)

	if e, ok := err.(RenderableError); ok {
		e.Render(w)

		return
	}

	JSONStatus(w, err, statusCodeFromError(err))
}

// StatusCodedError is the set of errors that implement the basic StatusCode
// function.
//
// Errors that implement this interface will use the code reported by StatusCode
// as the HTTP response code when being rendered by this package.
type StatusCodedError interface {
	error

	StatusCode() int
}

func statusCodeFromError(err error) (code int) {
	code = http.StatusInternalServerError

	type causer interface {
		Cause() error
	}

	for err != nil {
		if sc, ok := err.(StatusCodedError); ok {
			code = sc.StatusCode()

			break
		}

		cause, ok := err.(causer)
		if !ok {
			break
		}
		err = cause.Cause()
	}

	return
}