olive

package module
v2.0.0-...-9542b16 Latest Latest
Warning

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

Go to latest
Published: Dec 7, 2022 License: Apache-2.0, MIT Imports: 16 Imported by: 0

Documentation

Overview

olive is a a tiny framework built on top of martini for rapid development of robust REST APIs.

olive handles content type negotation, serialization, deserialization, unique request-id logging and panic recovery leaving you free to write just your application's business logic.

Simple example:

package main
import (
	"net/http"
	"github.com/inconshreveable/olive"
)

func main() {
	o := olive.Martini()
	o.Debug = true
	o.Post("/fact", o.Endpoint(factorial).Param(Input{}))
	http.ListenAndServe(":8080", o)
}

type Input struct {
	Num     int `json:"num" xml:"Num"`
	Timeout int `json:"timeout" xml:"Timeout"`
}

type Output struct {
	Factorial int `json:"answer" xml:"Answer"`
}

func factorial(r olive.Response, in *Input) {
	r.Info("computing factorial", "num", in.Num, "timeout", in.Timeout)
	ans, err := computeFactorial(in.Num, time.Duration(in.Timeout) * time.Second)
	if err != nil {
		r.Abort(err)
	}
	r.Encode(Output{Factorial: ans})
}

The above API will appropriately deserialize a POST body of XML, JSON, or x-www-form-urlencoded depending on the Content-Type header. Based on the client's Accept header, the result will be serialized in either XML or JSON. Appropriate failures are returned for invalid client requests. The logger assigns a unique id to each request for easy tracing purposes:

INFO[11-21|15:33:58] start                                    pg=/fact id=e416b6cc83f386bc
INFO[11-21|15:33:58] computing factorial                      pg=/fact id=e416b6cc83f386bc num=4 timeout=5
INFO[11-21|15:33:58] end                                      pg=/fact id=e416b6cc83f386bc status=200 dur=371.98us

A more advanced example explaining features in detail:

package main

import (
	"net/http"

	"github.com/go-martini/martini"
	"github.com/inconshreveable/olive"
)

func main() {
	o := olive.Martini()
	o.Post("/accounts", o.Endpoint(createAccount).Param(CreateAccountParam{}))
	o.Get("/accounts", o.Endpoint(getAccounts).Param(GetAccountsParam{}))
	o.Get("/accounts/:id", o.Endpoint(getAccount)).Name("accountInstance")

	// serve the API
	http.ListenAndServe(":8080", o)
}

// This is the expected request payload for the createAccount endpoint
// It will automatically be deserialized appropriately depending on
// the Content-Type header sent by the client
type CreateAccountParam struct {
	Name  string `json:"name" xml:"Name"`
	Email string `json:"email" xml:"Email"`
}

// If a struct is specified with Endpoint's Param() function, the request body
// is deserialized and a pointer to the result is dependency injected
func createAccount(r olive.Response, param *CreateAccountParam) {
	// this is all business logic
	ac, err := account.Create(param.Name, param.Email)
	if err != nil {
		// Abort fails the request immediately, there is no need to return.
		// If the standard 'error' interface is passed in, we return a
		// 500 internal server error. see below for fine-grained control
		r.Abort(err)
	}

	// custom status codes need a call to WriteHeader first
	r.WriteHeader(201)

	// serialize output
	r.Encode(ac)
}

type GetAccountsParam struct {
	Email string `param:"email"`
}

// Unlike createAccount, GetAccountsParam is deserialized from the query URI instead
// of from the request body because this is a GET request
func getAccounts(r olive.Response, param *GetAccountsParam) {
	acs, err := account.GetAccountsForEmail(param.Email)
	if err != nil {
		r.Abort(err)
	}

	// the Response interface embeds a log15.Logger that you can use for easy logging
	// every request has a unique ID included in the log line
	r.Debug("fetched accounts", "email", param.Email, "count", len(acs))

	r.Encode(acs)
}

func getAccount(r olive.Response, p martini.Params) {
	// access to URL parameters is the same as Martini
	accountId := p["id"]
	s, err := account.GetById(accountId)
	switch {
	case err == account.NotFoundError:
		// if you pass an olive.Error to Abort(), you can exert more sophisticated
		// control over the returned response
		r.Abort(&olive.Error{
			StatusCode: 404, // http status code
			ErrorCode:  102, // unique error code for this failure ("account not found")
			Message:    "account not found",
			Details:    olive.M{"id": accountId},
		})
	case err != nil:
		r.Abort(err)
	}
	r.Encode(s)
}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ContentEncoder

