jshapi

package module
Version: v0.0.0-...-0e6c039 Latest Latest
Warning

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

Go to latest
Published: Sep 19, 2016 License: MIT Imports: 16 Imported by: 0

README

JSH-API

GoDoc Build Status Go Report Card

A JSON API specification micro-service builder created on top of jsh, Goji, and context to handle the nitty gritty but predictable (un)wrapping, validating, preparing, and logging necessary for any JSON API written in Go. The rest (storage, and business logic) is up to you.

Setup

The easiest way to get started is like so:

import github.com/derekdowling/jsh-api

// implement jshapi/store.CRUD interface and add resource specific middleware via Goji
userStorage := &UserStorage{}
resource := jshapi.NewCRUDResource("user", userStorage)
resource.UseC(yourUserMiddleware)

// setup a logger, your shiny new API, and give it a resource
logger := log.New(os.Stderr, "<yourapi>: ", log.LstdFlags)
api := jshapi.Default("<prefix>", true, logger)
api.Add(resource)

// launch your api
http.ListenAndServe("localhost:8000", api)

For a completely custom setup:

import github.com/derekdowling/jsh-api

// manually setup your API
api := jshapi.New("<prefix>")

// add a custom send handler
jshapi.SendHandler = func(c context.Context, w http.ResponseWriter, r *http.Request, sendable jsh.Sendable) {
    // do some custom logging, or manipulation
    jsh.Send(w, r, sendable)
}

// add top level Goji Middleware
api.UseC(yourTopLevelAPIMiddleware)

http.ListenAndServe("localhost:8000", api)

Feature Overview

There are a few things you should know about JSHAPI. First, this project is maintained with emphasis on these two guiding principles:

  • reduce JSONAPI boilerplate in your code as much as possible
  • keep separation of concerns in mind, let developers decide and customize as much as possible

The other major point is that this project uses a small set of storage interfaces that make handling API actions endpoint simple and consistent. In each of the following examples, these storage interfaces are utilized. For more information about how these work, see the Storage Example.

Simple Default CRUD Implementation

Quickly build resource APIs for:

  • POST /resources
  • GET /resources
  • GET /resources/:id
  • DELETE /resources/:id
  • PATCH /resources/:id
resourceStorage := &ResourceStorage{}
resource := jshapi.NewCRUDResource("resources", resourceStorage)
Relationships

Routing for relationships too:

  • GET /resources/:id/relationships/otherResource[s]
  • GET /resources/:id/otherResource[s]
resourceStorage := &ResourceStorage{}
resource := jshapi.NewResource("resources", resourceStorage)
resource.ToOne("foo", fooToOneStorage)
resource.ToMany("bar", barToManyStorage)
Custom Actions
  • GET /resources/:id/
resourceStorage := &ResourceStorage{}
resource := jshapi.NewResource("resources", resourceStorage)
resource.Action("reset", resetAction)
Other Features
  • Default Request, Response, and 5XX Auto-Logging

Working With Storage Interfaces

Below is a basic example of how one might implement parts of a CRUD Storage interface for a basic user resource using jsh for Save and Update. This should give you a pretty good idea of how easy it is to implement the Storage driver with jsh.

type User struct {
    ID string
    Name string `json:"name"`
}

func Save(ctx context.Context, object *jsh.Object) (*jsh.Object, jsh.ErrorType) {
    user := &User{}
    err := object.Unmarshal("user", user)
    if err != nil {
        return err
    }

    // generate your id, however you choose
    user.ID = "1234"

    err := object.Marshal(user)
    if err != nil {
        return nil, err
    }

    // do save logic
    return object, nil
}

func Update(ctx context.Context, object *jsh.Object) (*jsh.Object, jsh.ErrorType) {
    user := &User{}
    err := object.Unmarshal("user", user)
    if err != nil {
        return err
    }

    user.Name = "NewName"
    
    err := object.Marshal(user)
    if err != nil {
        return nil, err
    }

    // perform patch
    return object, nil
}

Documentation

Overview

Package jshapi is a http.Handler compatible wrapper that makes building JSON API resource handlers easy.

Index

Constants

This section is empty.

Variables

View Source
var EnableClientGeneratedIDs bool

EnableClientGeneratedIDs is an option that allows consumers to allow for client generated IDs.

View Source
var SendHandler = DefaultSender(log.New(os.Stderr, "jshapi: ", log.LstdFlags))

SendHandler allows the customization of how API responses are sent and logged. This is used by all jshapi.Resource objects.

Functions

This section is empty.

Types

type API

