README

herodot

Build Status Coverage Status


Herodot is a lightweight SDK for writing RESTful responses. It is comparable to render but provides easier error handling. The error model implements the well established Google API Design Guide. Herodot currently supports only JSON responses but can be extended easily.

Herodot is used by ORY Hydra and serves millions of requests already.

Installation

Herodot is versioned using go modules and works best with pkg/errors. To install it, run

go get -u github.com/ory/herodot

Upgrading

Tips on upgrading can be found in UPGRADE.md

Usage

Using Herodot is straightforward. The examples below will help you get started.

JSON

Herodot supplies an interface, allowing to return errors in many data formats like XML and others. Currently, it supports only JSON.

Write responses
var hd = herodot.NewJSONWriter(nil)

func GetHandler(rw http.ResponseWriter, r *http.Request) {
	// run your business logic here
	hd.Write(rw, r, map[string]interface{}{
	    "key": "value"
	})
}

type MyStruct struct {
	Key string `json:"key"`
}

func GetHandlerWithStruct(rw http.ResponseWriter, r *http.Request) {
	// business logic
	hd.Write(rw, r, &MyStruct{Key: "value"})
}

func PostHandlerWithStruct(rw http.ResponseWriter, r *http.Request) {
	// business logic
	hd.WriteCreated(rw, r, "/path/to/the/resource/that/was/created", &MyStruct{Key: "value"})
}

func SomeHandlerWithArbitraryStatusCode(rw http.ResponseWriter, r *http.Request) {
	// business logic
	hd.WriteCode(rw, r, http.StatusAccepted, &MyStruct{Key: "value"})
}

func SomeHandlerWithArbitraryStatusCode(rw http.ResponseWriter, r *http.Request) {
	// business logic
	hd.WriteCode(rw, r, http.StatusAccepted, &MyStruct{Key: "value"})
}
Dealing with errors
var writer = herodot.NewJSONWriter(nil)

func GetHandlerWithError(rw http.ResponseWriter, r *http.Request) {
    if err := someFunctionThatReturnsAnError(); err != nil {
        hd.WriteError(w, r, err)
        return
    }
    
    // ...
}

func GetHandlerWithErrorCode(rw http.ResponseWriter, r *http.Request) {
    if err := someFunctionThatReturnsAnError(); err != nil {
        hd.WriteErrorCode(w, r, http.StatusBadRequest, err)
        return
    }
    
    // ...
}
Errors

Herodot implements the error model of the well established Google API Design Guide. Additionally, it makes the fields request and reason available. A complete Herodot error response looks like this:

{
  "error": {
    "code": 404,
    "status": "some-status",
    "request": "foo",
    "reason": "some-reason",
    "details": [
      { "foo":"bar" }
    ],
    "message":"foo"
  }
}

To add context to your errors, implement herodot.ErrorContextCarrier. If you only want to set the status code of errors implement herodot.StatusCodeCarrier.

Expand ▾ Collapse ▴

Documentation

