jsh

package module
v0.0.0-...-76701a9 Latest Latest
Warning

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

Go to latest
Published: Jun 29, 2017 License: MIT Imports: 10 Imported by: 7

README

A Go JSONAPI Specification Handler

GoDoc Travis CI Go Report Card TestCoverage

A (de)serialization handler for writing JSON API Specification compatible software in Go. Works with Ember/Ember-Data too!

Contents

  1. JSH
  1. JSC
  2. JSH-API

jsh - JSON Specification Handler

For streamlined JSONAPI object serialization. Uses govalidator for input validation.

import github.com/derekdowling/go-json-spec-handler

type User struct {
  // valid from github.com/asaskevich/govalidator gives us input validation
  // when object.Unmarshal() is invoked on this type
  Name string `json:"name" valid:"alphanum"`
}

// example http.HandlerFunc for PATCH /users/1
func PatchUser(w http.ResponseWriter, r *http.Request) {

  // performs Specification checks against the request
  object, err := jsh.ParseObject(r)
  if err != nil {
    // jsh returns API friendly errors, that are easy to respond with
    jsh.Send(w, r, err)
    return
  }

  // use object.ID to look up user/do business logic
  user := &User{}
  
  // unmarshal data into relevant internal types if govalidator passes, otherwise
  // return the pre-formatted HTTP 422 error to signify how the input failed
  err = object.Unmarshal("users", user)
  if err != nil {
    jsh.Send(w, r, err)
    return
  }

  // modify your resource object
  user.Name = "Bob"

  // repackage and send the updated resource as a response
  err = object.Marshal(user)
  if err != nil {
    jsh.Send(w, r, err)
    return
  }

  jsh.Send(w, r, object)
}

Motivation for JSH

JSH was written for tackling the issue of dealing with Ember-Data within a pre-existing API server. In sticking with Go's philosophy of modules over frameworks, it is intended to be a drop in serialization layer focusing only on parsing, validating, and sending JSONAPI compatible responses.

Features

Implemented:

