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 glide 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,
    }
    View Source
    var ErrConflict = DefaultError{
    	StatusField: http.StatusText(http.StatusConflict),
    	ErrorField:  "The resource could not be created due to a conflict",
    	CodeField:   http.StatusConflict,
    }
    View Source
    var ErrForbidden = DefaultError{
    	StatusField: http.StatusText(http.StatusForbidden),
    	ErrorField:  "The requested action was forbidden",
    	CodeField:   http.StatusForbidden,
    }
    View Source
    var ErrInternalServerError = DefaultError{
    	StatusField: http.StatusText(http.StatusInternalServerError),
    	ErrorField:  "An internal server error occurred, please contact the system administrator",
    	CodeField:   http.StatusInternalServerError,
    }
    View Source
    var ErrNotFound = DefaultError{
    	StatusField: http.StatusText(http.StatusNotFound),
    	ErrorField:  "The requested resource could not be found",
    	CodeField:   http.StatusNotFound,
    }
    View Source
    var ErrUnauthorized = DefaultError{
    	StatusField: http.StatusText(http.StatusUnauthorized),
    	ErrorField:  "The request could not be authorized",
    	CodeField:   http.StatusUnauthorized,
    }
    View Source
    var ErrUnsupportedMediaType = DefaultError{
    	StatusField: http.StatusText(http.StatusUnsupportedMediaType),
    	ErrorField:  "The request is using an unknown content type",
    	CodeField:   http.StatusUnsupportedMediaType,
    }

    Functions

    func DefaultErrorReporter

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

    Types

    type DefaultError

    type DefaultError struct {
    	CodeField    int                    `json:"code,omitempty"`
    	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) 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 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{})

            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{})

              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{})
                                                
                                                	// WriteCode writes a response object to the ResponseWriter and sets a response code.
                                                	WriteCode(w http.ResponseWriter, r *http.Request, code int, e interface{})
                                                
                                                	// 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