payload

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 1, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

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

View Source
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

View Source
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

func Encode(v Value, ts *time.Time, f Format) ([]byte, error)

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

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

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

func DetectFormat(p []byte) Format

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.

func (Format) String

func (f Format) String() string

String returns a stable lowercase label (also used as a metrics label).

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.

Jump to

Keyboard shortcuts

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