Compatibility
binder follows Semantic Versioning. Within a major version the list below will not change incompatibly, and the list after it may change in any release. Being explicit about the second list is what makes the first one worth anything.
Stable within a major version
- The exported functions
BindandBindWithOptions. - The exported types
BindOptions,BindError,BindErrorsandValidator, and the meaning of their fields. - The sentinel errors
ErrMalformedBody,ErrBodyTooLarge,ErrInvalidTarget,ErrMissingRequiredandErrUnknownField. Match on these witherrors.Israther than on message text. - The struct tags
path,query,body,json,cookie,headerandform, the order in which they take precedence, and theomitemptyandrequiredoptions.
Not part of the contract
- The text of error messages. Only the sentinels and
BindError's fields are stable; parsing a message is not supported. - The value of
DefaultMaxBodySize. PassBindOptions.MaxBodySizeif your service depends on a particular limit. - The order in which fields are bound, and how many allocations binding takes.
Go version support
binder supports the Go releases the Go project supports: the two most
recent. It currently requires Go 1.27, so until Go 1.28
ships, 1.27 is the only supported release. JSON is decoded with
encoding/json/jsontext, so the toolchain's jsonv2 experiment
must be on, as it is by default; GOEXPERIMENT=nojsonv2 will
not build binder. Raising that minimum is a minor version bump, not
a major one, in line with the wider Go ecosystem.
*json.SyntaxError:
binder parses with encoding/json/jsontext, so
errors.As(err, new(*json.SyntaxError)) does not match. Test for ErrMalformedBody instead.
Upgrading from 1.1 to 1.2
1.2.0 changes the API and some behaviour in ways that need code changes or a check:
| Change | What to do |
|---|---|
Validator takes a context |
Change Validate() error to Validate(ctx context.Context) error; rules that do not need the context can ignore it. A type still declaring the old method no longer satisfies Validator and is not validated, so check every implementation. |
Field failures are BindErrors |
Every field is attempted and all failures come back as BindErrors, even when there is only one. err.(*binder.BindError) no longer matches; use errors.As to get the list, or a *BindError for the first failure. |
| Unknown fields are entries | With DisallowUnknownFields, each unknown key is its own BindErrors entry wrapping ErrUnknownField, rather than one error listing them all. errors.Is(err, ErrUnknownField) still works. |
| Nested failures are named by path | A failure inside a nested struct or slice is its own entry, with Field and Name such as Inner.A and inner.a, or IDs[1] and id[1]. |
BindStruct is gone |
It exposed an internal detail of nested binding through reflect.Value; Bind binds nested structs itself. |
The MaxBodySize variable is gone |
Pass BindOptions{MaxBodySize: n} to BindWithOptions instead. Bind uses DefaultMaxBodySize, as does a zero MaxBodySize; a negative value still removes the limit. |
| No body field, no body read | A target with no body or json field leaves the body unread, so an oversized or malformed body no longer fails it. Set DisallowUnknownFields if such an endpoint must reject a JSON or form body that carries members. |
| Empty values do not fill a slice | ?tags= used to bind [""] and satisfy required. A query parameter or header whose every value is empty now counts as absent for a slice, as it already did for a single value. |
| Trailing JSON is malformed | A JSON body followed by anything but whitespace, such as a second object, is ErrMalformedBody; it used to bind from the first value. |
json:"-" is not bound |
It used to bind a body key literally named -. A tag with an empty name now binds under the Go field name rather than the key "". |
| Form bodies on every method | A form-encoded body now binds on GET and DELETE too, is limited by MaxBodySize rather than also by net/http's 10 MB form cap, and a malformed URL query no longer fails it. |
| JSON arrays and objects are not strings | Binding one into a string field is an error; it used to bind Go's formatting, such as [a b]. |
omitempty has no effect on pointers |
A *bool or *int tagged omitempty used to skip a JSON false or 0, so a PATCH setting one was lost. The pointer alone now tells "not sent" from zero. |
A time.Duration needs a unit in text |
Text such as 9 used to bind as 9 nanoseconds; it is now an error, except 0. Send 9ns, or a JSON number, which is still nanoseconds. |
| Fractions do not truncate | A JSON number such as 1.9 bound into an integer field, or time.Duration, used to truncate to 1; it is now an error, as it already was from a query string. |
| A failed occurrence stands | A JSON member sent twice binds its last occurrence, but an earlier occurrence that failed to convert is reported, as in encoding/json; the body used to be decoded into a map, so only the last occurrence was looked at. |
| Nested options apply | required and omitempty inside a nested struct used to be ignored; they now behave as they do at the top level. |
Upgrading from 1.0 to 1.1
1.1.0 is the first release intended for general use; 1.0 ran only on our own client projects. If you are arriving at binder fresh, none of this applies to you — it is written for those two codebases.
No exported function changed shape, so 1.1.0 is source compatible. It does change behaviour in cases that previously failed quietly, which is the point of most of it:
| Change | What to expect |
|---|---|
| Request bodies are capped at 10 MB | Anything larger is ErrBodyTooLarge instead of being read into memory. The change most likely to be noticed. In 1.1 the package-level binder.MaxBodySize raised, lowered or removed it; from 1.2, pass BindOptions.MaxBodySize instead. |
| A body that cannot be parsed is an error | Malformed JSON previously bound nothing and reported success. Expect new 400s where there were quiet successes with empty fields. |
| Tag options now bind | body:"email,omitempty" searched for a key literally named email,omitempty and never bound. Such fields will start receiving values, which may surface data a handler never saw. |
| Chunked request bodies are read | They were skipped entirely, because the body was gated on a positive Content-Length and a chunked request declares none. |
| Repeated values fill slices | A query parameter, header or form field given more than once now binds every value to a slice field rather than the first. Non-slice fields are unaffected. |
| Go 1.27 is required | The previous release declared go 1.25.1; the documentation's claim of 1.22+ was never accurate. |
The full list is in the changelog.
Security
binder parses untrusted input by design, so the body limit, the refusal to truncate and the absence of panics are treated as security properties rather than niceties. Report anything that looks like a vulnerability through the process in SECURITY.md rather than a public issue.