lenient

package
v0.35.0 Latest Latest
Warning

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

Go to latest
Published: Aug 18, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

README

lenient

Scalar types that are forgiving about how a remote system encodes them: lenient.Int64 and lenient.String. They exist for the receiving half of Postel's law — be conservative in what you send, be liberal in what you accept. Part of rosetta.

Go Reference

A plain int64 field fails an entire document the first time a peer sends "480" instead of 480. Swap in lenient.Int64 and that document parses, along with every other numeric spelling in the wild — quoted integers, floats, nulls, empty strings, "100%". lenient.String does the same for text fields that peers sometimes send as bare numbers or booleans, which is how oEmbed's "version": 1.0 arrives from SoundCloud.

type Response struct {
	Version lenient.String `json:"version"` // arrives as 1.0, a JSON *number*
	Width   lenient.Int64  `json:"width"`   // arrives as "480", a JSON *string*
	Height  lenient.Int64  `json:"height"`  // arrives as null
}

Tolerance never leaks into what you publish: both types marshal to the plain, correct JSON encoding of their underlying value.

What survives, and what doesn't

Int64 defers to convert.Int64, so floats truncate toward zero, out-of-range values clamp to the int64 bounds, and anything unparseable quietly becomes zero — "not provided" rather than an error. Top-level integers are read from their source text, so IDs and timestamps above 2^53 stay exact instead of rounding through a float64.

It is Int64 rather than Int deliberately. convert.Int clamps to the platform int width, so on a 32-bit target — GOARCH=wasm is one — a plain Int would silently cap at 2^31. A sender's JSON has no idea what you built for. Precision beyond int64 is explicitly not a goal: larger values clamp, and an integer nested inside an array rounds.

String keeps a number's source text too, which is the whole point: 1.0 must stay "1.0" and never become "1" or "1.00".

Tolerance stops at structure. String rejects objects and arrays outright, because coercing one would invent a value the sender never wrote. Both types error only on input that isn't valid JSON at all.

Fuzzing

The invariants are pinned by property-based fuzz targets in fuzz_test.go: decode never panics, a successful decode always re-encodes to valid JSON, values are a fixed point under repeated round trips, exact integers stay exact, and both types behave as struct fields without desynchronizing the decoder for their neighbors. The corpus in testdata/fuzz includes every crasher these targets have found — the round-trip property is what caught the float64 precision loss.

Add a new lenient type to jsonTargets in that file and it inherits the whole universal safety net.

Documentation

Overview

Package lenient provides scalar types that are forgiving about how a remote system encodes them. They exist for the receiving half of Postel's law: be conservative in what you send, be liberal in what you accept.

A plain `int64` field fails an entire document the first time a peer sends "480" instead of 480. Substituting lenient.Int64 absorbs that, and every other numeric spelling seen in the wild, without weakening the strict validation you apply to documents you author. lenient.String does the same for text fields that peers sometimes send as bare numbers or booleans.

The numeric type is Int64, not Int, on purpose: a sender's JSON has no idea what GOARCH you built for, so the accepted range must not narrow on a 32-bit target. Precision beyond int64 is not a goal.

Tolerance stops at structure. Both types reject input that is not a JSON scalar at all, because coercing an object or array would invent a value the sender never wrote. Everything short of that quietly degrades to the zero value rather than erroring, so one sloppy field never costs you the document.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Int64

type Int64 int64

Int64 is a tolerant integer that accepts whatever numeric encoding a remote system actually sends. Parsing semantics are convert.Int64, applied per Postel's law: JSON numbers and quoted integer strings parse, floats truncate toward zero, out-of-range values clamp to the int64 bounds, and null, empty, or unparseable values quietly become zero ("not provided"). Output is always a plain JSON number.

Integers are exact across the whole int64 range: a top-level JSON integer is parsed from its source text, so values above 2^53 survive intact rather than degrading through a float64. The underlying type is int64 rather than int so that range does not depend on the platform — a sender's JSON has no idea what GOARCH you built for. Precision beyond int64 is not a goal: larger values clamp, and an integer nested inside an array (`[9007199254740993]`) unwraps through the tolerant path and rounds.

func (Int64) MarshalJSON

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

MarshalJSON encodes the value as a plain JSON number.

func (*Int64) UnmarshalJSON

func (i *Int64) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes any JSON value into the integer, tolerantly.

func (*Int64) UnmarshalXML

func (i *Int64) UnmarshalXML(decoder *xml.Decoder, start xml.StartElement) error

UnmarshalXML decodes the element's character data with the same tolerant numeric parsing as UnmarshalJSON.

type String

type String string

String is a tolerant string that accepts whatever scalar encoding a remote system actually sends. Applied per Postel's law: quoted strings pass through, JSON numbers keep their exact source text (oEmbed providers send `"version": 1.0`, and "1.0" must not become "1"), booleans become "true"/"false", and null becomes the empty string. Objects and arrays are not scalars and are rejected. Output is always a plain JSON string.

func (*String) UnmarshalJSON

func (s *String) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes any scalar JSON value into the string, tolerantly.

Jump to

Keyboard shortcuts

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