HTTP Request and Response Parameter Mapping (Go)
Overview
When a Go agent is exposed over HTTP (see golem-add-http-endpoint-go), the platform maps parts of each request onto the agent’s constructor (ID) fields and the method’s input struct fields. This skill covers how path segments, query parameters, headers, and the request body bind to those fields, which Go types each binding accepts, and how return types map to HTTP responses.
Binding is metadata only and declared on the definition: the mount Path on Spec.HTTP and the golem.HTTP(...) option on each method. There is no incoming-request handler in the guest.
Steps
- Name fields to match wire names. A path/query/header variable refers to an input field by its schema name: the Go field name in lowerCamelCase with each initialism as one word, so
MaxResultsismaxResults,SKUissku,UserIDisuserIdandAPIKeyisapiKey. - Bind path variables by putting
{name}in the mount path (bindsIDfields) or an endpoint path suffix (binds method input fields). - Bind query and header values inline (
?name={field}) or withgolem.Query/golem.Header. - Leave body fields unbound on body-carrying verbs; they arrive as JSON.
Path variables
{var} segments bind by schema name.
- Mount path
{var}s bind the agent’s constructor (ID) fields. EveryIDfield must appear as a{var}so a request selects one instance. - Endpoint path
{var}s bind the method’s input fields.
type ID struct{ Name string } // bound by {name} in the mount
type LookupIn struct {
SKU string
Detailed bool
}
var Agent = golem.DefineAgent[ID](golem.Spec{
Name: "CatalogAgent",
HTTP: &golem.Mount{Path: "/catalog/{name}"}, // {name} → ID.Name
})
// GET /catalog/{name}/items/{sku} — {sku} → LookupIn.SKU
var Lookup = Agent.Method[LookupIn, ItemInfo]("lookup", golem.HTTP(golem.GET("/items/{sku}", golem.Query("detailed", "detailed"))))The trailing catch-all {*rest} captures everything after a prefix and may only appear as the last endpoint segment; it is not allowed in a mount path.
Query parameters
Two equivalent forms. Inline ?name={field} in the path suffix, or golem.Query(wire, field) when the wire name differs from the field:
type SearchIn struct {
Query string
MaxResults uint64
}
// GET /catalog/{name}/search?q=hello&limit=10
var Search = Agent.Method[SearchIn, []Result]("search", golem.HTTP(golem.GET("/search",
golem.Query("q", "query"), // ?q= → SearchIn.Query
golem.Query("limit", "maxResults"), // ?limit= → SearchIn.MaxResults
)))Inline golem.GET("/search?q={query}&limit={maxResults}") is equivalent when the parameter name matches the field’s wire name.
Header variables
golem.Header(wire, field) maps a request header to an input field:
type DataIn struct {
RequestID string
Token string
}
var GetData = Agent.Method[DataIn, Data]("getData", golem.HTTP(golem.GET("/data",
golem.Header("X-Request-Id", "requestId"),
golem.Header("Authorization", "token"),
)))Header names are matched case-insensitively; binding the same header twice on one endpoint is a definition error.
Supported types for path, query, and header bindings
A value bound from a URL path/query/header string must decode into a scalar Go field. Only these kinds bind:
| Go type | Bound from |
|---|---|
string | used as-is |
bool | "true" / "false" |
int8, int16, int32, int64 | signed integer |
uint8, uint16, uint32, uint64 | unsigned integer |
float32, float64 | floating-point number |
Binding a non-scalar field (struct, slice, map) from path/query/header is a definition error: “cannot bind input field … only scalar fields decode from a URL”. Such fields must arrive in the body instead.
Prefer sized integers throughout. Bare
int/uintare rejected at the agent-schema level because their width is platform-dependent (seegolem-add-agent-go).
Request body mapping
For body-carrying verbs (POST, PUT, DELETE, PATCH, …), any input field not bound to a path/query/header binding is populated from the JSON request body:
type UpdateIn struct {
SKU string // from the path
Name string // from the body
Count uint64 // from the body
}
// POST /catalog/{name}/items/{sku} Body: {"name":"Widget","count":5}
var Update = Agent.Method[UpdateIn, Item]("update", golem.HTTP(golem.POST("/items/{sku}")))Each unbound field becomes a top-level key in the JSON body object (keyed by schema name, as elsewhere in the SDK).
GET and HEAD are bodyless: they carry no request body, so they must bind every input field from path/query/header. An unbound field on a bodyless verb is a definition error.
Return type to HTTP response mapping
Handlers return their output value; the platform maps it to a response. In Go:
| Return type | HTTP status | Body |
|---|---|---|
golem.Unit | 204 No Content | empty |
T (any type) | 200 OK | JSON-serialized T |
*T | 200 OK if non-nil, 404 Not Found if nil | JSON T or empty |
golem.Result[T, E] | 200 OK on Ok, 500 Internal Server Error on Err | JSON T or JSON E |
golem.Result[*T, E] | 200 OK on Ok with a value, 404 Not Found on Ok(nil), 500 on Err | JSON T, empty, or JSON E |
golem.UnstructuredBinary[M] | 200 OK | raw bytes with Content-Type (default application/octet-stream) |
golem.UnstructuredText[L] | 200 OK | text/plain; charset=utf-8 (plus Content-Language when set) |
an unstructured value with URL set | 307 Temporary Redirect | Location = the URL |
golem.Unit is the empty result placeholder; *T is Golem’s option<T> (a nil pointer means “absent”, as does golem.Option[T]); golem.Result[Ok, Err] returns the error as an observable value (see golem-add-agent-go on returning failures).
The value inside an option or an Ok follows the same rules as a top-level value. An unstructured value nested in *T, golem.Result[T, E] or golem.Result[*T, E] is sent as the raw body (or a redirect), not as JSON:
// 200 raw bytes / 404 when missing / 500 with a JSON DownloadError
var Download = Agent.Method[DownloadIn, golem.Result[*golem.UnstructuredBinary[golem.AnyMimeType], DownloadError]](
"download", golem.HTTP(golem.GET("/files/{id}")))Data type to JSON mapping
| Go type | JSON |
|---|---|
string | string |
| sized ints / uints | number (integer) |
float32, float64 | number |
bool | boolean |
[]T | array |
| struct | object keyed by schema names (MaxResults → maxResults, UserID → userId) |
*T | value or null |
map[K]V | object / WIT map |
Key Constraints
- A variable can bind an input field from exactly one of path/query/header — binding it more than once is a definition error.
- Bindable path/query/header fields must be scalar; non-scalars go in the body.
- Bodyless verbs (
GET,HEAD) must bind every input field. - Field references on the wire use the schema name, the field name in lowerCamelCase with each initialism as one word (
SKU→sku,RequestID→requestId). - A raw (non-JSON) body is a single body field of type
golem.UnstructuredBinary[M]orgolem.UnstructuredText[L];M/Lisgolem.AnyMimeType/golem.AnyLanguageor a marker type whoseMimeTypes()/Languages()lists what is accepted. Any other body is a JSON object keyed by the schema names.
Related Guides
| Guide | Description |
|---|---|
golem-add-http-endpoint-go | The high-level workflow of mounting an agent and adding routes |
golem-add-http-auth-go | Require authentication on endpoints |
golem-add-cors-go | Allow cross-origin requests |
golem-add-agent-go | Define the agent and its input/output types |