type API struct {
	*goji.Mux

	Resources map[string]*Resource
	Debug     bool
	// contains filtered or unexported fields
}

API is used to direct HTTP requests to resources

func Default

func Default(prefix string, debug bool, logger std.Logger) *API

Default builds a new top-level API with a few out of the box additions to get people started without needing to add a lot of extra functionality.

The most basic implementation is:

// create a logger, the std log package works, as do most other loggers
// std.Logger interface defined here:
// https://github.com/derekdowling/go-stdlogger/blob/master/logger.go
logger := log.New(os.Stderr, "jshapi: ", log.LstdFlags)

// create the API. Specify a http://yourapi/<prefix>/ if required
api := jshapi.Default("<prefix>", false, logger)
api.Add(yourResource)

func New

func New(prefix string) *API

New initializes a new top level API Resource without doing any additional setup.

func (*API) Action

func (a *API) Action(action string, storage store.Action)

func (*API) Add

func (a *API) Add(resource *Resource)

Add implements mux support for a given resource which is effectively handled as: pat.New("/(prefix/)resource.Plu*)

func (*API) RouteTree

func (a *API) RouteTree() string

RouteTree prints out all accepted routes for the API that use jshapi implemented ways of adding routes through resources.

type MockStorage

type MockStorage struct {
	// ResourceType is the name of the resource you are mocking i.e. "user", "comment"
	ResourceType string
	// ResourceAttributes a sample set of attributes a resource object should have
	// used by GET /resources and GET /resources/:id
	ResourceAttributes interface{}
	// ListCount is the number of sample objects to return in a GET /resources request
	ListCount int
}

MockStorage allows you to mock out APIs really easily. It is also used internally for testing the API layer.

func (*MockStorage) Delete

func (m *MockStorage) Delete(ctx context.Context, id string) jsh.ErrorType

Delete does nothing

func (*MockStorage) Get

func (m *MockStorage) Get(ctx context.Context, id string) (*jsh.Object, jsh.ErrorType)

Get returns a resource with ID as specified by the request

func (*MockStorage) List

func (m *MockStorage) List(ctx context.Context) (jsh.List, jsh.ErrorType)

List returns a sample list

func (*MockStorage) SampleList

func (m *MockStorage) SampleList(length int) jsh.List

SampleList generates a sample list of resources that can be used for/against the mock API

func (*MockStorage) SampleObject

func (m *MockStorage) SampleObject(id string) *jsh.Object

SampleObject builds an object based on provided resource specifications

func (*MockStorage) Save

func (m *MockStorage) Save(ctx context.Context, object *jsh.Object) (*jsh.Object, jsh.ErrorType)

Save assigns a URL of 1 to the object

func (*MockStorage) Update

func (m *MockStorage) Update(ctx context.Context, object *jsh.Object) (*jsh.Object, jsh.ErrorType)

Update does nothing

type MockToManyStorage

type MockToManyStorage MockStorage

MockToManyStorage allows you to mock out APIs to-many relationships really easily. \ It is also used internally for testing the API layer.

func (*MockToManyStorage) Delete

func (m *MockToManyStorage) Delete(ctx context.Context, id string, list jsh.IDList) (jsh.IDList, jsh.ErrorType)

Delete does nothing

func (*MockToManyStorage) List

func (m *MockToManyStorage) List(ctx context.Context, id string) (jsh.IDList, jsh.ErrorType)

ListRelationships returns the to-many relationship ID objects as specified by the request

func (*MockToManyStorage) ListResources

func (m *MockToManyStorage) ListResources(ctx context.Context, id string) (jsh.List, jsh.ErrorType)

List returns the to-many relationship resources with ID as specified by the request

func (*MockToManyStorage) SampleIDList

func (m *MockToManyStorage) SampleIDList(id string) jsh.IDList

SampleIDObject builds an ID object based on provided resource specifications

func (*MockToManyStorage) SampleList

func (m *MockToManyStorage) SampleList(id string) jsh.List

SampleObject builds an object based on provided resource specifications

func (*MockToManyStorage) Save

func (m *MockToManyStorage) Save(ctx context.Context, id string, list jsh.IDList) (jsh.IDList, jsh.ErrorType)

Save does nothing

func (*MockToManyStorage) Update

func (m *MockToManyStorage) Update(ctx context.Context, id string, list jsh.IDList) (jsh.IDList, jsh.ErrorType)

Update does nothing

type MockToOneStorage

type MockToOneStorage MockStorage

MockToOneStorage allows you to mock out APIs to-one relationships really easily. \ It is also used internally for testing the API layer.

func (*MockToOneStorage) Get

func (m *MockToOneStorage) Get(ctx context.Context, id string) (*jsh.IDObject, jsh.ErrorType)

GetRelationship returns the to-one relationship ID object as specified by the request

func (*MockToOneStorage) GetResource

func (m *MockToOneStorage) GetResource(ctx context.Context, id string) (*jsh.Object, jsh.ErrorType)

Get returns the to-one relationship resource with ID as specified by the request

func (*MockToOneStorage) SampleIDObject

func (m *MockToOneStorage) SampleIDObject(id string) *jsh.IDObject

SampleIDObject builds an ID object based on provided resource specifications

func (*MockToOneStorage) SampleObject

func (m *MockToOneStorage) SampleObject(id string) *jsh.Object

SampleObject builds an object based on provided resource specifications

func (*MockToOneStorage) Update

func (m *MockToOneStorage) Update(ctx context.Context, id string,
	relationship *jsh.IDObject) (*jsh.IDObject, jsh.ErrorType)

Update does nothing

type Relationship

type Relationship string

Relationship helps define the relationship between two resources

const (
	// ToOne signifies a one to one relationship
	ToOne Relationship = "One-To-One"
	// ToMany signifies a one to many relationship
	ToMany Relationship = "One-To-Many"
)

type Resource

type Resource struct {
	*goji.Mux
	// The singular name of the resource type("user", "post", etc)
	Type string
	// Routes is a list of routes registered to the resource
	Routes []Route
	// Map of relationships
	Relationships map[string]Relationship
}

Resource holds the necessary state for creating a REST API endpoint for a given resource type. Will be accessible via `/<type>`.

Using NewCRUDResource you can generate a generic CRUD handler for a JSON Specification Resource end point. If you wish to only implement a subset of these endpoints that is also available through NewResource() and manually registering storage handlers via .Post(), .Get(), .List(), .Patch(), and .Delete():

Besides the built in registration helpers, it isn't recommended, but you can add your own routes using the goji.Mux API:

func searchHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	name := pat.Param(ctx, "name")
	fmt.Fprintf(w, "Hello, %s!", name)
}

