Documentation
¶
Overview ¶
Package payload implements the dual-format Astarte data-payload codec (docs/DESIGN.md §3.5): standard BSON `{v, t}` documents as produced by the official Astarte device SDKs, and the strict Astrate JSON profile (§3.5.3) for constrained clients (AtomVM and friends) on the same topics with the same semantics.
The package is pure: it sniffs the wire format (§3.5.2), decodes and validates a payload against a compiled interface mapping with the upstream coercion rules (docs/DESIGN.md §2.6 step 5), and encodes outbound `{v, t}` documents in either format (§3.5.4). Every rejection carries a typed RejectReason that internal/engine feeds into per-reason metrics and device_error triggers.
Index ¶
Constants ¶
const ( // DefaultMaxSize is the default accepted payload size for both // formats: 64 KiB (configurable per call site). DefaultMaxSize = 64 << 10 // MaxStringLen is the maximum byte length of a decoded string value. MaxStringLen = 64 << 10 // MaxArrayLen is the maximum number of elements in an array value. MaxArrayLen = 1024 )
Size and cardinality limits (docs/DESIGN.md §2.6 step 5, §3.5.3).
Variables ¶
var ( // MinDateTime is the earliest accepted instant: 0001-01-01T00:00:00Z. MinDateTime = time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC) // MaxDateTime is the latest accepted instant: 9999-12-31T23:59:59.999Z. MaxDateTime = time.Date(9999, time.December, 31, 23, 59, 59, 999_000_000, time.UTC) )
MinDateTime and MaxDateTime bound accepted datetime values (both payload values and explicit `t` timestamps). The window — years 0001 through 9999 — is the intersection of what RFC 3339 (4-digit years), BSON UTC datetime, and PostgreSQL timestamptz all represent without surprises.
Functions ¶
func Encode ¶
Encode builds an outbound `{v, t}` document in the requested format (docs/DESIGN.md §3.5.4: BSON by default, JSON for devices hinted to the JSON profile). v must be one of the closed Value set — scalars, arrays, or map[string]Value for object aggregation; ts is the optional explicit timestamp. FormatEmpty encodes the property-unset payload: v and ts must be nil and the result is the empty payload.
Types ¶
type DecodedPayload ¶
type DecodedPayload struct {
// Value is the decoded value (see Value); nil for an unset payload.
Value Value
// Timestamp is the explicit `t` timestamp, if the mapping declares
// explicit_timestamp and the payload carried one; nil otherwise
// (reception time applies).
Timestamp *time.Time
// Format is the wire format the payload arrived in. internal/engine
// uses it to maintain the device's payload_format_hint
// (docs/DESIGN.md §3.5.4).
Format Format
}
DecodedPayload is the result of decoding one data payload.
func Decode ¶
func Decode(p []byte, m *interfaceschema.CompiledMapping) (DecodedPayload, error)
Decode decodes an individual-aggregation payload with the default limits.
func DecodeObject ¶
func DecodeObject(p []byte, leaves map[string]*interfaceschema.CompiledMapping) (DecodedPayload, error)
DecodeObject decodes an object-aggregation payload with the default limits.
func (DecodedPayload) IsUnset ¶
func (d DecodedPayload) IsUnset() bool
IsUnset reports whether the payload was the empty property-unset payload.
type Decoder ¶
type Decoder struct {
// MaxSize caps the accepted payload size in bytes for both formats
// (docs/DESIGN.md §3.5.3); 0 means DefaultMaxSize.
MaxSize int
}
Decoder decodes inbound data payloads. The zero value is ready to use with the default limits.
func (Decoder) Individual ¶
func (d Decoder) Individual(p []byte, m *interfaceschema.CompiledMapping) (DecodedPayload, error)
Individual decodes p against an individual-aggregation mapping: BSON or JSON `{v, t}` envelope, value coerced to m.ValueType, empty payload = property unset (allowed only with allow_unset). The explicit-timestamp policy follows docs/DESIGN.md §2.6 step 5: a mapping with explicit_timestamp requires `t`; otherwise a present `t` is tolerated and ignored (upstream leniency).
func (Decoder) Object ¶
func (d Decoder) Object(p []byte, leaves map[string]*interfaceschema.CompiledMapping) (DecodedPayload, error)
Object decodes p against an object-aggregated interface: `v` must be a document of last-level endpoint names, each resolving in leaves (CompiledInterface.ObjectLeaves). Object aggregation exists only on datastreams, so the empty (property-unset) payload is always rejected. The explicit-timestamp policy is taken from the leaves (uniform across an object-aggregated interface by construction).
type Format ¶
type Format uint8
Format is the detected wire format of an inbound data payload (docs/DESIGN.md §3.5.2).
const ( // FormatInvalid marks a payload that is neither empty, BSON, nor JSON. FormatInvalid Format = iota // FormatEmpty is the zero-length payload (property-unset semantics). FormatEmpty // FormatBSON is a BSON `{v, t}` document (official SDKs). FormatBSON // FormatJSON is an Astrate JSON-profile `{"v": ..., "t": ...}` document. FormatJSON )
Format values returned by DetectFormat.
func DetectFormat ¶
DetectFormat classifies a raw inbound payload, implementing the exact docs/DESIGN.md §3.5.2 sniffing algorithm:
if len(p) == 0 → FormatEmpty (property unset)
else if len(p) >= 5
&& int32LE(p[0:4]) == len(p) → FormatBSON (self-describing prefix)
&& p[len(p)-1] == 0x00
else if first non-WS byte == '{' → FormatJSON
else → FormatInvalid
The two structural branches cannot collide: valid JSON text contains no NUL byte, so it can never satisfy the BSON terminator condition, while a BSON document whose first byte happens to be '{' (a 123-byte document) is claimed by the BSON branch first. DetectFormat never allocates.
type RejectError ¶
type RejectError struct {
// Reason is the rejection class (metrics label).
Reason RejectReason
// Detail is a human-readable explanation for logs and trigger events.
Detail string
}
RejectError is the typed error returned for every payload rejection.
func (*RejectError) Error ¶
func (e *RejectError) Error() string
Error implements the error interface.
type RejectReason ¶
type RejectReason uint8
RejectReason classifies why a payload was rejected. internal/engine keys its per-reason rejection counters and device_error trigger events on it (docs/DESIGN.md §2.6 "failures are never silent").
const ( // ReasonNone is the zero value; never carried by an error. ReasonNone RejectReason = iota // ReasonTooLarge: payload exceeds the configured size cap. ReasonTooLarge // ReasonUnknownFormat: the payload is neither empty, BSON, nor JSON. ReasonUnknownFormat // ReasonMalformed: detected format, but not a decodable document. ReasonMalformed // ReasonNoValue: the `{v, t}` envelope carries no `v` field. ReasonNoValue // ReasonBadTimestamp: `t` missing where required, undecodable, or out // of the [MinDateTime, MaxDateTime] window. ReasonBadTimestamp // ReasonTypeMismatch: the value cannot coerce to the declared // ValueType under the docs/DESIGN.md §2.6 step 5 rules. ReasonTypeMismatch // ReasonValueTooLarge: a string exceeds MaxStringLen or an array // exceeds MaxArrayLen. ReasonValueTooLarge // ReasonBadObject: object-aggregation shape violation (not a // document, empty, or a key that resolves to no declared leaf). ReasonBadObject // ReasonUnsetNotAllowed: empty payload on a mapping without // allow_unset (datastreams never allow it). ReasonUnsetNotAllowed )
RejectReason values. The zero value means "not a rejection".
func ReasonOf ¶
func ReasonOf(err error) RejectReason
ReasonOf extracts the RejectReason from err, or ReasonNone if err is nil or not a *RejectError.
func RejectReasons ¶
func RejectReasons() []RejectReason
RejectReasons returns every reject reason, in order, so metric consumers can pre-register one counter per label.
func (RejectReason) String ¶
func (r RejectReason) String() string
String returns the stable snake_case label used for metrics and logs.
type Value ¶
type Value = any
Value is a decoded payload value. It is always one of a closed set of Go types, determined by the mapping's ValueType:
double float64 doublearray []float64 integer int32 integerarray []int32 boolean bool booleanarray []bool longinteger int64 longintegerarray []int64 string string stringarray []string binaryblob []byte binaryblobarray [][]byte datetime time.Time datetimearray []time.Time
Object-aggregated payloads decode to map[string]Value keyed by the last-level endpoint name, each entry holding one of the types above. Encode accepts exactly the same set.