Overview

    * Copyright © 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @author Aeneas Rekkas <aeneas+oss@aeneas.io> * @copyright 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io> * @license Apache-2.0

    * Copyright © 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @author Aeneas Rekkas <aeneas+oss@aeneas.io> * @copyright 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io> * @license Apache-2.0

    * Copyright © 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @author Aeneas Rekkas <aeneas+oss@aeneas.io> * @copyright 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io> * @license Apache-2.0

    * Copyright © 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @author Aeneas Rekkas <aeneas+oss@aeneas.io> * @copyright 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io> * @license Apache-2.0

    Index

    Constants

    This section is empty.

    Variables

    View Source
    var ErrBadRequest = DefaultError{
    	StatusField:   http.StatusText(http.StatusBadRequest),
    	ErrorField:    "The request was malformed or contained invalid parameters",
    	CodeField:     http.StatusBadRequest,
    	GRPCCodeField: codes.FailedPrecondition,
    }
    View Source
    var ErrConflict = DefaultError{
    	StatusField:   http.StatusText(http.StatusConflict),
    	ErrorField:    "The resource could not be created due to a conflict",
    	CodeField:     http.StatusConflict,
    	GRPCCodeField: codes.FailedPrecondition,
    }
    View Source
    var ErrForbidden = DefaultError{
    	StatusField:   http.StatusText(http.StatusForbidden),
    	ErrorField:    "The requested action was forbidden",
    	CodeField:     http.StatusForbidden,
    	GRPCCodeField: codes.PermissionDenied,
    }
    View Source
    var ErrInternalServerError = DefaultError{
    	StatusField:   http.StatusText(http.StatusInternalServerError),
    	ErrorField:    "An internal server error occurred, please contact the system administrator",
    	CodeField:     http.StatusInternalServerError,
    	GRPCCodeField: codes.Internal,
    }
    View Source
    var ErrNotFound = DefaultError{
    	StatusField:   http.StatusText(http.StatusNotFound),
    	ErrorField:    "The requested resource could not be found",
    	CodeField:     http.StatusNotFound,
    	GRPCCodeField: codes.NotFound,
    }
    View Source
    var ErrUnauthorized = DefaultError{
    	StatusField:   http.StatusText(http.StatusUnauthorized),
    	ErrorField:    "The request could not be authorized",
    	CodeField:     http.StatusUnauthorized,
    	GRPCCodeField: codes.Unauthenticated,
    }
    View Source
    var ErrUnsupportedMediaType = DefaultError{
    	StatusField:   http.StatusText(http.StatusUnsupportedMediaType),
    	ErrorField:    "The request is using an unknown content type",
    	CodeField:     http.StatusUnsupportedMediaType,
    	GRPCCodeField: codes.InvalidArgument,
    }

    Functions

    func DefaultErrorReporter

    func DefaultErrorReporter(logger *logrusx.Logger, args ...interface{}) func(w http.ResponseWriter, r *http.Request, code int, err error)

    func StreamErrorUnwrapInterceptor

    func StreamErrorUnwrapInterceptor(srv interface{}, ss grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error

      StreamErrorUnwrapInterceptor is a gRPC server-side interceptor that unwraps herodot errors for Streaming RPCs. See https://github.com/grpc/grpc-go/issues/2934 for why this is necessary.

      func UnaryErrorUnwrapInterceptor

      func UnaryErrorUnwrapInterceptor(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error)

        UnaryErrorUnwrapInterceptor is a gRPC server-side interceptor that unwraps herodot errors for Unary RPCs. See https://github.com/grpc/grpc-go/issues/2934 for why this is necessary.

        func UnescapedHTML

        func UnescapedHTML(enc *json.Encoder)

          UnescapedHTML prevents HTML entities &, <, > from being unicode-escaped.

          Types

          type DefaultError

          type DefaultError struct {
          	CodeField     int                    `json:"code,omitempty"`
          	GRPCCodeField codes.Code             `json:"-"`
          	StatusField   string                 `json:"status,omitempty"`
          	RIDField      string                 `json:"request,omitempty"`
          	ReasonField   string                 `json:"reason,omitempty"`
          	DebugField    string                 `json:"debug,omitempty"`
          	DetailsField  map[string]interface{} `json:"details,omitempty"`
          	ErrorField    string                 `json:"message"`
          	// contains filtered or unexported fields
          }

          func ToDefaultError

          func ToDefaultError(err error, id string) *DefaultError

          func (DefaultError) Debug

          func (e DefaultError) Debug() string

          func (DefaultError) Details

          func (e DefaultError) Details() map[string]interface{}

          func (DefaultError) Error

          func (e DefaultError) Error() string

          func (DefaultError) Format

          func (e DefaultError) Format(s fmt.State, verb rune)

          func (DefaultError) GRPCStatus

          func (e DefaultError) GRPCStatus() *status.Status

          func (DefaultError) Is

          func (e DefaultError) Is(err error) bool

          func (DefaultError) Reason

          func (e DefaultError) Reason() string

          func (DefaultError) RequestID

          func (e DefaultError) RequestID() string

          func (*DefaultError) StackTrace

          func (e *DefaultError) StackTrace() (trace errors.StackTrace)

            StackTrace returns the error's stack trace.

            func (DefaultError) Status

            func (e DefaultError) Status() string

            func (DefaultError) StatusCode

            func (e DefaultError) StatusCode() int

            func (DefaultError) Unwrap

            func (e DefaultError) Unwrap() error

            func (DefaultError) WithDebug

            func (e DefaultError) WithDebug(debug string) *DefaultError

            func (DefaultError) WithDebugf

            func (e DefaultError) WithDebugf(debug string, args ...interface{}) *DefaultError

            func (DefaultError) WithDetail

            func (e DefaultError) WithDetail(key string, detail interface{}) *DefaultError

            func (DefaultError) WithDetailf

            func (e DefaultError) WithDetailf(key string, message string, args ...interface{}) *DefaultError

            func (DefaultError) WithError

            func (e DefaultError) WithError(message string) *DefaultError

            func (DefaultError) WithErrorf

            func (e DefaultError) WithErrorf(message string, args ...interface{}) *DefaultError

            func (DefaultError) WithReason

            func (e DefaultError) WithReason(reason string) *DefaultError

            func (DefaultError) WithReasonf

            func (e DefaultError) WithReasonf(reason string, args ...interface{}) *DefaultError

            func (*DefaultError) WithTrace

            func (e *DefaultError) WithTrace(err error) *DefaultError

            func (DefaultError) WithWrap

            func (e DefaultError) WithWrap(err error) *DefaultError

            func (*DefaultError) Wrap

            func (e *DefaultError) Wrap(err error)

            type EncoderOptions

            type EncoderOptions func(*json.Encoder)

            type JSONWriter

            type JSONWriter struct {
            	Reporter      reporter
            	ErrorEnhancer func(r *http.Request, err error) interface{}
            	// contains filtered or unexported fields
            }

              json outputs JSON.

              func NewJSONWriter

              func NewJSONWriter(logger *logrusx.Logger) *JSONWriter

                NewJSONWriter returns a json

                func (*JSONWriter) Write

                func (h *JSONWriter) Write(w http.ResponseWriter, r *http.Request, e interface{}, opts ...EncoderOptions)

                  Write a response object to the ResponseWriter with status code 200.

                  func (*JSONWriter) WriteCode

                  func (h *JSONWriter) WriteCode(w http.ResponseWriter, r *http.Request, code int, e interface{}, opts ...EncoderOptions)

                    WriteCode writes a response object to the ResponseWriter and sets a response code.

                    func (*JSONWriter) WriteCreated

                    func (h *JSONWriter) WriteCreated(w http.ResponseWriter, r *http.Request, location string, e interface{})

                      WriteCreated writes a response object to the ResponseWriter with status code 201 and the Location header set to location.

                      func (*JSONWriter) WriteError

                      func (h *JSONWriter) WriteError(w http.ResponseWriter, r *http.Request, err error, opts ...Option)

                        WriteError writes an error to ResponseWriter and tries to extract the error's status code by asserting statusCodeCarrier. If the error does not implement statusCodeCarrier, the status code is set to 500.

                        func (*JSONWriter) WriteErrorCode

                        func (h *JSONWriter) WriteErrorCode(w http.ResponseWriter, r *http.Request, code int, err error, opts ...Option)

                          WriteErrorCode writes an error to ResponseWriter and forces an error code.

                          type NegotiationHandler

                          type NegotiationHandler struct {
                          	// contains filtered or unexported fields
                          }

                            NegotiationHandler automatically negotiates the content type with the request client.

                            func NewNegotiationHandler

                            func NewNegotiationHandler(logger *logrusx.Logger) *NegotiationHandler

                              NewNegotiationHandler creates a new NewNegotiationHandler.

                              func (*NegotiationHandler) Write

                              func (h *NegotiationHandler) Write(w http.ResponseWriter, r *http.Request, e interface{})

                                Write a response object to the ResponseWriter with status code 200.

                                func (*NegotiationHandler) WriteCode

                                func (h *NegotiationHandler) WriteCode(w http.ResponseWriter, r *http.Request, code int, e interface{})

                                  WriteCode writes a response object to the ResponseWriter and sets a response code.

                                  func (*NegotiationHandler) WriteCreated

                                  func (h *NegotiationHandler) WriteCreated(w http.ResponseWriter, r *http.Request, location string, e interface{})

                                    WriteCreated writes a response object to the ResponseWriter with status code 201 and the Location header set to location.

                                    func (*NegotiationHandler) WriteError

                                    func (h *NegotiationHandler) WriteError(w http.ResponseWriter, r *http.Request, err error)

                                      WriteError writes an error to ResponseWriter and tries to extract the error's status code by asserting statusCodeCarrier. If the error does not implement statusCodeCarrier, the status code is set to 500.

                                      func (*NegotiationHandler) WriteErrorCode

                                      func (h *NegotiationHandler) WriteErrorCode(w http.ResponseWriter, r *http.Request, code int, err error)

                                        WriteErrorCode writes an error to ResponseWriter and forces an error code.

                                        type Option

                                        type Option func(*options)

                                        func NoLog

                                        func NoLog() Option

                                        type TextWriter

                                        type TextWriter struct {
                                        	Reporter reporter
                                        	// contains filtered or unexported fields
                                        }

                                          json outputs JSON.

                                          func NewTextWriter

                                          func NewTextWriter(logger *logrusx.Logger, contentType string) *TextWriter

                                            NewPlainWriter returns a json

                                            func (*TextWriter) Write

                                            func (h *TextWriter) Write(w http.ResponseWriter, r *http.Request, e interface{})

                                              Write a response object to the ResponseWriter with status code 200.

                                              func (*TextWriter) WriteCode

                                              func (h *TextWriter) WriteCode(w http.ResponseWriter, r *http.Request, code int, e interface{})

                                                WriteCode writes a response object to the ResponseWriter and sets a response code.

                                                func (*TextWriter) WriteCreated

                                                func (h *TextWriter) WriteCreated(w http.ResponseWriter, r *http.Request, location string, e interface{})

                                                  WriteCreated writes a response object to the ResponseWriter with status code 201 and the Location header set to location.

                                                  func (*TextWriter) WriteError

                                                  func (h *TextWriter) WriteError(w http.ResponseWriter, r *http.Request, err interface{})

                                                    WriteError writes an error to ResponseWriter and tries to extract the error's status code by asserting statusCodeCarrier. If the error does not implement statusCodeCarrier, the status code is set to 500.

                                                    func (*TextWriter) WriteErrorCode

                                                    func (h *TextWriter) WriteErrorCode(w http.ResponseWriter, r *http.Request, code int, err interface{})

                                                      WriteErrorCode writes an error to ResponseWriter and forces an error code.

                                                      type Writer

                                                      type Writer interface {
                                                      	// Write a response object to the ResponseWriter with status code 200.
                                                      	Write(w http.ResponseWriter, r *http.Request, e interface{}, opts ...EncoderOptions)
                                                      
                                                      	// WriteCode writes a response object to the ResponseWriter and sets a response code.
                                                      	WriteCode(w http.ResponseWriter, r *http.Request, code int, e interface{}, opts ...EncoderOptions)
                                                      
                                                      	// WriteCreated writes a response object to the ResponseWriter with status code 201 and
                                                      	// the Location header set to location.
                                                      	WriteCreated(w http.ResponseWriter, r *http.Request, location string, e interface{})
                                                      
                                                      	// WriteError writes an error to ResponseWriter and tries to extract the error's status code by
                                                      	// asserting statusCodeCarrier. If the error does not implement statusCodeCarrier, the status code
                                                      	// is set to 500.
                                                      	WriteError(w http.ResponseWriter, r *http.Request, err error, opts ...Option)
                                                      
                                                      	// WriteErrorCode writes an error to ResponseWriter and forces an error code.
                                                      	WriteErrorCode(w http.ResponseWriter, r *http.Request, code int, err error, opts ...Option)
                                                      }

                                                        Writer is a helper to write arbitrary data to a ResponseWriter

                                                        Directories

                                                        Path Synopsis
                                                        Package httputil is a toolkit for the Go net/http package.
                                                        Package httputil is a toolkit for the Go net/http package.
                                                        header
                                                        Package header provides functions for parsing HTTP headers.
                                                        Package header provides functions for parsing HTTP headers.