resource := jshapi.NewCRUDResource("users", userStorage)
// creates /users/search/:name
resource.HandleC(pat.New("search/:name"), searchHandler)

func NewCRUDResource

func NewCRUDResource(resourceType string, storage store.CRUD) *Resource

NewCRUDResource generates a resource

func NewMockResource

func NewMockResource(resourceType string, listCount int, sampleObject interface{}) *Resource

NewMockResource builds a mock API endpoint that can perform basic CRUD actions:

GET    /types
POST   /types
GET    /types/:id
DELETE /types/:id
PATCH  /types/:id

Will return objects and lists based upon the sampleObject that is specified here in the constructor.

func NewResource

func NewResource(resourceType string) *Resource

NewResource is a resource constructor that makes no assumptions about routes that you'd like to implement, but still provides some basic utilities for managing routes and handling API calls.

The prefix parameter causes all routes created within the resource to be prefixed.

func (*Resource) Action

func (res *Resource) Action(action string, storage store.Action, allow bool)

Action adds to the resource a custom action of the form: POST /resources/:id/<action>

func (*Resource) CRUD

func (res *Resource) CRUD(storage store.CRUD)

CRUD is syntactic sugar and a shortcut for registering all JSON API CRUD routes for a compatible storage implementation:

Registers handlers for:

GET    /resource
POST   /resource
GET    /resource/:id
DELETE /resource/:id
PATCH  /resource/:id

func (*Resource) Delete

func (res *Resource) Delete(storage store.Delete, allow bool)

Delete registers a `DELETE /resource/:id` handler for the resource.

func (*Resource) DeleteMany

func (res *Resource) DeleteMany(storage store.ToManyUpdate, matcher string, allow bool)

DeleteMany registers a `DELETE /resources/:id/relationships/<relationship>` handler for the resource relationships.

func (*Resource) Get

func (res *Resource) Get(storage store.Get, allow bool)

Get registers a `GET /resource/:id` handler for the resource.

func (*Resource) GetRelated

func (res *Resource) GetRelated(storage store.Get, matcher string, allow bool)

GetRelated registers a `GET /resources/:id/<relationship>` handler for the resource relationship.

func (*Resource) GetRelationship

func (res *Resource) GetRelationship(storage store.ToOneGet, matcher string, allow bool)

GetRelationship registers a `GET /resources/:id/relationships/<relationship>` handler for the resource relationship.

