certificates/api/render/render.go
Panagiotis Siatras 00634fb648
api/render, api/log: initial implementation of the packages (#860)
* api/render: initial implementation of the package

* acme/api: refactored to support api/render

* authority/admin: refactored to support api/render

* ca: refactored to support api/render

* api: refactored to support api/render

* api/render: implemented Error

* api: refactored to support api/render.Error

* acme/api: refactored to support api/render.Error

* authority/admin: refactored to support api/render.Error

* ca: refactored to support api/render.Error

* ca: fixed broken tests

* api/render, api/log: moved error logging to this package

* acme: refactored Error so that it implements render.RenderableError

* authority/admin: refactored Error so that it implements render.RenderableError

* api/render: implemented RenderableError

* api/render: added test coverage for Error

* api/render: implemented statusCodeFromError

* api: refactored RootsPEM to work with render.Error

* acme, authority/admin: fixed pointer receiver name for consistency

* api/render, errs: moved StatusCoder & StackTracer to the render package
2022-03-30 11:22:22 +03:00

122 lines
2.8 KiB
Go

// 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
}