Yokai JSON API Module

Yokai module for JSON API, based on google/jsonapi.
Overview
This module provides to your Yokai application a Processor, that you can inject in your HTTP handlers to process JSON API requests and responses.
It also provides automatic error handling, compliant with the JSON API specifications.
Installation
Install the module:
go get github.com/ankorstore/yokai-contrib/fxjsonapi
Then activate it in your application bootstrapper:
// internal/bootstrap.go
package internal
import (
"github.com/ankorstore/yokai-contrib/fxjsonapi"
"github.com/ankorstore/yokai/fxcore"
)
var Bootstrapper = fxcore.NewBootstrapper().WithOptions(
// load modules
fxjsonapi.FxJSONAPIModule,
// ...
)
Configuration
Configuration reference:
# ./configs/config.yaml
modules:
jsonapi:
log:
enabled: true # to automatically log JSON API processing, disabled by default
trace:
enabled: true # to automatically trace JSON API processing, disabled by default
Processing
JSON API request & response processing are driven by the jsonapi tag that you can set on your structs for un/marshalling operations.
You can find more information about this in the underlying google/jsonapi library documentation.
Request processing
You can use the provided Processor to automatically process a JSON API request:
package handler
import (
"net/http"
"github.com/ankorstore/yokai-contrib/fxjsonapi"
"github.com/google/jsonapi"
"github.com/labstack/echo/v4"
)
type Foo struct {
ID int `jsonapi:"primary,foo"`
Name string `jsonapi:"attr,name"`
Bar *Bar `jsonapi:"relation,bar"`
}
func (f Foo) JSONAPIMeta() *jsonapi.Meta {
return &jsonapi.Meta{
"some": "foo meta",
}
}
type Bar struct {
ID int `jsonapi:"primary,bar"`
Name string `jsonapi:"attr,name"`
}
func (b Bar) JSONAPIMeta() *jsonapi.Meta {
return &jsonapi.Meta{
"some": "bar meta",
}
}
type JSONAPIHandler struct {
processor fxjsonapi.Processor
}
func NewJSONAPIHandler(processor fxjsonapi.Processor) *JSONAPIHandler {
return &JSONAPIHandler{
processor: processor,
}
}
func (h *JSONAPIHandler) Handle() echo.HandlerFunc {
return func(c echo.Context) error {
foo := Foo{}
// unmarshall JSON API request payload in foo
err := h.processor.ProcessRequest(
// echo context
c,
// pointer to the struct to unmarshall
&foo,
// optionally override module config for logging
fxjsonapi.WithLog(true),
// optionally override module config for tracing
fxjsonapi.WithTrace(true),
)
if err != nil {
return err
}
return c.JSON(http.StatusOK, foo)
}
}
Notes about ProcessRequest():
- if the request payload does not respect the JSON API specifications, a
400 error will be automatically returned
- if the request
Content-Type header is not application/vnd.api+json, a 415 error will be automatically returned
Response processing
You can use the provided Processor to automatically process a JSON API response:
package handler
import (
"net/http"
"github.com/ankorstore/yokai-contrib/fxjsonapi"
"github.com/ankorstore/yokai-contrib/fxjsonapi/testdata/model"
"github.com/labstack/echo/v4"
)
type Foo struct {
ID int `jsonapi:"primary,foo"`
Name string `jsonapi:"attr,name"`
Bar *Bar `jsonapi:"relation,bar"`
}
func (f Foo) JSONAPIMeta() *jsonapi.Meta {
return &jsonapi.Meta{
"some": "foo meta",
}
}
type Bar struct {
ID int `jsonapi:"primary,bar"`
Name string `jsonapi:"attr,name"`
}
func (b Bar) JSONAPIMeta() *jsonapi.Meta {
return &jsonapi.Meta{
"some": "bar meta",
}
}
type JSONAPIHandler struct {
processor fxjsonapi.Processor
}
func NewJSONAPIHandler(processor fxjsonapi.Processor) *JSONAPIHandler {
return &JSONAPIHandler{
processor: processor,
}
}
func (h *JSONAPIHandler) Handle() echo.HandlerFunc {
return func(c echo.Context) error {
foo := Foo{
ID: 123,
Name: "foo",
Bar: &Bar{
ID: 456,
Name: "bar",
},
}
return h.processor.ProcessResponse(
// echo context
c,
// HTTP status code
http.StatusOK,
// pointer to the struct to marshall
&foo,
// optionally pass metadata to the JSON API response
fxjsonapi.WithMetadata(map[string]interface{}{
"some": "response meta",
}),
// optionally remove the included from the JSON API response (enabled by default)
fxjsonapi.WithIncluded(false),
// optionally override module config for logging
fxjsonapi.WithLog(true),
// optionally override module config for tracing
fxjsonapi.WithTrace(true),
)
}
}
Notes about ProcessResponse():
- you can pass a
pointer or a slice of pointers to marshall as JSON API
application/vnd.api+json will be automatically added to the response Content-Type header
Error handling
This module automatically enables the ErrorHandler, to convert errors bubbling up in JSON API format.
It handles:
- JSON API errors: automatically sets the
status code of the error
- validation errors: automatically sets a
400 status code
- HTTP errors: automatically sets the
status code of the error
- or any generic error: automatically sets a
500 status code
You can optionally obfuscate the errors details (ex: for production) in the HTTP server configuration:
# ./configs/config.yaml
modules:
http:
server:
errors:
obfuscate: true # disabled by default
Testing
This module provides a ProcessorMock for mocking Processor, see usage example.