problems

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2019 License: Apache-2.0 Imports: 5 Imported by: 16

README

problems

Problems is an RFC-7807 compliant library for describing HTTP errors, written purely in Go. For more information see RFC-7807.

Build Status Go Report Card GoDoc

Usage

The problems library exposes an assortment of interfaces, structs, and functions for defining and using HTTP Problem detail resources.

Predefined Errors

You can define basic Problem details up front by using the NewStatusProblem function

import "github.com/moogar0880/problems"

var (
  // The NotFound problem will be built with an appropriate status code and
  // informative title set. Additional information can be provided in the Detail
  // field of the generated struct
  NotFound = problems.NewStatusProblem(404)
)

Which, when served over HTTP as JSON will look like the following:

{
   "type": "about:blank",
   "title": "Not Found",
   "status": 404
}
Detailed Errors

New errors can also be created a head of time, or on the fly like so:

import "github.com/moogar0880/problems"

func NoSuchUser() *problems.DefaultProblem {
	nosuch := problems.NewStatusProblem(404)
	nosuch.Detail = "Sorry, that user does not exist."
	return nosuch
}

Which, when served over HTTP as JSON will look like the following:

{
   "type": "about:blank",
   "title": "Not Found",
   "status": 404,
   "detail": "Sorry, that user does not exist."
}
Expanded Errors

The specification for these HTTP problems was designed to allow for arbitrary expansion of the problem resources. This can be accomplished through this library by implementing the Problem interface:

import "github.com/moogar0880/problems"

type CreditProblem struct {
	problems.DefaultProblem

    Balance  float64
    Accounts []string
}

func (cp *CreditProblem) ProblemType() (*url.URL, error) {
	u, err := url.Parse(cp.Type)
	if err != nil {
		return nil, err
	}
	return u, nil
}

func (cp *CreditProblem) ProblemTitle() string {
	return cp.Title
}

Which, when served over HTTP as JSON will look like the following:

{
   "type": "about:blank",
   "title": "Unauthorized",
   "status": 401,
   "balance": 30,
   "accounts": ["/account/12345", "/account/67890"]
}

Serving Problems

Additionally, RFC-7807 defines two new media types for problem resources, application/problem+json" and application/problem+xml. This library defines those media types as the constants ProblemMediaType and ProblemMediaTypeXML.

In order to facilitate serving problem definitions, this library exposes two http.HandlerFunc implementations which accept a problem, and return a functioning HandlerFunc that will server that error.

package main

import (
	"net/http"

    "github.com/moogar0880/problems"
)

var Unauthorized = problems.NewStatusProblem(401)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/secrets", problems.StatusProblemHandler(Unauthorized))

	server := http.Server{Handler: mux, Addr: ":8080"}
	server.ListenAndServe()
}

Documentation

Overview

