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[T any](repository Repository[T]) http.HandlerFunc
- func Get[T any](repository Repository[T]) http.HandlerFunc
- func GetAll[T any](repository Repository[T]) http.HandlerFunc
- func Post[T any](repository Repository[T]) http.HandlerFunc
- func Put[T any](repository Repository[T]) http.HandlerFunc
- func RespondWithError(w http.ResponseWriter, code int, message string) error
- func RespondWithJSON(w http.ResponseWriter, code int, payload any) error
- type Controller
- func (c *Controller[T]) Delete(w http.ResponseWriter, r *http.Request)
- func (c *Controller[T]) Get(w http.ResponseWriter, r *http.Request)
- func (c *Controller[T]) GetAll(w http.ResponseWriter, r *http.Request)
- func (c *Controller[T]) Post(w http.ResponseWriter, r *http.Request)
- func (c *Controller[T]) Put(w http.ResponseWriter, r *http.Request)
- type Persistable
- type QueryOptions
- type Repository
- 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") // ErrInvalidQueryOpts will make the controller return a 400 error ErrInvalidQueryOpts = errors.New("invalid query options") )
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[T any](repository Repository[T]) http.HandlerFunc
Delete handles the DELETE verb. Should be mapped to: DELETE /thing/:id
func Get ¶
func Get[T any](repository Repository[T]) http.HandlerFunc
Get handles the GET verb for individual items. Should be mapped to: GET /thing/:id
func GetAll ¶
func GetAll[T any](repository Repository[T]) 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[T any](repository Repository[T]) http.HandlerFunc
Post handles the POST verb. Should be mapped to: POST /thing
func Put ¶
func Put[T any](repository Repository[T]) 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 any) error
RespondWithJSON returns a message formatted as JSON, and sets the http status to code
Types ¶
type Controller ¶
type Controller[T any] struct { Repository Repository[T] }
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[T]) Delete ¶
func (c *Controller[T]) Delete(w http.ResponseWriter, r *http.Request)
Delete handles the DELETE verb
func (*Controller[T]) Get ¶
func (c *Controller[T]) Get(w http.ResponseWriter, r *http.Request)
Get handles the GET verb for individual items.
func (*Controller[T]) GetAll ¶
func (c *Controller[T]) GetAll(w http.ResponseWriter, r *http.Request)
GetAll handles the GET verb for the full collection
func (*Controller[T]) Post ¶
func (c *Controller[T]) Post(w http.ResponseWriter, r *http.Request)
Post handles the POST verb
func (*Controller[T]) Put ¶
func (c *Controller[T]) Put(w http.ResponseWriter, r *http.Request)
Put handles the PUT verb
type Persistable ¶
type Persistable[T any] interface { // Save adds the entity to the repository and returns the newly created id Save(ctx context.Context, entity *T) (string, error) // Update the entity identified by id. Optionally select the fields to be updated Update(ctx context.Context, id string, entity T, fields ...string) error // Delete the entities identified by id Delete(ctx context.Context, ids ...string) error }
Persistable must be implemented by repositories in addition to the Repository interface, to allow the POST, PUT and DELETE http methods. If this interface is not implemented by the repository, calls to these http 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. E.g.: {"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]any
}
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[T any] interface { // Count returns the number of entities that matches the criteria specified by the options Count(context.Context, ...QueryOptions) (int64, error) // Read returns the entity identified by id Read(ctx context.Context, id string) (*T, error) // ReadAll returns a slice of entities that matches the criteria specified by the options ReadAll(context.Context, ...QueryOptions) ([]T, error) }
Repository is the interface that must be created to access your data. This will be used for the GET http method.
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
func (ValidationError) Is ¶
func (m ValidationError) Is(target error) bool