- Handles both single object and array based JSON requests and responses
- Input validation with HTTP 422 Status support via [go-validator](https://github.com/go-validator/validator)
- Client request validation with HTTP 406 Status responses
- Links, Relationship, Meta fields
- Prepackaged error responses, easy to use Internal Service Error builder
- Smart responses with correct HTTP Statuses based on Request Method and HTTP Headers
- HTTP Client for GET, POST, DELETE, PATCH

TODO:

- [Reserved character checking](http://jsonapi.org/format/upcoming/#document-member-names-reserved-characters)

Not Implementing:

* These features aren't handled because they are beyond the scope of what
  this module is meant to achieve. See [jshapi](https://github.com/derekdowling/go-json-spec-handler/tree/master/jsh-api)
  for a full-fledged API solution that solves many of these problems.

- Routing
- Sorting
- Pagination
- Filtering
- ORM

Stability

jsh has a mostly stabilized core data document model. At this point in time I am not yet ready to declare v1, but am actively trying to avoid breaking the public API. The areas most likely to receive improvement include relationship, link, and metadata management. At this point in time I can confidentally suggest you use jsh without risking major upgrade incompatibility going forward!

jsc - JSON Specification Client

A HTTP JSONAPI Client for making outbound server requests. Built on top of http.Client and jsh:

import github.com/derekdowling/go-json-spec-handler/client

// GET http://your.api/users/1
document, resp, err := jsc.Fetch("http://your.api/", "users", "1")
object := doc.First()

user := &yourUser{}
err := object.Unmarshal("users", user)

JSH-API

If you're looking for a good place to start with a new API, I've since created jshapi which builds on top of Goji 2 and jsh in order to handle the routing structure that JSON API requires as well as a number of other useful tools for testing and mocking APIs as you develop your own projects. JSHAPI is similar in spirit to this project as it inteniontally focuses on eliminating boilerplate from your projects without trying to solve all of the world's problems.

Examples

There are lots of great examples in the tests themselves that show exactly how jsh works. The godocs as linked above have a number of examples in them as well.

Documentation

Overview

Package jsh (JSON API Specification Handler) makes it easy to parse JSON API requests and send responses that match the JSON API Specification: http://jsonapi.org/ from your server.

For a request client, see: jsc: https://godoc.org/github.com/derekdowling/go-json-spec-handler/client

For a full http.Handler API builder see jshapi: https://godoc.org/github.com/derekdowling/go-json-spec-handler/jsh-api

Index

Constants

View Source
const (
	// ContentType is the data encoding of choice for HTTP Request and Response Headers
	ContentType = "application/vnd.api+json"
)
View Source
const JSONAPIVersion = "1.1"

JSONAPIVersion is version of JSON API Spec that is currently compatible: http://jsonapi.org/format/1.1/

View Source
const MaxContentLength int64 = 10 << 20

MaxContentLength is 10MB https://github.com/golang/go/blob/abb3c0618b658a41bf91a087f1737412e93ff6d9/src/pkg/net/http/request.go#L617

Variables

View Source
var DefaultErrorDetail = "Request failed, something went wrong."

DefaultError can be customized in order to provide a more customized error Detail message when an Internal Server Error occurs. Optionally, you can modify a returned jsh.Error before sending it as a response as well.

View Source
var DefaultErrorTitle = "Internal Server Error"

DefaultTitle can be customized to provide a more customized ISE Title

View Source
var IncludeJSONAPIVersion = true

IncludeJSONAPIVersion is an option that allows consumers to include/remove the `jsonapi` top-level member from server responses.

Functions

func CreateReadCloser

func CreateReadCloser(data []byte) io.ReadCloser

CreateReadCloser is a helper function for dealing with creating HTTP requests

func NewObject

func NewObject(id string, resourceType string, attributes interface{}) (*Object, *Error)

NewObject prepares a new JSON Object for an API response. Whatever is provided as attributes will be marshalled to JSON.

func ParseDoc

func ParseDoc(r *http.Request, mode DocumentMode) (*Document, *Error)

ParseDoc parses and returns a top level jsh.Document. In most cases, using "ParseList" or "ParseObject" is preferable.

func ParseList

func ParseList(r *http.Request) (List, *Error)

ParseList validates the HTTP request and returns a resulting list of objects parsed from the request Body. Use just like ParseObject.

func ParseObject

func ParseObject(r *http.Request) (*Object, *Error)

ParseObject validates the HTTP request and returns a JSON object for a given io.ReadCloser containing a raw JSON payload. Here's an example of how to use it as part of your full flow.

func Handler(w http.ResponseWriter, r *http.Request) {
	obj, error := jsh.ParseObject(r)
	if error != nil {
		// log your error
		err := jsh.Send(w, r, error)
		return
	}

	yourType := &YourType{}

	err := object.Unmarshal("yourtype", &yourType)
	if err != nil {
		err := jsh.Send(w, r, err)
		return
	}

	yourType.ID = obj.ID
	// do business logic

	err := object.Marshal(yourType)
	if err != nil {
		// log error
		err := jsh.Send(w, r, err)
		return
	}

	err := jsh.Send(w, r, object)
}

Types

type Document

type Document struct {
	Data List `json:"data"`
	// Object   *Object     `json:"-"`
	Errors   ErrorList   `json:"errors,omitempty"`
	Links    *Links      `json:"links,omitempty"`
	Included []*Object   `json:"included,omitempty"`
	Meta     interface{} `json:"meta,omitempty"`
	JSONAPI  *JSONAPI    `json:"jsonapi,omitempty"`
	// Status is an HTTP Status Code
	Status int `json:"-"`
	// DataMode to enforce for the document
	Mode DocumentMode `json:"-"`
	// contains filtered or unexported fields
}

Document represents a top level JSON formatted Document. Refer to the JSON API Specification for a full descriptor of each attribute: http://jsonapi.org/format/#document-structure

func Build

func Build(payload Sendable) *Document

Build creates a Sendable Document with the provided sendable payload, either Data or errors. Build also assumes you've already validated your data with .Validate() so it should be used carefully.

func New

func New() *Document

New instantiates a new JSON Document object.

func Ok

func Ok() *Document

Ok makes it simple to return a 200 OK response via jsh:

jsh.SendDocument(w, r, jsh.Ok())

func (*Document) AddError

func (d *Document) AddError(newErr *Error) *Error

AddError adds an error to the Document. It will also set the document Mode to "ErrorMode" if not done so already.

func (*Document) AddObject

func (d *Document) AddObject(object *Object) *Error

AddObject adds another object to the JSON Document after validating it.

func (*Document) Error

func (d *Document) Error() string

func (*Document) First

func (d *Document) First() *Object

First will return the first object from the document data if possible.

func (*Document) HasData

func (d *Document) HasData() bool

HasData will return true if the JSON document's Data field is set

func (*Document) HasErrors

func (d *Document) HasErrors() bool

HasErrors will return true if the Errors attribute is not nil.

func (*Document) MarshalJSON

func (d *Document) MarshalJSON() ([]byte, error)

MarshalJSON handles the custom serialization case caused by case where the "data" element of a document might be either a single resource object, or a collection of them.

func (*Document) Validate

func (d *Document) Validate(r *http.Request, isResponse bool) *Error

Validate performs document level checks against the JSONAPI specification. It is assumed that if this call returns without an error, your document is valid and can be sent as a request or response.

type DocumentMode

type DocumentMode int

DocumentMode allows different specification settings to be enforced based on the specified mode.

const (
	// ObjectMode enforces fetch request/response specifications
	ObjectMode DocumentMode = iota
	// ListMode enforces listing request/response specifications
	ListMode
	// ErrorMode enforces error response specifications
	ErrorMode
)

type Error

type Error struct {
	Title  string `json:"title"`
	Detail string `json:"detail"`
	Status int    `json:"status,string"`
	Source struct {
		Pointer string `json:"pointer"`
	} `json:"source"`
	ISE string `json:"-"`
}

Error consists of a number of contextual attributes to make conveying certain error type simpler as per the JSON API specification: http://jsonapi.org/format/#error-objects

error := &jsh.Error{
	Title: "Authentication Failure",
	Detail: "Category 4 Username Failure",
	Status: 401
}

jsh.Send(w, r, error)

func ISE

func ISE(internalMessage string) *Error

ISE is a convenience function for creating a ready-to-go Internal Service Error response. The message you pass in is set to the ErrorObject.ISE attribute so you can gracefully log ISE's internally before sending them.

func InputError

func InputError(msg string, attribute string) *Error

InputError creates a properly formatted HTTP Status 422 error with an appropriate user safe message. The parameter "attribute" will format err.Source.Pointer to be "/data/attributes/<attribute>".

func NotFound

func NotFound(resourceType string, id string) *Error

NotFound returns a 404 formatted error

func Send

func Send(w http.ResponseWriter, r *http.Request, payload Sendable) *Error

Send will return a JSON payload to the requestor. If the payload response validation fails, it will send an appropriate error to the requestor and will return the error

func SendDocument

func SendDocument(w http.ResponseWriter, r *http.Request, document *Document) *Error

SendDocument handles sending a fully prepared JSON Document. This is useful if you require custom validation or additional build steps before sending.

SendJSON is designed to always send a response, but will also return the last error it encountered to help with debugging in the event of an Internal Server Error.

func SpecificationError

func SpecificationError(detail string) *Error

SpecificationError is used whenever the Client violates the JSON API Spec

func (*Error) Error

func (e *Error) Error() string

Error will print an internal server error if set, or default back to the SafeError() format if not. As usual, err.Error() should not be considered safe for presentation to the end user, use err.SafeError() instead.

func (*Error) StatusCode

func (e *Error) StatusCode() int

StatusCode (HTTP) for the error. Defaults to 0.

func (*Error) Validate

func (e *Error) Validate(r *http.Request, response bool) *Error

Validate ensures that the an error meets all JSON API criteria.

type ErrorList

type ErrorList []*Error

ErrorList is wraps an Error Array so that it can implement Sendable

func (ErrorList) Error

func (e ErrorList) Error() string

Fulfills the default error interface

func (ErrorList) StatusCode

func (e ErrorList) StatusCode() int

StatusCode (HTTP) of the first error in the list. Defaults to 0 if the list is empty or one has not yet been set for the first error.

func (ErrorList) Validate

func (e ErrorList) Validate(r *http.Request, response bool) *Error

Validate checks all errors within the list to ensure that they are valid

type ErrorType

type ErrorType interface {
	// Error returns a formatted error and allows it to conform to the stdErr
	// interface.
	Error() string
	// Validate checks that the error is valid in the context of JSONAPI
	Validate(r *http.Request, response bool) *Error
	// StatusCode returns the first encountered HTTP Status Code for the error type.
	// Returns 0 if none is set.
	StatusCode() int
}

ErrorType represents the common interface requirements that libraries may specify if they would like to accept either a single error or a list.

type JSONAPI

type JSONAPI struct {
	Version string `json:"version"`
}

JSONAPI is the top-level member of a JSONAPI document that includes the server compatible version of the JSONAPI specification.

type Link struct {
	HREF string                 `json:"href,omitempty"`
	Meta map[string]interface{} `json:"meta,omitempty"`
}

Link is a resource link that can encode as a string or as an object as per the JSON API specification.

func NewLink(href string) *Link

NewLink creates a new link encoded as a string.

func NewMetaLink(href string, meta map[string]interface{}) *Link

NewMetaLink creates a new link with metadata encoded as an object.

func (*Link) MarshalJSON

func (l *Link) MarshalJSON() ([]byte, error)

MarshalJSON implements the Marshaler interface for Link.

func (*Link) UnmarshalJSON

func (l *Link) UnmarshalJSON(data []byte) error

UnmarshalJSON implements the Unmarshaler interface for Link.

type Links struct {
	Self    *Link `json:"self,omitempty"`
	Related *Link `json:"related,omitempty"`
}

Links is a top-level document field

type List

type List []*Object

List is just a wrapper around an object array that implements Sendable

func (*List) UnmarshalJSON

func (list *List) UnmarshalJSON(rawData []byte) error

UnmarshalJSON allows us to manually decode a list via the json.Unmarshaler interface.

func (List) Validate

func (list List) Validate(r *http.Request, response bool) *Error

Validate ensures that List is JSON API compatible.

type Object

type Object struct {
	Type          string                   `json:"type" valid:"required"`
	ID            string                   `json:"id"`
	Attributes    json.RawMessage          `json:"attributes,omitempty"`
	Links         map[string]*Link         `json:"links,omitempty"`
	Relationships map[string]*Relationship `json:"relationships,omitempty"`
	Meta          map[string]interface{}   `json:"meta,omitempty"`
	// Status is the HTTP Status Code that should be associated with the object
	// when it is sent.
	Status int `json:"-"`
}

Object represents the default JSON spec for objects

func (*Object) Marshal

func (o *Object) Marshal(attributes interface{}) *Error

Marshal allows you to load a modified payload back into an object to preserve all of the data it has.

func (*Object) String

func (o *Object) String() string

String prints a formatted string representation of the object

func (*Object) Unmarshal

func (o *Object) Unmarshal(resourceType string, target interface{}) ErrorList

Unmarshal puts an Object's Attributes into a more useful target resourceType defined by the user. A correct object resourceType specified must also be provided otherwise an error is returned to prevent hard to track down situations.

Optionally, used https://github.com/go-validator/validator for request input validation. Simply define your struct with valid input tags:

struct {
	Username string `json:"username" valid:"required,alphanum"`
}

As the final action, the Unmarshal function will run govalidator on the unmarshal result. If the validator fails, a Sendable error response of HTTP Status 422 will be returned containing each validation error with a populated Error.Source.Pointer specifying each struct attribute that failed. In this case, all you need to do is:

errors := obj.Unmarshal("mytype", &myType)
if errors != nil {
	// log errors via error.ISE
	jsh.Send(w, r, errors)
}

func (*Object) Validate

func (o *Object) Validate(r *http.Request, response bool) *Error

Validate ensures that an object is JSON API compatible. Has a side effect of also setting the Object's Status attribute to be used as the Response HTTP Code if one has not already been set.

type Parser

type Parser struct {
	Method  string
	Headers http.Header
}

Parser is an abstraction layer that helps to support parsing JSON payload from many types of sources, and allows other libraries to leverage this if desired.

func NewParser

func NewParser(request *http.Request) *Parser

NewParser creates a parser from an http.Request

func (*Parser) Document

func (p *Parser) Document(payload io.ReadCloser, mode DocumentMode) (*Document, *Error)

Document returns a single JSON data object from the parser. In the process it will also validate any data objects against the JSON API.

type Relationship

type Relationship struct {
	Links *Links                 `json:"links,omitempty"`
	Data  ResourceLinkage        `json:"data,omitempty"`
	Meta  map[string]interface{} `json:"meta,omitempty"`
}

Relationship represents a reference from the resource object in which it's defined to other resource objects.

type ResourceIdentifier

type ResourceIdentifier struct {
	Type string `json:"type" valid:"required"`
	ID   string `json:"id" valid:"required"`
}

ResourceIdentifier identifies an individual resource.

type ResourceLinkage

type ResourceLinkage []*ResourceIdentifier

ResourceLinkage is a typedef around a slice of resource identifiers. This allows us to implement a custom UnmarshalJSON.

func (*ResourceLinkage) UnmarshalJSON

func (rl *ResourceLinkage) UnmarshalJSON(data []byte) error

UnmarshalJSON allows us to manually decode a the resource linkage via the json.Unmarshaler interface.

type Sendable

type Sendable interface {
	Validate(r *http.Request, response bool) *Error
}

Sendable implements functions that allows different response types to produce a sendable JSON Response format

Directories

Path Synopsis
_vendor
github.com/asaskevich/govalidator
Package govalidator is package of validators and sanitizers for strings, structs and collections.
Package govalidator is package of validators and sanitizers for strings, structs and collections.
github.com/derekdowling/go-stdlogger
Package std is the missing standard logging interface that should be present within Go.
Package std is the missing standard logging interface that should be present within Go.
github.com/gopherjs/gopherjs/js
Package js provides functions for interacting with native JavaScript APIs.
Package js provides functions for interacting with native JavaScript APIs.
github.com/jtolds/gls
Package gls implements goroutine-local storage.
Package gls implements goroutine-local storage.
github.com/smartystreets/assertions
Package assertions contains the implementations for all assertions which are referenced in goconvey's `convey` package (github.com/smartystreets/goconvey/convey) and gunit (github.com/smartystreets/gunit) for use with the So(...) method.
Package assertions contains the implementations for all assertions which are referenced in goconvey's `convey` package (github.com/smartystreets/goconvey/convey) and gunit (github.com/smartystreets/gunit) for use with the So(...) method.
github.com/smartystreets/assertions/internal/oglematchers
Package oglematchers provides a set of matchers useful in a testing or mocking framework.
Package oglematchers provides a set of matchers useful in a testing or mocking framework.
github.com/smartystreets/goconvey/convey
Package convey contains all of the public-facing entry points to this project.
Package convey contains all of the public-facing entry points to this project.
github.com/smartystreets/goconvey/convey/gotest
Package gotest contains internal functionality.
Package gotest contains internal functionality.
github.com/smartystreets/goconvey/convey/reporting
Package reporting contains internal functionality related to console reporting and output.
Package reporting contains internal functionality related to console reporting and output.
github.com/zenazn/goji/web/mutil
Package mutil contains various functions that are helpful when writing http middleware.
Package mutil contains various functions that are helpful when writing http middleware.
goji.io
Package goji is a minimalistic and flexible HTTP request multiplexer.
Package goji is a minimalistic and flexible HTTP request multiplexer.
goji.io/internal
Package internal is a private package that allows Goji to expose a less confusing interface to its users.
Package internal is a private package that allows Goji to expose a less confusing interface to its users.
goji.io/pat
Package pat is a URL-matching domain-specific language for Goji.
Package pat is a URL-matching domain-specific language for Goji.
goji.io/pattern
Package pattern contains utilities for Goji Pattern authors.
Package pattern contains utilities for Goji Pattern authors.
Package jsc (JSON Specification Client) is an http client that makes sending HTTP requests that match the JSON Specification: http://jsonapi.org/ simple.
Package jsc (JSON Specification Client) is an http client that makes sending HTTP requests that match the JSON Specification: http://jsonapi.org/ simple.
Package jshapi is a http.Handler compatible wrapper that makes building JSON API resource handlers easy.
Package jshapi is a http.Handler compatible wrapper that makes building JSON API resource handlers easy.
store
Package store is a collection of composable interfaces that are can be implemented in order to build a storage driver
Package store is a collection of composable interfaces that are can be implemented in order to build a storage driver

Jump to

Keyboard shortcuts

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