hmc

package module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 30, 2026 License: CC0-1.0 Imports: 8 Imported by: 0

README

Hyper Media Controls

hmc's primary purpose is to provide human-readable XML wrappers to a REST interface, to guide the user in how to interact with the interface. Similar to HTML, but stripped down for readability; generally on the commandline using a tool like HTTPie.

An XML element under the c: namespace is a hypermedia control provided by this package that tells the user what interactions on the given resource are possible.

There are five main elements that hmc provides. They generally try to mimic the existing standard of HTML:

  • <c:Form>: analogous to HTML's <form>. It encloses a group of inputs, and generally describes which HTTP verb to use under the method attribute, e.g. POST or by default GET.
  • <c:Input>: analogous to HTML's <input> type. It represents a single name-value pair. It may have validation attributes, similar to HTML: e.g. type, required, minlength. An <c:Input> (or a <c:Select>, or a <c:Map>) outside of a <c:Form> is not a valid input.
  • <c:Select>: analogous to HTML's <select>. It represents an input with fixed options. The option list may be non-exaustive. It may take multiple options if the multiple attribute is set.
  • <c:Link>: analogous to HTML's <a> hyperlink. It provides directions to other relevant resources.
  • <c:Map>: Is the only element without an HTML analogue. A <c:Map> with name="foo" means that arbitrary name-value pairs may be provided under the namespace "foo" with bracket notation, e.g. foo[bar]=baz.

These elements can also be serialised to JSON for ease of querying, especially using jq.

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
        Submit   hmc.Submit
    }]
    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.

Pairs Well With

This library pairs naturally with github.com/Teajey/rsvp, which provides HTTP handlers with ergonomic content negotiation, making it easy to serve the same semantic data in multiple formats based on Accept headers.

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 client-side form library—it's server-side semantics. This is not a validation framework—it provides minimal checks and extension points for your domain rules.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Form

type Form[T any] struct {
	Method   string `json:"method,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

func (i Form[T]) MarshalXML(e *xml.Encoder, start xml.StartElement) error

type Input

type Input struct {
	Label     string
	Type      string
	Name      string
	Required  bool
	Value     string
	Error     string
	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>.

IMPORTANT: Some fields are mutually-irrelevant; such as Options and MinLength, but they are both kept in this struct for simplicity. It is not an error to have them both set at the same time, but it is semantically incorrect and may cause confusion.

func (*Input) ExtractFormValue

func (i *Input) ExtractFormValue(form url.Values)

ValueFromUrlValues will searching for the Input's value under p.Name, setting p.Value.

The found value is deleted from form.

func (Input) MarshalJSON

func (i Input) MarshalJSON() ([]byte, error)

func (Input) MarshalXML

func (i Input) MarshalXML(e *xml.Encoder, start xml.StartElement) error

func (*Input) Validate

func (p *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.

This functionality can be extended with more bespoke validation by checking fields and setting the [Input.Error] field accordingly.

type Link struct {
	Label string `json:"label"`
	Href  string `json:"href"`
}

Link represents a state transition that requires no input—a simple navigation or action trigger.

func (Link) MarshalXML

func (i Link) MarshalXML(e *xml.Encoder, start xml.StartElement) error

type Map

type Map struct {
	Label   string              `json:"label"`
	Name    string              `json:"name"`
	Entries map[string][]string `json:"entries"`
	Error   string              `json:"error,omitempty"`
}

func (*Map) ExtractFormValue

func (m *Map) ExtractFormValue(form url.Values)

func (Map) MarshalXML

func (m Map) MarshalXML(e *xml.Encoder, start xml.StartElement) error

func (Map) NamedKey

func (m Map) NamedKey(key string) string

type Namespace

type Namespace struct {
	HcXmlns string      `xml:"xmlns:c,attr" json:"-"`
	Docs    xml.Comment `xml:",comment" json:"-"`
}

Namespace should be embedded in the top-level element to provide context about what the hyper control elements are.

SetNamespace should be used to populate the values to their default.

func SetNamespace

func SetNamespace() Namespace

SetNamespace provides a default setting for the Namespace struct.

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

func (o Option) MarshalXML(e *xml.Encoder, start xml.StartElement) error

type Select

type Select struct {
	Multiple bool     `json:"multiple,omitempty"`
	Label    string   `json:"label"`
	Name     string   `json:"name"`
	Required bool     `json:"required,omitempty"`
	Options  []Option `json:"options"`
	Error    string   `json:"error,omitempty"`
}

func (*Select) ExtractFormValue

func (s *Select) ExtractFormValue(form url.Values)

func (Select) MarshalXML

func (i Select) MarshalXML(e *xml.Encoder, start xml.StartElement) error

func (*Select) SetValues

func (s *Select) SetValues(values ...string)

func (Select) Value

func (s Select) Value() string

func (Select) Values

func (s Select) Values() iter.Seq[string]

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL