api2go

package module
v0.0.0-...-e3f13cf Latest Latest
Warning

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

Go to latest
Published: Mar 5, 2015 License: MIT Imports: 15 Imported by: 0

README

api2go

GoDoc Build Status

A JSON API Implementation for Go, to be used e.g. as server for Ember Data.

import "github.com/univedo/api2go"

api2go works, but we're still working on some rough edges. Things might change. Open an issue and join in!

Usage

Take the simple structs:

type Post struct {
  ID          int
  Title       string
  Comments    []Comment
  CommentsIDs []int
}

type Comment struct {
  ID   int
  Text string
}
Building a REST API

First, write an implementation of api2go.DataSource. You have to implement 5 methods:

type fixtureSource struct {}

func (s *fixtureSource) FindAll(r api2go.Request) (interface{}, error) {
  // Return a slice of all posts as []Post
}

func (s *fixtureSource) FindOne(ID string, r api2go.Request) (interface{}, error) {
  // Return a single post by ID as Post
}

func (s *fixtureSource) FindMultiple(IDs string, r api2go.Request) (interface{}, error) {
  // Return multiple posts by ID as []Post
  // For example for Requests like GET /posts/1,2,3
}

func (s *fixtureSource) Create(obj interface{}) (string, error) {
  // Save the new Post in `obj` and return its ID.
}

func (s *fixtureSource) Delete(id string) error {
  // Delete a post
}

func (s *fixtureSource) Update(obj interface{}) error {
  // Apply the new values in the Post in `obj`
}

As an example, check out the implementation of fixtureSource in api_test.go.

You can then create an API:

api := api2go.NewAPI("v1")
api.AddResource(Post{}, &PostsSource{})
http.ListenAndServe(":8080", api.Handler())

This generates the standard endpoints:

OPTIONS /v1/posts
OPTIONS /v1/posts/<id>
GET     /v1/posts
POST    /v1/posts
GET     /v1/posts/<id>
PUT     /v1/posts/<id>
DELETE  /v1/posts/<id>
GET     /v1/posts/<id>,<id>,...
Query Params

To support all the features mentioned in the Fetching Resources section of Jsonapi: http://jsonapi.org/format/#fetching

If you want to support any parameters mentioned there, you can access them in your Resource via the api2go.Request Parameter. This currently supports QueryParams which holds all query parameters as map[string][]string unfiltered. So you can use it for:

  • Filtering
  • Inclusion of Linked Resources
  • Sparse Fieldsets
  • Sorting
  • Aything else you want to do that is not in the official Jsonapi Spec
type fixtureSource struct {}

func (s *fixtureSource) FindAll(req api2go.Request) (interface{}, error) {
  for key, values range r.queryParams {
    ...
  }
  ...
}

If there are multiple values, you have to separate them with a komma. api2go automatically slices the values for you.

Example Request
GET /people?fields=id,name,age

req.QueryParams["fields"] contains values: ["id", "name", "age"]
Use Custom Controllers

By using the api2go.DataSource and registering it with AddResource, api2go will do everything for you automatically and you cannot change it. This means that you cannot access the request, perform some user authorization and so on...

In order to register a Controller for a DataSource, implement the api2go.Controller interface:

type Controller interface {
  // FindAll gets called after resource was called
  FindAll(r *http.Request, objs *interface{}) error

  // FindOne gets called after resource was called
  FindOne(r *http.Request, obj *interface{}) error

  // Create gets called before resource was called
  Create(r *http.Request, obj *interface{}) error

  // Delete gets called before resource was called
  Delete(r *http.Request, id string) error

  // Update gets called before resource was called
  Update(r *http.Request, obj *interface{}) error
}

Now, you can access the request and for example perform some user authorization by reading the Authorization header or some cookies. In addition, you also have the object out of your database, in case you need that too.

To deny access you just return a new httpError with api2go.NewHTTPError

...
func (c *yourController) FindAll(r *http.Request, objs *interface{}) error {
  // do some authorization stuff
  return api2go.NewHTTPError(someError, "Access denied", 403)
}
...

Register your Controller with the DataSource together

api := api2go.NewAPI("v1")
api.AddResourceWithController(Post{}, &PostsSource{}, &YourController{})
http.ListenAndServe(":8080", api.Handler())
Manual marshaling / unmarshaling
comment1 = Comment{ID: 1, Text: "First!"}
comment2 = Comment{ID: 2, Text: "Second!"}
post = Post{ID: 1, Title: "Foobar", Comments: []Comment{comment1, comment2}}

json, err := api2go.MarshalJSON(post)

will yield

{
  "data": [
    {
      "id": "1",
      "type": "posts",
      "links": {
        "comments": {
          "ids": ["1", "2"],
          "type": "comments"
        },
      },
      "title": "Foobar"
    }
  ],
  "linked": [
    {"id": "1", "type: "comments", "text": "First!"},
    {"id": "2", "type: "comments", "text": "Second!"}
  ]
}

Recover the structure from above using

var posts []Post
err := api2go.UnmarshalFromJSON(json, &posts)
// posts[0] == Post{ID: 1, Title: "Foobar", CommentsIDs: []int{1, 2}}

Note that when unmarshaling, api2go will always fill the CommentsIDs field, never the Comments field.

Conventions

Structs MUST have:

  • A field called ID that is either a string or int.

Structs MAY have:

  • Fields with struct-slices, e.g. Comments []Comment. They will be serialized as links (using the field name) and the linked structs embedded.
  • Fields with int / string slices, e.g. CommentsIDs. They will be serialized as links (using the field name minus an optional IDs suffix), but not embedded.
  • Fields of struct type, e.g. Author Person. They will be serialized as a single link (using the field name) and the linked struct embedded.
  • Fields of int / string type, ending in ID, e.g. AuthorID. They will be serialized as a single link (using the field name minus the ID suffix), but not embedded.

Tests

go test
ginkgo                # Alternative
ginkgo watch -notify  # Watch for changes

Documentation

Overview

Package api2go enables building REST servers for the JSONAPI.org standard.

See https://github.com/univedo/api2go for usage instructions.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Marshal

func Marshal(data interface{}) (interface{}, error)

Marshal takes a struct (or slice of structs) and marshals them to a json encodable interface{} value

func MarshalToJSON

func MarshalToJSON(val interface{}) ([]byte, error)

MarshalToJSON takes a struct and marshals it to JSONAPI compliant JSON

func Unmarshal

func Unmarshal(ctx unmarshalContext, values interface{}) error

Unmarshal reads a JSONAPI map to a model struct

func UnmarshalFromJSON

func UnmarshalFromJSON(data []byte, values interface{}) error

UnmarshalFromJSON reads a JSONAPI compatible JSON document to a model struct

Types

type API

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

API is a REST JSONAPI.

func NewAPI

func NewAPI(prefix string) *API

NewAPI returns an initialized API instance `prefix` is added in front of all endpoints.

func (*API) AddResource

func (api *API) AddResource(prototype interface{}, source DataSource)

AddResource registers a data source for the given resource `resource` should by an empty struct instance such as `Post{}`. The same type will be used for constructing new elements.

func (*API) AddResourceWithController

func (api *API) AddResourceWithController(prototype interface{}, source DataSource, controller Controller)

AddResourceWithController does the same as `AddResource` but also couples a custom `Controller` Use this controller to implement access control and other things that depend on the request

func (*API) Handler

func (api *API) Handler() http.Handler

Handler returns the http.Handler instance for the API.

type Controller

type Controller interface {
	// FindAll gets called after resource was called
	FindAll(r *http.Request, objs *interface{}) error

	// FindOne gets called after resource was called
	FindOne(r *http.Request, obj *interface{}) error

	// Create gets called before resource was called
	Create(r *http.Request, obj *interface{}) error

	// Delete gets called before resource was called
	Delete(r *http.Request, id string) error

	// Update gets called before resource was called
	Update(r *http.Request, obj *interface{}) error
}

Controller provides more customization of each route. You can define a controller for every DataSource if needed

type DataSource

type DataSource interface {
	// FindAll returns all objects
	FindAll(req Request) (interface{}, error)

	// FindOne returns an object by its ID
	FindOne(ID string, req Request) (interface{}, error)

	// FindMultiple returns all objects for the specified IDs
	FindMultiple(IDs []string, req Request) (interface{}, error)

	// Create a new object and return its ID
	Create(interface{}) (string, error)

	// Delete an object
	Delete(id string) error

	// Update an object
	Update(obj interface{}) error
}

DataSource provides methods needed for CRUD.

type Error

type Error struct {
	ID     string `json:"id,omitempty"`
	Href   string `json:"href,omitempty"`
	Status string `json:"status,omitempty"`
	Code   string `json:"code,omitempty"`
	Title  string `json:"title,omitempty"`
	Detail string `json:"detail,omitempty"`
	Path   string `json:"path,omitempty"`
}

Error can be used for all kind of application errors e.g. you would use it to define form errors or any other semantical application problems for more information see http://jsonapi.org/format/#errors

type HTTPError

type HTTPError struct {
	Errors []Error `json:"errors,omitempty"`
	// contains filtered or unexported fields
}

HTTPError is used for errors

func NewHTTPError

func NewHTTPError(err error, msg string, status int) HTTPError

NewHTTPError creates a new error with message and status code. `err` will be logged (but never sent to a client), `msg` will be sent and `status` is the http status code. `err` can be nil.

func (HTTPError) Error

func (e HTTPError) Error() string

Error returns a nice string represenation including the status

type Request

type Request struct {
	QueryParams map[string][]string
	Header      http.Header
}

Request holds additional information for FindOne and Find Requests

Jump to

Keyboard shortcuts

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