Documentation
¶
Overview ¶
Package hmc provides types for building HATEOAS-driven REST APIs with progressive enhancement.
Each type represents a hypermedia control—a semantic element that guides client state transitions. These controls can be serialized as JSON, XML, or wrapped in HTML templates depending on the client's capabilities.
Philosophy ¶
Traditional REST APIs serve data (JSON) and leave state transitions to out-of-band documentation. HATEOAS APIs serve hypermedia—data enriched with the controls needed to interact with it. This library provides the building blocks for those controls.
The types in hmc are deliberately minimal, focusing on the semantic layer rather than presentation. They describe WHAT actions are available and HOW to invoke them, not how they should be styled or rendered.
Progressive Enhancement ¶
The same endpoint can serve multiple representations:
- JSON: Machine-readable, suitable for scripts and traditional API clients
- XML: Human-readable structure for CLI tools (curl, HTTPie + xmllint)
- HTML: Browser-ready interfaces when wrapped with your templates
This enables a "write once, serve many" approach where a single handler can satisfy browsers, CLIs, and programmatic clients through content negotiation.
Usage ¶
Types are designed to be embedded in your domain structs:
type LoginPage struct {
LoginForm hmc.Form[struct {
Username hmc.Input
Password hmc.Input
}]
RegisterLink hmc.Link
}
Marshal to JSON for API clients, XML for CLI tools, or wrap in HTML templates for browsers. Examples at ./examples/templates.
Input validation is minimal and extensible—Validate() checks Required and MinLength, matching basic browser behavior. Extend by inspecting Input.Value and setting Input.Error for domain-specific rules.
What This Is Not ¶
This is not a complete HTML generation framework. You bring your own templates for presentation (you should use the examples as a starting point). This is not a validation framework—it provides minimal checks that may be extended by you.
Index ¶
- type ErrInputMax
- type ErrInputMaxLength
- type ErrInputMin
- type ErrInputMinLength
- type ErrInputRequired
- type ErrInputValueAsDate
- type ErrInputValueAsDatetime
- type ErrInputValueAsDatetimeLocal
- type ErrInputValueAsTime
- type ErrMapMaxEntries
- type ErrMapMaxLength
- type ErrMapMaxValues
- type ErrSelectHasNonOption
- type Form
- type Input
- func (i *Input) ExtractFormValue(form url.Values)
- func (i Input) MarshalJSON() ([]byte, error)
- func (i Input) MarshalXML(e *xml.Encoder, label xml.StartElement) error
- func (i *Input) ParseValueAsDate() (t time.Time, err error)
- func (i *Input) ParseValueAsDatetime() (t time.Time, err error)
- func (i *Input) ParseValueAsDatetimeLocal() (t time.Time, err error)
- func (i *Input) ParseValueAsTime() (t time.Time, err error)
- func (p *Input) Validate() (err error)
- type Link
- type Map
- type Namespace
- type Option
- type Select
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ErrInputMax ¶ added in v0.6.0
type ErrInputMax struct {
Max string
}
func (ErrInputMax) Error ¶ added in v0.6.0
func (e ErrInputMax) Error() string
type ErrInputMaxLength ¶ added in v0.6.0
type ErrInputMaxLength struct {
MaxLength uint
}
func (ErrInputMaxLength) Error ¶ added in v0.6.0
func (e ErrInputMaxLength) Error() string
type ErrInputMin ¶ added in v0.6.0
type ErrInputMin struct {
Min string
}
func (ErrInputMin) Error ¶ added in v0.6.0
func (e ErrInputMin) Error() string
type ErrInputMinLength ¶ added in v0.6.0
type ErrInputMinLength struct {
MinLength uint
}
func (ErrInputMinLength) Error ¶ added in v0.6.0
func (e ErrInputMinLength) Error() string
type ErrInputRequired ¶ added in v0.6.0
type ErrInputRequired struct{}
func (ErrInputRequired) Error ¶ added in v0.6.0
func (e ErrInputRequired) Error() string
type ErrInputValueAsDate ¶ added in v0.6.0
type ErrInputValueAsDate struct {
Err error
}
func (ErrInputValueAsDate) Error ¶ added in v0.6.0
func (e ErrInputValueAsDate) Error() string
type ErrInputValueAsDatetime ¶ added in v0.6.0
type ErrInputValueAsDatetime struct {
Err error
}
func (ErrInputValueAsDatetime) Error ¶ added in v0.6.0
func (e ErrInputValueAsDatetime) Error() string
type ErrInputValueAsDatetimeLocal ¶ added in v0.6.0
type ErrInputValueAsDatetimeLocal struct {
Err error
}
func (ErrInputValueAsDatetimeLocal) Error ¶ added in v0.6.0
func (e ErrInputValueAsDatetimeLocal) Error() string
type ErrInputValueAsTime ¶ added in v0.6.0
type ErrInputValueAsTime struct {
Err error
}
func (ErrInputValueAsTime) Error ¶ added in v0.6.0
func (e ErrInputValueAsTime) Error() string
type ErrMapMaxEntries ¶ added in v0.6.0
type ErrMapMaxEntries struct {
Max int
}
func (ErrMapMaxEntries) Error ¶ added in v0.6.0
func (e ErrMapMaxEntries) Error() string
type ErrMapMaxLength ¶ added in v0.6.0
func (ErrMapMaxLength) Error ¶ added in v0.6.0
func (e ErrMapMaxLength) Error() string
type ErrMapMaxValues ¶ added in v0.6.0
func (ErrMapMaxValues) Error ¶ added in v0.6.0
func (e ErrMapMaxValues) Error() string
type ErrSelectHasNonOption ¶ added in v0.6.0
type ErrSelectHasNonOption struct{}
func (ErrSelectHasNonOption) Error ¶ added in v0.6.0
func (ErrSelectHasNonOption) Error() string
type Form ¶
type Form[T any] struct { Method string `json:"method,omitempty"` Action string `json:"action,omitempty"` Elements T `json:"elements"` }
Form is analogous to HTML's <form> which represents a state transition that requires input from the client. It describes what data is needed, how it should be submitted, and where it should be sent.
Elements should contain a struct representing, and elements semantically related to the form, which would usually be Input, Select, Map, Link, etc. but might also be something like `Error string` or `Warning string` fields.
func (Form[T]) MarshalXML ¶
type Input ¶
type Input struct {
Label string
Type string
Name string
Value string
Error string
Required bool
Disabled bool
MinLength uint
MaxLength uint
Step float32
Min string
Max string
}
Input describes a piece of data the server needs from the client, including validation requirements.
It is analogous to HTML's <input> and <textarea>.
func (*Input) ExtractFormValue ¶
ExtractFormValue sets i.Value to the first value found at form[i.Name].
The found value is deleted from form.
func (Input) MarshalJSON ¶
func (Input) MarshalXML ¶
func (*Input) ParseValueAsDate ¶ added in v0.6.0
ParseValueAsDate parses i.Value as `type="date"`. An ISO 8601 date.
Input.Type is not checked here.
func (*Input) ParseValueAsDatetime ¶ added in v0.6.0
ParseValueAsDatetime parses i.Value as `type="datetime"`. An ISO 8601 datetime that expects a timezone.
WARNING: This is not widely supported by browsers.
Input.Type is not checked here.
func (*Input) ParseValueAsDatetimeLocal ¶ added in v0.6.0
ParseValueAsDatetimeLocal parses i.Value as `type="datetime-local"`. An ISO 8601 datetime without a timezone.
Input.Type is not checked here.
func (*Input) ParseValueAsTime ¶ added in v0.6.0
ParseValueAsTime parses i.Value as `type="time"`. An ISO 8601 time.
Input.Type is not checked here.
func (*Input) Validate ¶
Validate performs some basic checks on the value of the input according to its settings.
Input.Required, Input.Max, Input.Min, Input.MaxLength, and Input.MinLength are checked, in that order. Similar to the minimal checks that a browser would make for equivalent HTML.
Because it is complex to implement for whatever many types Input.Type might be set to, this function does not validate Input.Step.
Input.Type is treated as a hint to the user, Input.Value is not checked against Input.Type. Such validation is performed when a corresponding Input.ParseValueAs* method is called, although again, Input.Type is not checked, and any parse method may be called regardless of the type attribute. It is your responsibility to make sure that Input.Type is set according to how it is parsed.
This functionality can be extended with more bespoke validation by checking fields and setting the Input.Error field accordingly.
type Link ¶
Link represents a state transition that requires no input—a simple navigation or action trigger.
func (Link) MarshalXML ¶
type Map ¶
type Map struct {
Label string `json:"label"`
Name string `json:"name"`
Error string `json:"error,omitempty"`
// MaxEntries sets the maximum number of entries allowed when [Map.Validate] is called.
//
// Does nothing if set to zero or less.
MaxEntries int `json:"max_entries,omitempty"`
// MaxLength sets the maximum length that any given entry may be when [Map.Validate] is called.
//
// The length of an entry is the length of the key + the length of the value.
//
// Does nothing if set to zero or less.
MaxLength int `json:"max_length,omitempty"`
// MaxValues sets the maximum number of values allowed per entry when [Map.Validate] is called.
//
// Does nothing if set to zero or less.
MaxValues int `json:"max_values,omitempty"`
Entries map[string][]string `json:"entries"`
}
Map represents an arbitrary set of key-value entries. Where Name == "foo", the form submission may populate Entries with values like this `foo[x]=y&foo[a]=b&foo[stuff]=etc`.
When Name == "", Map.ExtractFormValue will extract all values from the given form. Extract specific fields first to prevent them from being captured by the catch-all.
func (*Map) ExtractFormValue ¶
ExtractFormValue takes all entries from form with name x[y], where x = m.Name, and y is an arbitrary key provided by the request.
If m.Name == "", then all entries in form are transferred to m.Entries. Extract specific fields first to prevent them from being captured by the catch-all.
func (Map) MarshalXML ¶
type Namespace ¶
type Namespace struct {
HcXmlns string `xml:"xmlns:c,attr" json:"-"`
Docs xml.Comment `xml:",comment" json:"-"`
}
Namespace provides the XML namespace declaration for hmc elements. It should be embedded in your top-level struct and initialized using NS().
Without the namespace, hmc elements (c:Form, c:Input, etc.) will not be properly recognized by browsers.
Example:
type MyPage struct {
Namespace hmc.Namespace
Title string
Form hmc.Form[MyFormData]
}
page := MyPage{
Namespace: hmc.NS(),
Title: "My Page",
}
This struct is omitted during JSON serialization.
type Option ¶
type Option struct {
Label string `json:"label,omitempty"`
Value string `json:"value"`
Selected bool `json:"selected,omitempty"`
Disabled bool `json:"disabled,omitempty"`
}
func (Option) MarshalXML ¶
type Select ¶
type Select struct {
Label string `json:"label"`
Multiple bool `json:"multiple,omitempty"`
Name string `json:"name"`
Error string `json:"error,omitempty"`
Required bool `json:"required,omitempty"`
Options []Option `json:"options"`
Disabled bool `json:"disabled,omitempty"`
}
func (*Select) ExtractFormValue ¶
ExtractFormValue behaves similarly to Input.ExtractFormValue. If s.Multiple is set, all values are taken; if not, the first value is taken.
An error is returned if a value is extracted that is not listed in s.Options; but it is safe to ignore this error if unlisted selections are allowed. See Select.SetValues
func (Select) MarshalXML ¶
func (*Select) SetValues ¶
SetValues returns an error if a value is provided that is not listed in s.Options. Ignore this error if this Select is meant to allow unlisted selections.