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) }