Skip to content
This repository has been archived by the owner on May 4, 2018. It is now read-only.

solher/zest

Repository files navigation

logo

Build Status Coverage Status Code Climate

Zest is a lightweight framework based on the Cli package allowing clean and easy command line interfaces, the Negroni middleware handler, and the Syringe injector.

Zest encourages the use of small, well chosen individual dependencies instead of high productivity, full-stack frameworks.

Overview

Having a good cli interface, a simple init/exit process and your app injected automatically should be the basis of your development.

Zest makes all that simple by aggregating well known and efficient packages. The Classic version also provides some default tools useful for most applications :

  • Gin inspired logging, CORS (allowing all origins by default) and Recovery middlewares
  • Pre-injected custom JSON renderer and Bone router

Installation

To install Zest:

go get github.com/solher/zest

Launch/exit sequences

The launch sequence is divided into three steps:

  • The register sequence is run, allowing the user to register dependencies into the injector.
  • The dependency injection is run.
  • The init sequence is run, allowing the user to properly initialize the freshly built app.

Launch and exit sequences are run following the order of the array, at each start/stop of the app, thanks to Cli and the graceful shutdown module.

type ZestFunc func(z *Zest) error

type Zest struct {
	cli     *cli.App
	Context *cli.Context

	Server   *negroni.Negroni
	Injector *syringe.Syringe

	RegisterSequence []SeqFunc
	InitSequence     []SeqFunc
	ExitSequence     []SeqFunc
}

Each function is called with the Zest app in argument, allowing the user to interact with the cli context, the Negroni server or any dependency registered in the injector.

In the New version of Zest, the launch sequence only triggers the dependency injection of the app. The RegisterSequence and InitSequence arrays are empty.

In the Classic version, default register and init steps are provided:

  • classicRegister which registers the default dependencies (Render, Bone) in the injector.
  • classicInit which initialize the Bone router and the default middlewares in Negroni.

In both versions, the exit sequence is empty.

API errors

One of the few conventions established by Zest is the API error messages style. It allows a consistent format between the recovery middleware and the render methods, and a better expressiveness.

type APIError struct {
	Status      int    `json:"status"`
	Description string `json:"description"`
	Raw         string `json:"raw"`
	ErrorCode   string `json:"errorCode"`
}
  • The Status is a copy of the status returned in the HTTP headers.
  • The Description is the message describing what kind of error occured.
  • The Raw field is the raw error message which triggered the API error. Its purpose is to allow a more efficient debugging and should not be used as an "error id" by the API client.
  • The ErrorCode is the "machine oriented" description of the API error.

Render

The custom Render module is based on the Render package, made more expressive thanks to the coupling with the Zest API error format.

func (r *Render) JSONError(w http.ResponseWriter, status int, apiError *APIError, err error){}

func (r *Render) JSON(w http.ResponseWriter, status int, object interface{}){}

When the JSONError method is called, the status and the error are automatically copied into the final APIError struct so you don't have to worry about that.

In situation, it looks like that :

var UnknownAPIError = &zest.APIError{Description: "An error occured. Please retry later.", ErrorCode: "UNKNOWN_ERROR"}

func (c *Controller) Handler(w http.ResponseWriter, r *http.Request) {
	result, err := c.m.Action()
	if err != nil {
		c.r.JSONError(w, http.StatusInternalServerError, UnknownAPIError, err)
		return
	}

	c.r.JSON(w, http.StatusOK, result)
}

Example

main.go

package main

import (
	"github.com/go-zoo/bone"
	"github.com/solher/zest"
)

func main() {
	app := zest.Classic()

	cli := app.Cli()
	cli.Name = "Test"
	cli.Usage = "This is a test application."
	app.SetCli(cli)

	app.InitSequence = append(app.InitSequence, SetRoutes)

	app.Run()
}

func SetRoutes(z *zest.Zest) error {
	d := &struct {
		Router *bone.Mux
		Ctrl   *Controller
	}{}

	if err := z.Injector.Get(d); err != nil {
		return err
	}

	d.Router.GetFunc("/", d.Ctrl.Handler)

	return nil
}

controller.go

package main

import (
	"net/http"

	"github.com/solher/zest"
)

func init() {
	zest.Injector.Register(NewController)
}

type Controller struct {
	m *Model
	r *zest.Render
}

func NewController(m *Model, r *zest.Render) *Controller {
	return &Controller{m: m, r: r}
}

func (c *Controller) Handler(w http.ResponseWriter, r *http.Request) {
	result, err := c.m.Action()
	if err != nil {
		apiErr := &zest.APIError{Description: "An error occured. Please retry later.", ErrorCode: "UNKNOWN_ERROR"}
		c.r.JSONError(w, http.StatusInternalServerError, apiErr, err)
		return
	}

	c.r.JSON(w, http.StatusOK, result)
}

model.go

package main

import "github.com/solher/zest"

func init() {
	zest.Injector.Register(NewModel)
}

type Model struct {
	s *Store
}

func NewModel(s *Store) *Model {
	return &Model{s: s}
}

func (m *Model) Action() (string, error) {
	result, err := m.s.DBAction()

	return result, err
}

store.go

package main

import "github.com/solher/zest"

func init() {
	zest.Injector.Register(NewStore)
}

type Store struct{}

func NewStore() *Store {
	return &Store{}
}

func (s *Store) DBAction() (string, error) {
	return "foobar", nil
}

About

Thanks to the Code Gangsta for his amazing work on Negroni and Cli.

License

MIT

About

Lightweight and unopinionated web framework delicately placed on the top of Negroni

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages