This package provides helper functions for marshaling and unmarshalling HTTP parameters in headers, cookies, and query arguments in various formats, as well as functions for reading and writing form encoded data representing models.
You won't need to use this package directly, since it's imported be the boilerplate
from oapi-codegen, however, you do need to use the correct version needed by
the code generator, since it makes assumptions about runtime behavior.
OpenAPI 3.x parameters are characterized by three orthogonal attributes: style, location, and explode. The serialized form on the wire is determined by the combination of all three.
Parameters come in the following styles (all defined by the OpenAPI 3.x spec):
simple— comma-separated values. The default for path and header parameters.label— values prefixed with., separated by.(explode) or,(no explode). Path parameters only.matrix— values prefixed with;name=, repeated (explode) or comma-separated (no explode). Path parameters only.form—name=valuepairs joined with&. The default for query and cookie parameters.spaceDelimited— array elements joined by literal spaces (no explode); behaves identically toformwhen exploded. Query parameters, arrays only.pipeDelimited— array elements joined by literal|(no explode); behaves identically toformwhen exploded. Query parameters, arrays only.deepObject— nested bracket notation, e.g.name[field]=value. Query parameters, objects only, must be exploded.
Each style is only valid in specific parameter locations:
| Location | Allowed styles |
|---|---|
path |
simple, label, matrix |
query |
form, spaceDelimited, pipeDelimited, deepObject |
header |
simple |
cookie |
form |
Each style can be explode: true or explode: false, which changes how
multi-value parameters (arrays and objects) are packed.
| Style | Type | explode: false | explode: true |
|---|---|---|---|
simple |
primitive 5 | 5 | 5 |
array [3,4,5] | 3,4,5 | 3,4,5 | |
object {role:"admin", firstName:"Alex"} | firstName,Alex,role,admin | firstName=Alex,role=admin | |
label |
primitive 5 | .5 | .5 |
array [3,4,5] | .3,4,5 | .3.4.5 | |
object {role:"admin", firstName:"Alex"} | .firstName,Alex,role,admin | .firstName=Alex.role=admin | |
matrix |
primitive 5 | ;id=5 | ;id=5 |
array [3,4,5] | ;id=3,4,5 | ;id=3;id=4;id=5 | |
object {role:"admin", firstName:"Alex"} | ;id=firstName,Alex,role,admin | ;firstName=Alex;role=admin | |
form |
primitive 5 | id=5 | id=5 |
array [3,4,5] | id=3,4,5 | id=3&id=4&id=5 | |
object {role:"admin", firstName:"Alex"} | id=firstName,Alex,role,admin | firstName=Alex&role=admin | |
spaceDelimited |
primitive | not supported | |
array [3,4,5] | id=3 4 5 | id=3&id=4&id=5 | |
| object | not supported | ||
pipeDelimited |
primitive | not supported | |
array [3,4,5] | id=3|4|5 | id=3&id=4&id=5 | |
| object | not supported | ||
deepObject |
primitive | not supported | |
array [3,4,5] | id[0]=3&id[1]=4&id[2]=5 (explode required) | ||
object {role:"admin", firstName:"Alex"} | id[firstName]=Alex&id[role]=admin (explode required) | ||
The above outputs are shown unescaped for readability. In real use, values destined for query parameters are passed through
url.QueryEscape(orurl.PathEscapefor path parameters), so reserved characters and non-ASCII bytes are percent-encoded on the wire.
The OpenAPI 3.x parameter styles are convenient but each has at least one sharp edge. The list below documents behaviors that surprise users and the cases where round-tripping is not possible in principle.
- Query and path values are percent-encoded. Reserved characters
(
&,=,#,?, etc.) and non-ASCII bytes are escaped viaurl.QueryEscape/url.PathEscape. Spaces in query values are encoded as+(form-urlencoded convention), matchingurl.Values.Encode(). - Header values are passed through raw. Per RFC 7230 §3.2.6, header
field values may contain visible ASCII plus space/tab; bytes ≥
0x80areobs-textand explicitly marked obsolete in RFC 9110. There is no generally-agreed mechanism for transporting non-ASCII text in arbitrary header values (RFC 8187 covers only header parameters likeContent-Dispositionfilename*=). If your spec puts non-ASCII or control characters into a header parameter, the wire format is RFC-noncompliant and proxies may strip or reject the request. - Cookie values are passed through raw. Per RFC 6265 §4.1.1, cookie
values may not contain
CTL, whitespace,",,,;,\, or any byte ≥0x80. Most cookie libraries URL-encode by convention, but this package does not — if your spec puts spaces or non-ASCII into a cookie parameter, the value will not be RFC 6265-conformant. - Map keys are percent-encoded for
deepObjectonly. Forsimple,label,matrix, andformstyles, map keys are emitted raw. If your map keys are non-ASCII or contain URL-reserved characters, the wire representation will not be encoded. allowReserved(StyleParamOptions.AllowReserved) is a query-only option per the OpenAPI 3.x spec, and only applies to values, not parameter names or map keys.
- Bracket notation is structural, not data.
MarshalDeepObjectpercent-encodes literal[and]inside values and map keys as%5B/%5Don the wire. However, once a server callsurl.ParseQuery, those escapes are decoded back to[and]— at which point a key likep[a[b]]is ambiguous between{p: {a: {b: ...}}}and{p: {"a[b]": ...}}.UnmarshalDeepObjectcannot distinguish these cases and adopts the same greedy left-to-right parse used by qs (Node), RailsRack::Utils.parse_nested_query, and similar libraries: every unescaped[opens a new nesting level. Literal[and]inside map keys cannot be round-tripped. Use a different separator in user-supplied keys if this matters to you. - OpenAPI 3.x defines
deepObjectonly for object schemas. This package extends it to maps and arrays for convenience (arrays are numerically indexed:p[0],p[1], …), but other tooling may not accept that wire form. deepObjectrequiresexplode: true. Non-explodeddeepObjecthas no well-defined wire format; an error is returned.
- Array-only. Per the OpenAPI spec, these styles only apply to arrays of primitives. Passing a primitive or object returns an error.
- Exploded form degenerates to
form. Whenexplode: true, the separator becomes&(not space or pipe), so the output is byte-identical toformexploded. The space/pipe separator only takes effect whenexplode: false. This is per the OpenAPI spec, but it surprises many users. - Unexploded
spaceDelimitedis RFC-fragile. Literal spaces in a query string are tolerated by most parsers but are not RFC 3986- conformant;+would be the form-urlencoded canonical form for space, butspaceDelimitedis defined to use literal%20-equivalent space (the value bytes themselves are then encoded normally).
nullin a struct field marshals to the literal string"null"indeepObjectoutput. There is no distinct OpenAPI representation for absent vs. JSON-null in query parameters.time.Timeandtypes.Dateare formatted via RFC 3339 and2006-01-02respectively when used as primitives in any style. If you want a different format, wrap the field in a type that implementsencoding.TextMarshaler.types.UUIDstringifies to the canonical hyphenated 36-character form; query/path location escaping is a no-op (UUIDs are in the unreserved set).json.Marshaleris consulted for structs, then the result is re-decoded withUseNumber()and re-styled. Numbers therefore retain their original precision, but the round-trip through JSON means struct field tags are honored (not raw Go field names).