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 ¶
- type ContentEncoder
- type Decoder
- type Encoder
- type Endpoint
- type Error
- type M
- type Olive
- func (o *Olive) Any(pattern string, e Endpoint) martini.Route
- func (o *Olive) Delete(pattern string, e Endpoint) martini.Route
- func (o *Olive) Endpoint(hs ...martini.Handler) Endpoint
- func (o *Olive) Get(pattern string, e Endpoint) martini.Route
- func (o *Olive) Head(pattern string, e Endpoint) martini.Route
- func (o *Olive) Options(pattern string, e Endpoint) martini.Route
- func (o *Olive) Patch(pattern string, e Endpoint) martini.Route
- func (o *Olive) Post(pattern string, e Endpoint) martini.Route
- func (o *Olive) Put(pattern string, e Endpoint) martini.Route
- type OliveMartini
- type Response
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ContentEncoder ¶
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 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.
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.
type OliveMartini ¶
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.