Simplifies API design, documentation and testing by binding a go type
to an API using tags
.
type WordFilter struct {
Blacklist *regexp.Regexp
R api.Request `url:"/api/wordfilter/v1"`
In string `api:"in,required" json:"in"`
Out string `api:"out" json:"out"`
Count int `api:"out" json:"count"`
}
func (this *WordFilter) Do(c ctx.C) error {
this.Out = this.Blacklist.ReplaceAllStringFunc(this.In, func(bad string) string {
this.Count++
return "***"
})
return nil
}
The above object can easily be "unit" tested by itself:
func TestWordFilter(t *testing.T) {
re := regexp.MustCompile(`(bad|worse|worst)`)
out := api.Test(t, &WordFilter{
Blacklist: re,
In: "ok, bad or worse",
})
test.EqualsStr(t, "ok, *** or ***", out.Out)
test.EqualsGo(t, 2, out.Count)
}
And can be used with forego/http
to directly
generate an API path:
s := http.NewServer(c)
_ = s.RegisterAPI(c, &WordFilter{
Blacklist: regexp.MustCompile(`(foo|bar)`), // this will be copied by ref for each request
})
Which exposes the above object to /api/wordfilter/v1
and also generate the OpenAPI documentation for it [see /http
for more details].
While normally you would use api
directly from http.Server
as shown above, you can also use it directly. Example for the server side:
// this is best to be done on startup…
h, err := api.NewHandler(c, &WordFilter{})
ser = h.Server() // can be used to perform Marshal/Unmarshal from the server side
// then on each request…
onRequest := func(c ctx.C, req api.ServerRequest, res api.ServerResponse) error {
// un marshal the request into a new *WordFilter
op, err := ser.Recv(c, req)
if err != nil {
return err
}
// call *WordFilter.Do()
err = op.Do(c)
if err != nil {
return err
}
// marshal back the response
return ser.Send(c, op, res)
}
When h.Server().Recv()
, the method api.ServerRequest.Auth()
is called, which can then return an error.
As in the example above, you can define properties on the api
object, which will be copied by reference on each request.
This makes it possible to use some shared state or configuration between requests.
This object is an helper for building api.ServerRequest
, api.ServerReponse
and the equivalent for the client.
It can be used on either ends to help building clients, unless you use http.Server.Register
directly and avoid all the boilerplate.
Beside the common json
tag used normally to marshal JSON data, there are few new tags you need to care about:
This tag could be either in
, in,required
, out
, both,required
or by default both
When unmarshalling a request on a server, only in
or both
will be processed.
Moreover, if either of them have ,required
as part of the tag (in,requred
or both,required
) but the field is zero, a 400
error is returned
Only fields tagged as out
or both
will be marshalled back to the client.
Conversely, the same happen on the client side, just with the roles reversed.
If a field is marked as auth
, it will be unmarshalled using the UID provided by the server side plumbing.
Moreover, if auth,required
a 403 should be returned if no valid authentication token is provided with the request (see api.ServerRequest.Auth()
).
Only one field should have the tag url
, which provides a definition for the entrypoint of the API, and if fully qualified, can be used from clients directly without overrides for requests over the network. (see http/client.go Client.API()
)