Documentation ¶
Overview ¶
Package rest provides a simple REST controller compatible with the JSON Server API "dialect". This package enables the creation of backends for the great Admin-on-rest package using pure Go, but can be used in other scenarios where you need a simple REST server for your data.
To use it, you will need to provide an implementation of the Repository interface and a function to create such repository.
The controller was created to be used with Gorilla Pat, as it requires URL params to be parsed and set as query params. You can easily adapt it to work with other routers and frameworks using a custom middleware.
The functionality is provided by a set of handlers named after the REST verbs they handle: Get(), GetAll(), Put(), Post() and Delete(). Each of these functions receive a function used to construct your repository, and an optional implementation of Logger (compatible with Logrus). If no Logger is specified, the functions falls back to the default Go log package
Example using Gorilla Pat (https://github.com/gorilla/pat):
func NewThingsRepository(ctx context) rest.Repository { return &ThingsRepository{ctx: ctx} } func main() { router := pat.New() router.Get("/thing/{id}", rest.Get(NewThingsRepository)) router.Get("/thing", rest.GetAll(NewThingsRepository)) router.Post("/thing", rest.Post(NewThingsRepository)) router.Put("/thing/{id}", rest.Put(NewThingsRepository)) router.Delete("/thing/{id}", rest.Delete(NewThingsRepository)) http.Handle("/", router) log.Print("Listening on 127.0.0.1:8000...") log.Fatal(http.ListenAndServe(":8000", nil)) }
Example using chi router (https://github.com/go-chi/chi):
func main() { router := chi.NewRouter() router.Route("/thing", func(r chi.Router) { r.Get("/", rest.GetAll(NewThingsRepository)) r.Post("/", rest.Post(NewThingsRepository)) r.Route("/{id:[0-9]+}", func(r chi.Router) { r.With(urlParams).Get("/", rest.Get(NewThingsRepository)) r.With(urlParams).Put("/", rest.Put(NewThingsRepository)) r.With(urlParams).Delete("/", rest.Delete(NewThingsRepository)) }) }) http.Handle("/", router) log.Print("Listening on 127.0.0.1:8000...") log.Fatal(http.ListenAndServe(":8000", nil)) } // Middleware to convert Chi URL params (from Context) to query params, as expected by our REST package func urlParams(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := chi.RouteContext(r.Context()) parts := make([]string, 0) for i, key := range ctx.URLParams.Keys { value := ctx.URLParams.Values[i] if key == "*" { continue } parts = append(parts, url.QueryEscape(":"+key)+"="+url.QueryEscape(value)) } q := strings.Join(parts, "&") if r.URL.RawQuery == "" { r.URL.RawQuery = q } else { r.URL.RawQuery += "&" + q } next.ServeHTTP(w, r) }) }
For more info see:
JSON Server: https://github.com/typicode/json-server admin-on-rest: https://marmelab.com/admin-on-rest/
Index ¶
- Variables
- func Delete(newRepository RepositoryConstructor, logger ...Logger) http.HandlerFunc
- func Get(newRepository RepositoryConstructor, logger ...Logger) http.HandlerFunc
- func GetAll(newRepository RepositoryConstructor, logger ...Logger) http.HandlerFunc
- func Post(newRepository RepositoryConstructor, logger ...Logger) http.HandlerFunc
- func Put(newRepository RepositoryConstructor, logger ...Logger) http.HandlerFunc
- func RespondWithError(w http.ResponseWriter, code int, message string) error
- func RespondWithJSON(w http.ResponseWriter, code int, payload interface{}) error
- type Controller
- func (c *Controller) Delete(w http.ResponseWriter, r *http.Request)
- func (c *Controller) Get(w http.ResponseWriter, r *http.Request)
- func (c *Controller) GetAll(w http.ResponseWriter, r *http.Request)
- func (c *Controller) Post(w http.ResponseWriter, r *http.Request)
- func (c *Controller) Put(w http.ResponseWriter, r *http.Request)
- type Logger
- type Persistable
- type QueryOptions
- type Repository
- type RepositoryConstructor
- type ValidationError
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotFound will make the controller return a 404 error ErrNotFound = errors.New("data not found") // ErrPermissionDenied will make the controller return a 403 error ErrPermissionDenied = errors.New("permission denied") )
Possible errors returned by a Repository implementation. Any error other than these will make the REST controller return a 500 http status code.
Functions ¶
func Delete ¶
func Delete(newRepository RepositoryConstructor, logger ...Logger) http.HandlerFunc
Delete handles the DELETE verb. Should be mapped to: DELETE /thing/:id
func Get ¶
func Get(newRepository RepositoryConstructor, logger ...Logger) http.HandlerFunc
Get handles the GET verb for individual items. Should be mapped to: GET /thing/:id
func GetAll ¶
func GetAll(newRepository RepositoryConstructor, logger ...Logger) http.HandlerFunc
GetAll handles the GET verb for the full collection. Should be mapped to: GET /thing For all query options available, see https://github.com/typicode/json-server
func Post ¶
func Post(newRepository RepositoryConstructor, logger ...Logger) http.HandlerFunc
Post handles the POST verb. Should be mapped to: POST /thing
func Put ¶
func Put(newRepository RepositoryConstructor, logger ...Logger) http.HandlerFunc
Put handles the PUT verb. Should be mapped to: PUT /thing/:id
func RespondWithError ¶
func RespondWithError(w http.ResponseWriter, code int, message string) error
RespondWithError returns an error message formatted as a JSON object, and sets the http status to code
func RespondWithJSON ¶
func RespondWithJSON(w http.ResponseWriter, code int, payload interface{}) error
RespondWithJSON returns a message formatted as JSON, and sets the http status to code
Types ¶
type Controller ¶
type Controller struct { Repository Repository Logger Logger }
Controller implements a set of RESTful handlers, compatible with the JSON Server API "dialect". Please prefer to use the functions provided in the handler.go file instead of these.
func (*Controller) Delete ¶
func (c *Controller) Delete(w http.ResponseWriter, r *http.Request)
Delete handles the DELETE verb
func (*Controller) Get ¶
func (c *Controller) Get(w http.ResponseWriter, r *http.Request)
Get handles the GET verb for individual items.
func (*Controller) GetAll ¶
func (c *Controller) GetAll(w http.ResponseWriter, r *http.Request)
GetAll handles the GET verb for the full collection
func (*Controller) Post ¶
func (c *Controller) Post(w http.ResponseWriter, r *http.Request)
Post handles the POST verb
func (*Controller) Put ¶
func (c *Controller) Put(w http.ResponseWriter, r *http.Request)
Put handles the PUT verb
type Logger ¶
type Logger interface { Warnf(format string, args ...interface{}) Errorf(format string, args ...interface{}) }
A Logger instance can be passed to the handlers provided by this package. This is compatible with Logrus, but also allows for full customization of the log system used. If you want to use a different logger, just implement a wrapper with the self-explanatory functions defined by this interface.
type Persistable ¶
type Persistable interface { // Adds the entity to the repository and returns the newly created id Save(entity interface{}) (string, error) // Updates the entity identified by id. Optionally select the fields to be updated Update(id string, entity interface{}, cols ...string) error // Delete the entity identified by id Delete(id string) error }
Persistable must be implemented by repositories in adition to the Repository interface, to allow the POST, PUT and DELETE methods. If this interface is not implemented by the repository, calls to these methods will return 405 - Method Not Allowed
type QueryOptions ¶
type QueryOptions struct { // Comma separated list of fields to sort the data Sort string // Possible values: asc (default), desc Order string // Max records to return. Used for pagination Max int // Initial record to return. Used for pagination Offset int // Map of filters to apply to the query. Keys are field names and values are the filter // to be applied to the field. Eg.: {"age": 30, "name": "john"} // How the values of the filters are applied to the fields is implementation dependent // (you can implement substring, exact match, etc..) Filters map[string]interface{} }
QueryOptions are optional query parameters that can be received by Count and ReadAll and are used to implement pagination, sorting and filtering.
type Repository ¶
type Repository interface { // Returns the number of entities that matches the criteria specified by the options Count(options ...QueryOptions) (int64, error) // Returns the entity identified by id Read(id string) (interface{}, error) // Returns a slice of entities that matches the criteria specified by the options ReadAll(options ...QueryOptions) (interface{}, error) // Return the entity name (used for logs and messages) EntityName() string // Returns a newly created instance. Should be as simple as return &Thing{} NewInstance() interface{} }
Repository is the interface that must be created for your data. See SampleRepository (in examples folder) for a simple in-memory map-based example.
type RepositoryConstructor ¶
type RepositoryConstructor func(ctx context.Context) Repository
RepositoryConstructor needs to be implemented by your custom repository implementation, and it returns a fully initialized repository. It is meant to be called on every HTTP request, so you shouldn't keep state in your repository, and it should execute fast. You have access to the current HTTP request's context.
type ValidationError ¶
ValidationError will make the controller return a 400 error, with the listed errors in the body
func (ValidationError) Error ¶
func (m ValidationError) Error() string