type ContentEncoder struct {
	ContentType string
	Encoder
}

A ContentEncoder is an Encoder that can encode a resource to the representation described by the ContentType mimetype. Requests with an Accept header that match the ContentType will use that ContentEncoder.

type Decoder

type Decoder interface {
	Decode(rd io.Reader, v interface{}) error
}

type Encoder

type Encoder interface {
	Encode(wr io.Writer, v interface{}) error
}

type Endpoint

type Endpoint interface {
	// stucture of the request input, deserialized either from the request body or query string
	// if set, a pointer to a value of this type will be dependency-injected into the handler
	Param(interface{}) Endpoint

	// overload the allowed decoders
	Decoders(map[string]Decoder) Endpoint

	// customize the allowed encoders for this endpoint
	Encoders([]ContentEncoder) Endpoint

	// debug determines if error stack traces are printed to the client
	Debug(bool) Endpoint

	// returns the handlers that make up the endpoint
	Handlers() []martini.Handler
}

An Endpoint describes an Endpoint in an olive REST API. Callers may customize an endpoint's behavior by chaining calls that manipulate its state. After the Endpoint is built, the caller can use the Handlers() function to get the set of martini.Handlers that implement the API endpoint.

o := olive.Martini()
e := o.Endpoint(listTables).Param(TableFilter{}).Debug(true)
o.Get("/tables", e.Handlers()...)

type Error

type Error struct {
	ErrorCode  int    `json:"error_code,omitempty" xml:",omitempty"` // unique error code
	StatusCode int    `json:"status_code"`                           // http status code
	Message    string `json:"msg"`                                   // user-facing error message
	Details    M      `json:"details" xml:"-"`                       // extra error context for client, XXX should work in XML
}

A structure with details about an error that occurred while handling a request. Passing this structure to an ErrEncoder's Abort() method gives the caller complete control over the error response shape and status code.

func (*Error) Error

func (e *Error) Error() string

type M

type M map[string]interface{}

a Map of extra error details

type Olive

type Olive struct {
	Encoders []ContentEncoder   // default set of ContentEncoders used by a new Endpoint
	Decoders map[string]Decoder // default map of Decoders used by a new Endpoint
	Debug    bool               // default debug flag of a new Endpoint
	// contains filtered or unexported fields
}

Olive creates API Endpoints. Customizing the properties of the Olive changes the defaults of the created Endpoints.

func New

func New(rt martini.Router) *Olive

Returns a new Olive API creating endpoints that can be mapped onto the given martini.Router.

rt := martini.NewRouter()
o := olive.New(rt)
e := o.Endpoint(showTables)
rt.Get(e.Handlers()...)

func (*Olive) Any

func (o *Olive) Any(pattern string, e Endpoint) martini.Route

func (*Olive) Delete

func (o *Olive) Delete(pattern string, e Endpoint) martini.Route

func (*Olive) Endpoint

func (o *Olive) Endpoint(hs ...martini.Handler) Endpoint

func (*Olive) Get

func (o *Olive) Get(pattern string, e Endpoint) martini.Route

func (*Olive) Head

func (o *Olive) Head(pattern string, e Endpoint) martini.Route

func (*Olive) Options

func (o *Olive) Options(pattern string, e Endpoint) martini.Route

func (*Olive) Patch

func (o *Olive) Patch(pattern string, e Endpoint) martini.Route

func (*Olive) Post

func (o *Olive) Post(pattern string, e Endpoint) martini.Route

func (*Olive) Put

func (o *Olive) Put(pattern string, e Endpoint) martini.Route

type OliveMartini

type OliveMartini struct {
	*martini.Martini
	*Olive
	Router martini.Router
}

A convenient pairing of an Olive and Martini which can be used to define and customize an Olive API.

func Martini

func Martini() *OliveMartini

Returns an *OliveMartini that has both an Olive router and *martini.Martini appropriately wired together and ready for use.

type Response

type Response interface {
	martini.ResponseWriter
	log.Logger

	// Encode uses the negotiated codec to serialize and write the value to the response.
	Encode(v interface{}) error

	// Abort terminates a handler immediately with an error and no further processing is done.
	//
	// If the error is of type *olive.Error, the properties of the *olive.Error will be used to
	// determine the status code and shape of the error response. Otherwise, the response will
	// be a 500 internal server error which includes the error argument as one of its details.
	Abort(error)
}

Response is a composition of the most common interfaces needed when handling a request.

Jump to

Keyboard shortcuts

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