HAL implementation in Go.
HAL is a simple format that gives a consistent and easy way to hyperlink between resources in your API.
Halgo helps with generating HAL-compliant JSON from Go structs, and provides a Navigator for walking a HAL-compliant API.
go get github.com/jagregory/halgo
Serialising a resource with HAL links:
import "github.com/jagregory/halgo"
type MyResource struct {
halgo.Links
Name string
}
res := MyResource{
Links: Links{}.
Self("/orders").
Next("/orders?page=2").
Link("ea:find", "/orders{?id}").
Add("ea:admin", Link{Href: "/admins/2", Title: "Fred"}, Link{Href: "/admins/5", Title: "Kate"}),
Name: "James",
}
bytes, _ := json.Marshal(res)
fmt.Println(bytes)
// {
// "_links": {
// "self": { "href": "/orders" },
// "next": { "href": "/orders?page=2" },
// "ea:find": { "href": "/orders{?id}", "templated": true },
// "ea:admin": [{
// "href": "/admins/2",
// "title": "Fred"
// }, {
// "href": "/admins/5",
// "title": "Kate"
// }]
// },
// "Name": "James"
// }
Navigating a HAL-compliant API:
res, err := halgo.Navigator("http://example.com").
Follow("products").
Followf("page", halgo.P{"n": 10}).
Get()
The following operations can be chained together to navigate a HAL-compliant API:
Follow(rel string)
- Follow the relationrel
Followf(rel string, params P)
- Follow the relationrel
and useparams
to evaluate any underlying template.Extract(rel string)
- Fetch the location of an embedded resource namedrel
and navigate to the full representation of that resource (i.e., follow itsself
URI).SetSessionHeader(header string, value string)
- Set a new header to all requests in this chain (e.g.,Authorization
header)AddSessionHeader(header string, value string)
- Add a new header to all requests in this chain.SetRequestHeader(header string, value string)
- Set a new header to the operation immediately preceeding this call in the chain.AddRequestHeader(header string, value string)
- Add a new header to the operation immediately preceeding this call in the chain.
These chains can be terminated by the following requests (each of
which returns an *http.Response
or error
):
Get(headers ...http.Header)
- Perform aGET
request (with optional additional headers)Options(headers ...http.Header)
- Perform anOPTIONS
request (with optional additional headers)Post(bodyType string, body io.Reader, headers ...http.Header)
- Perform aPOST
request withbody
of content typebodyType
(and optional additional headers)PostForm(data url.Values, headers ...http.Header)
- Perform aPOST
request with formdata
(and optional additional headers)Patch(bodyType string, body io.Reader, headers ...http.Header)
- Perform aPATCH
request withbody
of content typebodyType
(and optional additional headers)Put(bodyType string, body io.Reader, headers ...http.Header)
- Perform aPUT
request withbody
of content typebodyType
(and optional additional headers)Delete(headers ...http.Header)
- Perform aDELETE
request (with optional additional headers)
In addition, following any request that returns a Location
header
(typically a Post
), a new navigator instance can be created that is
rooted at the location specified by the Location
header by calling:
Location(resp *http.Response)
- which returns either a new navigator or an error
Deserialising a resource:
import "github.com/jagregory/halgo"
type MyResource struct {
halgo.Links
Name string
}
data := []byte(`{
"_links": {
"self": { "href": "/orders" },
"next": { "href": "/orders?page=2" },
"ea:find": { "href": "/orders{?id}", "templated": true },
"ea:admin": [{
"href": "/admins/2",
"title": "Fred"
}, {
"href": "/admins/5",
"title": "Kate"
}]
},
"Name": "James"
}`)
res := MyResource{}
json.Unmarshal(data, &res)
res.Name // "James"
res.Links.Href("self") // "/orders"
res.Links.HrefParams("self", Params{"id": 123}) // "/orders?id=123"
- Curies
- Embedded resources