Package problems provides an RFC 7807 (https://tools.ietf.org/html/rfc7807) compliant implementation of HTTP problem details. Which are defined as a means to carry machine-readable details of errors in an HTTP response to avoid the need to define new error response formats for HTTP APIs.

The problem details specification was designed to allow for schema extensions. Because of this the exposed Problem interface only enforces the required Type and Title fields be set appropriately.

Additionally, this library also ships with default http.HandlerFunc's capable of writing problems to http.ResponseWriter's in either of the two standard media formats JSON and XML.

Index

Examples

Constants

View Source
const (
	// ProblemMediaType is the default media type for a Problem response
	ProblemMediaType = "application/problem+json"

	// ProblemMediaTypeXML is the XML variant on the Problem Media type
	ProblemMediaTypeXML = "application/problem+xml"

	// DefaultURL is the default url to use for problem types
	DefaultURL = "about:blank"
)

Variables

View Source
var ErrTitleMustBeSet = fmt.Errorf("%s: problem title must be set", errPrefix)

ErrTitleMustBeSet is the error returned from a call to ValidateProblem if the problem is validated without a title.

Functions

func NewErrInvalidProblemType

func NewErrInvalidProblemType(e error) error

NewErrInvalidProblemType returns a new ErrInvalidProblemType instance which wraps the provided error.

func ProblemHandler

func ProblemHandler(p Problem) http.HandlerFunc

ProblemHandler returns an http.HandlerFunc which writes a provided problem to an http.ResponseWriter as JSON

func StatusProblemHandler

func StatusProblemHandler(p StatusProblem) http.HandlerFunc

StatusProblemHandler returns an http.HandlerFunc which writes a provided problem to an http.ResponseWriter as JSON with the status code

func ValidateProblem

func ValidateProblem(p Problem) error

ValidateProblem ensures that the provided Problem implementation meets the Problem description requirements. Which means that the Type is a valid uri, and that the Title be a non-empty string. Should the provided Problem be in violation of either of these requirements, an error is returned.

func XMLProblemHandler

func XMLProblemHandler(p Problem) http.HandlerFunc

XMLProblemHandler returns an http.HandlerFunc which writes a provided problem to an http.ResponseWriter as XML

func XMLStatusProblemHandler

func XMLStatusProblemHandler(p StatusProblem) http.HandlerFunc

XMLStatusProblemHandler returns an http.HandlerFunc which writes a provided problem to an http.ResponseWriter as XML with the status code

Types

type DefaultProblem

type DefaultProblem struct {
	// Type contains a URI that identifies the problem type. This URI will,
	// ideally, contain human-readable documentation for the problem when
	// de-referenced.
	Type string `json:"type"`

	// Title is a short, human-readable summary of the problem type. This title
	// SHOULD NOT change from occurrence to occurrence of the problem, except for
	// purposes of localization.
	Title string `json:"title"`

	// The HTTP status code for this occurrence of the problem.
	Status int `json:"status,omitempty"`

	// A human-readable explanation specific to this occurrence of the problem.
	Detail string `json:"detail,omitempty"`

	// A URI that identifies the specific occurrence of the problem. This URI
	// may or may not yield further information if de-referenced.
	Instance string `json:"instance,omitempty"`
}

DefaultProblem is a default problem implementation. The Problem specification allows for arbitrary extensions to include new fields, in which case a new Problem implementation will likely be required.

func NewDetailedProblem

func NewDetailedProblem(status int, details string) *DefaultProblem

NewDetailedProblem returns a new DefaultProblem with a Detail string set for a more detailed explanation of the problem being returned.

func NewProblem

func NewProblem() *DefaultProblem

NewProblem returns a new instance of a DefaultProblem with the DefaultURL set as the problem Type.

func NewStatusProblem

func NewStatusProblem(status int) *DefaultProblem

NewStatusProblem will generate a default problem for the provided HTTP status code. The Problem's Status field will be set to match the status argument, and the Title will be set to the default Go status text for that code.

Example
package main

import (
	"encoding/json"
	"fmt"

	"github.com/moogar0880/problems"
)

func main() {
	notFound := problems.NewStatusProblem(404)
	b, _ := json.MarshalIndent(notFound, "", "  ")
	fmt.Println(string(b))
}
Output:

{
  "type": "about:blank",
  "title": "Not Found",
  "status": 404
}
Example (Detailed)
package main

import (
	"encoding/json"
	"fmt"

	"github.com/moogar0880/problems"
)

func main() {
	notFound := problems.NewStatusProblem(404)
	notFound.Detail = "The item you've requested either does not exist or has been deleted."
	b, _ := json.MarshalIndent(notFound, "", "  ")
	fmt.Println(string(b))
}
Output:

{
  "type": "about:blank",
  "title": "Not Found",
  "status": 404,
  "detail": "The item you've requested either does not exist or has been deleted."
}

func (*DefaultProblem) ProblemStatus

func (p *DefaultProblem) ProblemStatus() int

ProblemStatus allows the DefaultStatusProblem to implement the StatusProblem interface, returning the Status code for this problem.

func (*DefaultProblem) ProblemTitle

func (p *DefaultProblem) ProblemTitle() string

ProblemTitle returns the unique title field for this Problem.

func (*DefaultProblem) ProblemType

func (p *DefaultProblem) ProblemType() (*url.URL, error)

ProblemType returns the uri for the problem type being defined and an optional error if the specified Type is not a valid URI.

type ErrInvalidProblemType

type ErrInvalidProblemType struct {
	Err error
}

ErrInvalidProblemType is the error type returned if a problems type is not a valid URI when it is validated. The inner Err will contain the error returned from attempting to parse the invalid URI.

func (*ErrInvalidProblemType) Error

func (e *ErrInvalidProblemType) Error() string

type Problem

type Problem interface {
	ProblemType() (*url.URL, error)
	ProblemTitle() string
}

Problem is the interface describing an HTTP API problem. These "problem details" are designed to encompass a way to carry machine- readable details of errors in a HTTP response to avoid the need to define new error response formats for HTTP APIs.

type StatusProblem

type StatusProblem interface {
	Problem
	ProblemStatus() int
}

StatusProblem is the interface describing a problem with an associated Status code.

Jump to

Keyboard shortcuts

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