89 lines
2.1 KiB
Go
89 lines
2.1 KiB
Go
|
|
package hal
|
||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
|
||
|
|
"github.com/raff/halgo"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Resource is a simple HAL resource envelope that uses halgo.Link for links.
|
||
|
|
type Resource struct {
|
||
|
|
Links map[string]halgo.Link `json:"_links,omitempty"`
|
||
|
|
Embedded map[string]interface{} `json:"_embedded,omitempty"`
|
||
|
|
// State holds arbitrary resource state merged at top-level when marshaled
|
||
|
|
State map[string]interface{} `json:"-"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// New creates a new HAL resource with a self link.
|
||
|
|
func New(selfHref string) *Resource {
|
||
|
|
r := &Resource{
|
||
|
|
Links: map[string]halgo.Link{},
|
||
|
|
Embedded: map[string]interface{}{},
|
||
|
|
State: map[string]interface{}{},
|
||
|
|
}
|
||
|
|
if selfHref != "" {
|
||
|
|
r.AddLink("self", halgo.Link{Href: selfHref})
|
||
|
|
}
|
||
|
|
return r
|
||
|
|
}
|
||
|
|
|
||
|
|
// AddLink adds a link with the given rel.
|
||
|
|
func (r *Resource) AddLink(rel string, link halgo.Link) {
|
||
|
|
if r.Links == nil {
|
||
|
|
r.Links = map[string]halgo.Link{}
|
||
|
|
}
|
||
|
|
r.Links[rel] = link
|
||
|
|
}
|
||
|
|
|
||
|
|
// AddCurie adds a CURIE link for the sakila namespace.
|
||
|
|
// Note: HAL recommends an array for the "curies" rel. This implementation uses a single link
|
||
|
|
// due to the underlying link map being single-valued per rel. Most clients accept this form.
|
||
|
|
func (r *Resource) AddCurie() {
|
||
|
|
r.AddLink("curies", halgo.Link{
|
||
|
|
Href: "/rels/{rel}",
|
||
|
|
Templated: true,
|
||
|
|
Name: "sakila",
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// Embed sets an embedded payload under the rel.
|
||
|
|
func (r *Resource) Embed(rel string, v interface{}) {
|
||
|
|
if r.Embedded == nil {
|
||
|
|
r.Embedded = map[string]interface{}{}
|
||
|
|
}
|
||
|
|
r.Embedded[rel] = v
|
||
|
|
}
|
||
|
|
|
||
|
|
// SetState sets or merges state fields.
|
||
|
|
func (r *Resource) SetState(obj interface{}) {
|
||
|
|
switch m := obj.(type) {
|
||
|
|
case map[string]interface{}:
|
||
|
|
for k, v := range m {
|
||
|
|
r.State[k] = v
|
||
|
|
}
|
||
|
|
default:
|
||
|
|
// marshal into map and merge
|
||
|
|
b, _ := json.Marshal(obj)
|
||
|
|
var mm map[string]interface{}
|
||
|
|
_ = json.Unmarshal(b, &mm)
|
||
|
|
for k, v := range mm {
|
||
|
|
r.State[k] = v
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// MarshalJSON merges State with _links and _embedded following HAL conventions.
|
||
|
|
func (r *Resource) MarshalJSON() ([]byte, error) {
|
||
|
|
m := map[string]interface{}{}
|
||
|
|
for k, v := range r.State {
|
||
|
|
m[k] = v
|
||
|
|
}
|
||
|
|
if len(r.Links) > 0 {
|
||
|
|
m["_links"] = r.Links
|
||
|
|
}
|
||
|
|
if len(r.Embedded) > 0 {
|
||
|
|
m["_embedded"] = r.Embedded
|
||
|
|
}
|
||
|
|
return json.Marshal(m)
|
||
|
|
}
|