func (*Resource) List

func (res *Resource) List(storage store.List, allow bool)

List registers a `GET /resource` handler for the resource.

func (*Resource) ListRelated

func (res *Resource) ListRelated(storage store.ToManyListResources, matcher string, allow bool)

ListRelated registers a `GET /resources/:id/<relationship>` handler for the resource relationships.

func (*Resource) ListRelationships

func (res *Resource) ListRelationships(storage store.ToManyList, matcher string, allow bool)

ListRelationships registers a `GET /resources/:id/relationships/<relationship>` handler for the resource relationships.

func (*Resource) Options

func (res *Resource) Options(pattern string)

Options registers a `OPTIONS /resource` handler for the resource.

func (*Resource) PartialCRUD

func (res *Resource) PartialCRUD(storage store.CRUD, disallow string)

PartialCRUD registers all CRUD routes with OPTIONS and HEAD support. It provides a handler that sends a 405 response for methods contained in the disallow parameter. Since GET is always allowed, the supported parameters are POST,PATCH,DELETE.

func (*Resource) PartialToMany

func (res *Resource) PartialToMany(relationship string, storage store.ToMany, disallow string)

PartialToMany registers to-many relationships routes with OPTIONS and HEAD support. It provides a handler that sends a 405 response for methods contained in the disallow parameter. Since GET is always allowed, the supported parameters are POST,PATCH,DELETE.

func (*Resource) PartialToOne

func (res *Resource) PartialToOne(relationship string, storage store.ToOne, disallow string)

PartialToOne registers to-one relationships routes with OPTIONS and HEAD support. It provides a handler that sends a 405 response for methods contained in the disallow parameter. Since GET is always allowed, the only supported parameter is PATCH.

func (*Resource) Patch

func (res *Resource) Patch(storage store.Update, allow bool)

Patch registers a `PATCH /resource/:id` handler for the resource.

func (*Resource) PatchMany

func (res *Resource) PatchMany(storage store.ToManyUpdate, matcher string, allow bool)

PatchMany registers a `PATCH /resources/:id/relationships/<relationship>` handler for the resource relationships.

func (*Resource) PatchOne

func (res *Resource) PatchOne(storage store.ToOneUpdate, matcher string, allow bool)

PatchOne registers a `PATCH /resources/:id/relationships/<relationship>` handler for the resource relationship.

func (*Resource) Post

func (res *Resource) Post(storage store.Save, allow bool)

Post registers a `POST /resource` handler for the resource.

func (*Resource) PostMany

func (res *Resource) PostMany(storage store.ToManyUpdate, matcher string, allow bool)

PostMany registers a `POST /resources/:id/relationships/<relationship>` handler for the resource relationships.

func (*Resource) RouteTree

func (res *Resource) RouteTree() string

RouteTree prints a recursive route tree based on what the resource, and all subresources have registered

func (*Resource) ToMany

func (res *Resource) ToMany(relationship string, storage store.ToMany)

ToMany is syntactic sugar for registering all JSON API routes for a to-many relationship:

Registers handlers for:

GET    /resource/:id/relationship
GET    /resource/:id/relationships/relationship
PATCH  /resource/:id/relationships/relationship
POST   /resource/:id/relationships/relationship
DELETE /resource/:id/relationships/relationship

CRUD actions on a specific relationship "resourceType" object should be performed via it's own top level /<resourceType> jsh-api handler as per JSONAPI specification.

func (*Resource) ToOne

func (res *Resource) ToOne(relationship string, storage store.ToOne)

ToOne is syntactic sugar for registering all JSON API routes for a to-one relationship:

Registers handlers for:

GET    /resource/:id/relationship
GET    /resource/:id/relationships/relationship
PATCH  /resource/:id/relationships/relationship

CRUD actions on a specific relationship "resourceType" object should be performed via it's own top level /<resourceType> jsh-api handler as per JSONAPI specification.

type Route

type Route struct {
	Method string
	Path   string
	Allow  bool
}

Route represents a resource route.

func (Route) String

func (r Route) String() string

String implements the Stringer interface for Route.

type Sender

type Sender func(context.Context, http.ResponseWriter, *http.Request, jsh.Sendable)

Sender is a function type definition that allows consumers to customize how they send and log API responses.

func DefaultSender

func DefaultSender(logger std.Logger) Sender

DefaultSender is the default sender that will log 5XX errors that it encounters in the process of sending a response.

Directories

Path Synopsis
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
t or T : Toggle theme light dark auto
y or Y : Canonical URL