herodot

package module
v0.9.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 30, 2021 License: Apache-2.0 Imports: 15 Imported by: 477

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.

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 added in v0.6.1

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

func StreamErrorUnwrapInterceptor added in v0.9.2

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 added in v0.9.2

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 added in v0.9.4

func UnescapedHTML(enc *json.Encoder)

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

Types

type DefaultError added in v0.1.3

type DefaultError struct {
	// The error ID
	//
	// Useful when trying to identify various errors in application logic.
	IDField string `json:"id,omitempty"`

	// The status code
	//
	// example: 404
	CodeField int `json:"code,omitempty"`

	// The status description
	//
	// example: Not Found
	StatusField string `json:"status,omitempty"`

	// The request ID
	//
	// The request ID is often exposed internally in order to trace
	// errors across service architectures. This is often a UUID.
	//
	// example: d7ef54b1-ec15-46e6-bccb-524b82c035e6
	RIDField string `json:"request,omitempty"`

	// A human-readable reason for the error
	//
	// example: User with ID 1234 does not exist.
	ReasonField string `json:"reason,omitempty"`

	// Debug information
	//
	// This field is often not exposed to protect against leaking
	// sensitive information.
	//
	// example: SQL field "foo" is not a bool.
	DebugField string `json:"debug,omitempty"`

	// Further error details
	DetailsField map[string]interface{} `json:"details,omitempty"`

	// Error message
	//
	// The error's message.
	//
	// example: The resource could not be found
	// required: true
	ErrorField string `json:"message"`

	GRPCCodeField codes.Code `json:"-"`
	// contains filtered or unexported fields
}

swagger:model genericError

func ToDefaultError added in v0.6.1

func ToDefaultError(err error, requestID string) *DefaultError

func (DefaultError) Debug added in v0.5.1

func (e DefaultError) Debug() string

func (DefaultError) Details added in v0.1.3

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

func (DefaultError) Error added in v0.1.3

func (e DefaultError) Error() string

func (DefaultError) Format added in v0.6.1

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

func (DefaultError) GRPCStatus added in v0.9.2

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

func (DefaultError) ID added in v0.9.8

func (e DefaultError) ID() string

func (DefaultError) Is added in v0.8.0

func (e DefaultError) Is(err error) bool

func (DefaultError) Reason added in v0.1.3

func (e DefaultError) Reason() string

func (DefaultError) RequestID added in v0.1.3

func (e DefaultError) RequestID() string

func (*DefaultError) StackTrace added in v0.6.0

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

StackTrace returns the error's stack trace.

func (DefaultError) Status added in v0.1.3

func (e DefaultError) Status() string

func (DefaultError) StatusCode added in v0.1.3

func (e DefaultError) StatusCode() int

func (DefaultError) Unwrap added in v0.8.0

func (e DefaultError) Unwrap() error

func (DefaultError) WithDebug added in v0.5.1

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

func (DefaultError) WithDebugf added in v0.5.1

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

func (DefaultError) WithDetail added in v0.2.0

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

func (DefaultError) WithDetailf added in v0.7.0

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

func (DefaultError) WithError added in v0.5.1

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

func (DefaultError) WithErrorf added in v0.5.1

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

func (DefaultError) WithID added in v0.9.9

func (e DefaultError) WithID(id string) *DefaultError

func (DefaultError) WithReason added in v0.2.0

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

func (DefaultError) WithReasonf added in v0.5.1

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

func (*DefaultError) WithTrace added in v0.6.2

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

func (DefaultError) WithWrap added in v0.8.2

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

func (*DefaultError) Wrap added in v0.8.0

func (e *DefaultError) Wrap(err error)

type EncoderOptions added in v0.9.4

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 added in v0.2.1

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

NegotiationHandler automatically negotiates the content type with the request client.

func NewNegotiationHandler added in v0.2.1

func NewNegotiationHandler(logger *logrusx.Logger) *NegotiationHandler

NewNegotiationHandler creates a new NewNegotiationHandler.

func (*NegotiationHandler) Write added in v0.2.1

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 added in v0.2.1

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 added in v0.2.1

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 added in v0.2.1

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 added in v0.2.1

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 added in v0.9.1

type Option func(*options)

func NoLog added in v0.9.1

func NoLog() Option

type TextWriter added in v0.2.1

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

json outputs JSON.

func NewTextWriter added in v0.2.1

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

NewPlainWriter returns a json

func (*TextWriter) Write added in v0.2.1

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 added in v0.2.1

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 added in v0.2.1

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 added in v0.2.1

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 added in v0.2.1

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL