Wado

core:serde

Serialization and deserialization framework.

Provides format-agnostic Serialize and Deserialize traits with a 17-method data model optimized for JSON and CBOR. Format implementations (e.g., core:json) implement Serializer and Deserializer to support specific wire formats.

use { Serialize, Deserialize, SerializeError, DeserializeError } from "core:serde";

Deriving Serialize / Deserialize

impl Serialize for T; / impl Deserialize for T; (empty body — the compiler fills it in) is optional: a T: Serialize bound is satisfied structurally once every field or case of T implements the trait — how an anonymous struct, which has no name for a marker, becomes serializable. Write the marker to force the impl with no bound present, or alongside #[wire(...)] customization.

struct Point { x: i32, y: i32 }               // no marker needed
let json = to_string(&Point { x: 1, y: 2 });  // Ok("{\"x\":1,\"y\":2}")

Field naming on the wire

By default the wire-form key is the Wado source field name verbatim (identity): user_name stays "user_name", userId stays "userId". The field name as written is the single source of truth. Override per struct with #[wire(name_policy = "...")], or per field with #[wire(name = "...")] (which takes precedence).

name_policy strategies: "camelCase", "snake_case", "PascalCase", "SCREAMING_SNAKE_CASE", "kebab-case", "SCREAMING-KEBAB-CASE".

The same name / name_policy overrides apply to enum and variant case names (default: the PascalCase name verbatim), e.g. #[wire(name_policy = "kebab-case")] makes AddRemote serialize as "add-remote".

// Default — wire keys are the field names as written
struct User {
    user_name: String,        // wire key: "user_name"
    account_id: i64,          // wire key: "account_id"
}

// Per-struct convention (e.g. camelCase for a JS-facing API)
#[wire(name_policy = "camelCase")]
struct Event {
    created_at: String,       // wire key: "createdAt"
    event_type: String,       // wire key: "eventType"
}

// Per-field override (wins over name_policy)
#[wire(name_policy = "kebab-case")]
struct Header {
    content_type: String,     // wire key: "content-type"
    #[wire(name = "X-Trace-Id")]
    trace_id: String,         // wire key: "X-Trace-Id"
}

Missing fields on deserialization

A missing required field produces DeserializeError with kind MissingField. A field with a default value (f: T = expr) is optional: when absent it falls back to that expression. This is the single mechanism for an optional field — for a zero-value fallback, write the zero literal (= 0, = "", = false, = [], = null).

struct Config {
    host: String,             // required - error if missing
    port: i32 = 0,            // missing -> 0
    timeout: i32 = 30,        // missing -> 30
}
impl Deserialize for Config;

// {"host":"localhost"}
//   -> Config { host: "localhost", port: 0, timeout: 30 }

Globals

pub global DEFAULT_MAX_DEPTH: i32

Default nesting limit. Unbounded nesting exhausts the stack and traps, which is unrecoverable unlike an error.

Functions

pub fn report_duplicate_field<K: Inspect>(policy: DuplicateFieldPolicy, key: &K) -> Result<(), DeserializeError>

Applies policy to a key the wire already wrote. Detecting the repeat is the caller's job: a struct sees a filled slot, a map asks try_insert, a set asks insert, whose element is its key.

pub fn deeper(depth: i32, max_depth: i32, offset: i64) -> Result<i32, DeserializeError>

One nesting level deeper, or DepthLimitExceeded at the limit. Threaded as a value, so there is nothing to decrement and a format cannot leave the count unbalanced; each picks its own max_depth and container boundaries.

pub fn apply_case<S: AsStrSlice>(style: CaseStyle, s: S) -> String

Apply a CaseStyle to an identifier. Identity returns s unchanged.

Word boundaries follow heck: _/-/space delimit and are dropped; a lower/digit → upper transition starts a word; inside an uppercase run, an uppercase directly before a lowercase starts one. Digits attach to the current word.

pub fn wire_name<M: Member>(m: &M, policy: CaseStyle) -> String

Resolve a member's wire name: its #[wire(name)] override wins, else the type's name_policy applies, else identity.

Traits

pub trait SerializeSeq with ()

fn element<T: Serialize>(&mut self, value: &T) -> Result<(), SerializeError>

#[compiler_item("serialize_seq_element")]

fn end(&mut self) -> Result<(), SerializeError>

#[compiler_item("serialize_seq_end")]

pub trait SerializeMap with ()

fn key<T: Serialize>(&mut self, key: &T) -> Result<(), SerializeError>

fn value<T: Serialize>(&mut self, value: &T) -> Result<(), SerializeError>

fn end(&mut self) -> Result<(), SerializeError>

pub trait SerializeStruct with ()

fn field<T: Serialize, S: AsStrSlice>(&mut self, name: S, value: &T) -> Result<(), SerializeError>

#[compiler_item("serialize_struct_field")]

fn end(&mut self) -> Result<(), SerializeError>

#[compiler_item("serialize_struct_end")]

pub trait SerializeVariant with ()

fn payload<T: Serialize>(&mut self, value: &T) -> Result<(), SerializeError>

#[compiler_item("serialize_variant_payload")]

fn end(&mut self) -> Result<(), SerializeError>

#[compiler_item("serialize_variant_end")]

pub trait Serializer with ()

fn serialize_i32(&mut self, v: i32) -> Result<(), SerializeError>

fn serialize_i64(&mut self, v: i64) -> Result<(), SerializeError>

fn serialize_u32(&mut self, v: u32) -> Result<(), SerializeError>

fn serialize_u64(&mut self, v: u64) -> Result<(), SerializeError>

fn serialize_i128(&mut self, v: i128) -> Result<(), SerializeError>

fn serialize_u128(&mut self, v: u128) -> Result<(), SerializeError>

fn serialize_f32(&mut self, v: f32) -> Result<(), SerializeError>

fn serialize_f64(&mut self, v: f64) -> Result<(), SerializeError>

fn serialize_bool(&mut self, v: bool) -> Result<(), SerializeError>

fn serialize_char(&mut self, v: char) -> Result<(), SerializeError>

fn serialize_string<S: AsStrSlice>(&mut self, v: S) -> Result<(), SerializeError>

fn is_human_readable(&self) -> bool

Whether the output is meant to be read by people. Binary formats override it to false so a value can pick a compact representation.

fn serialize_tag(&mut self, _tag: u64) -> Result<(), SerializeError>

Tag the next value with a CBOR semantic tag (major type 6, RFC 8949 §3.4). Formats with no tag concept ignore it; the default no-op means only tag-aware serializers (CBOR) need to implement it.

fn serialize_bytes(&mut self, v: ByteSlice) -> Result<(), SerializeError>

Serialize a byte string. Self-describing binary formats (CBOR) emit a native byte string; JSON emits base64. The default falls back to a sequence of u8 (the historical List<u8> behaviour), so a format with no distinct byte-string form keeps working unchanged.

fn serialize_null(&mut self) -> Result<(), SerializeError>

fn begin_seq(&mut self, len: i32) -> Result<Self::SeqSerializer, SerializeError>

#[compiler_item("serializer_begin_seq")]

fn begin_map(&mut self, len: i32) -> Result<Self::MapSerializer, SerializeError>

fn begin_struct<S: AsStrSlice>(&mut self, name: S, fields: i32) -> Result<Self::StructSerializer, SerializeError>

#[compiler_item("serializer_begin_struct")]

fn serialize_unit_variant<S: AsStrSlice, S1: AsStrSlice>(&mut self, type_name: S, variant_name: S1, disc: i32) -> Result<(), SerializeError>

#[compiler_item("serializer_serialize_unit_variant")]

fn begin_variant<S: AsStrSlice, S1: AsStrSlice>(&mut self, type_name: S, variant_name: S1, disc: i32) -> Result<Self::VariantSerializer, SerializeError>

#[compiler_item("serializer_begin_variant")]

pub trait Serialize with ()

fn serialize<S: Serializer>(&self, s: &mut S) -> Result<(), SerializeError>

pub trait DeserializeSeq with ()

fn next_element<T: Deserialize>(&mut self) -> Result<Option<T>, DeserializeError>

#[compiler_item("deserialize_seq_next_element")]

fn end(&mut self) -> Result<(), DeserializeError>

#[compiler_item("deserialize_seq_end")]

pub trait DeserializeMap with ()

fn next_key<K: Deserialize>(&mut self) -> Result<Option<K>, DeserializeError>

The next entry's key, or null past the last one. A text format spells every key as a string, so a key there is a scalar parsed back as K.

fn next_value<V: Deserialize>(&mut self) -> Result<V, DeserializeError>

fn end(&mut self) -> Result<(), DeserializeError>

pub trait FieldSchema with ()

Static, per-type, format-agnostic field-name → index mapping.

The struct-deserialize synthesiser implements FieldSchema for every deserializable struct. Its lookup walks a decision tree over the key's bytes: the length at the root, then the byte that best tells the remaining field names apart. A key costs one walk down that tree, not one test per declared field. Self-describing formats invoke it from DeserializeStruct::next_field with the key's bytes; non-self-describing formats ignore the schema and stay positional.

The key is a byte view (Slice<u8>, i.e. ByteSlice), not a String: deserializer input is bytes (UTF-8 for JSON, a byte string for CBOR), so a bytes key avoids a String round-trip and is shared identically by every format.

lookup is a static method resolved at monomorphization, so the call is direct and inlinable — no closure value, no per-decode allocation.

A field marked #[wire(positional)] is ordinal, not nominal: lookup omits it (it is never matched by name) and positional_at enumerates the positional fields in declaration order. Name-only and sequence-only formats ignore positional_at and stay backward compatible; core:args consults it to bind bare tokens to positional fields.

fn lookup(key: ByteSlice) -> Option<i32>

fn positional_at(rank: i32) -> Option<i32>

Field index of the rank-th #[wire(positional)] field (in declaration order), or null when rank is out of range. Returns null for every rank when the type has no positional fields.

pub trait DeserializeStruct with ()

fn next_field<S: FieldSchema>(&mut self) -> Result<Option<i32>, DeserializeError>

#[compiler_item("deserialize_struct_next_field")]

fn value<T: Deserialize>(&mut self) -> Result<T, DeserializeError>

#[compiler_item("deserialize_struct_value")]

fn skip(&mut self) -> Result<(), DeserializeError>

#[compiler_item("deserialize_struct_skip")]

fn end(&mut self) -> Result<(), DeserializeError>

#[compiler_item("deserialize_struct_end")]

pub trait DeserializeVariant with ()

fn variant_name(&mut self) -> Result<String, DeserializeError>

#[compiler_item("deserialize_variant_variant_name")]

fn disc(&mut self) -> Result<i32, DeserializeError>

#[compiler_item("deserialize_variant_disc")]

fn payload<T: Deserialize>(&mut self) -> Result<T, DeserializeError>

#[compiler_item("deserialize_variant_payload")]

fn is_unit(&mut self) -> Result<bool, DeserializeError>

fn end(&mut self) -> Result<(), DeserializeError>

#[compiler_item("deserialize_variant_end")]

pub trait Visitor with ()

fn visit_null(&mut self) -> Result<Self::Value, DeserializeError>

fn visit_bool(&mut self, v: bool) -> Result<Self::Value, DeserializeError>

fn visit_f64(&mut self, v: f64) -> Result<Self::Value, DeserializeError>

fn visit_i64(&mut self, v: i64) -> Result<Self::Value, DeserializeError>

Integer visits. Self-describing binary formats (CBOR) call these to preserve full integer precision; the defaults widen to f64 so a visitor that only cares about JSON-style numbers keeps working.

fn visit_u64(&mut self, v: u64) -> Result<Self::Value, DeserializeError>

fn visit_i128(&mut self, v: i128) -> Result<Self::Value, DeserializeError>

fn visit_u128(&mut self, v: u128) -> Result<Self::Value, DeserializeError>

fn visit_string<S: AsStrSlice>(&mut self, v: S) -> Result<Self::Value, DeserializeError>

fn visit_bytes(&mut self, _v: ByteList) -> Result<Self::Value, DeserializeError>

Byte-string visit (CBOR major type 2; JSON base64). Defaults to an UnexpectedType error so text-only visitors are unaffected.

fn visit_seq<A: DeserializeSeq>(&mut self, seq: &mut A) -> Result<Self::Value, DeserializeError>

fn visit_map<A: DeserializeMap>(&mut self, map: &mut A) -> Result<Self::Value, DeserializeError>

fn visit_unknown(&mut self, _raw: ByteList) -> Result<Self::Value, DeserializeError>

Opaque passthrough for a format item the value model cannot represent (e.g. an unassigned CBOR simple value or an uninterpreted tag). Only self-describing binary formats call this; defaults to an error.

fn visit_undefined(&mut self) -> Result<Self::Value, DeserializeError>

The CBOR undefined simple value (major 7, value 23). Distinct from null: a dynamic value model (core:value) keeps its own Undefined arm. The default collapses it to null so visitors that do not model the distinction (e.g. JSON) keep working.

pub trait Deserializer with ()

fn deserialize_i32(&mut self) -> Result<i32, DeserializeError>

fn deserialize_i64(&mut self) -> Result<i64, DeserializeError>

fn deserialize_u32(&mut self) -> Result<u32, DeserializeError>

fn deserialize_u64(&mut self) -> Result<u64, DeserializeError>

fn deserialize_i128(&mut self) -> Result<i128, DeserializeError>

fn deserialize_u128(&mut self) -> Result<u128, DeserializeError>

fn deserialize_f32(&mut self) -> Result<f32, DeserializeError>

fn deserialize_f64(&mut self) -> Result<f64, DeserializeError>

fn deserialize_f16(&mut self) -> Result<f16, DeserializeError>

Rounds what the input spells once, to nearest, into a half. Reading an f32 and narrowing it would round twice.

fn deserialize_bf16(&mut self) -> Result<bf16, DeserializeError>

deserialize_f16, into a bf16.

fn deserialize_bool(&mut self) -> Result<bool, DeserializeError>

fn deserialize_char(&mut self) -> Result<char, DeserializeError>

fn deserialize_string(&mut self) -> Result<String, DeserializeError>

fn deserialize_bytes(&mut self) -> Result<ByteList, DeserializeError>

Deserialize a byte string. Self-describing binary formats read a native byte string; JSON reads base64. The default reads a sequence of u8.

fn is_null(&mut self) -> Result<bool, DeserializeError>

fn on_duplicate_field(&self) -> DuplicateFieldPolicy

What this format does when the wire repeats a field or key.

fn begin_seq(&mut self) -> Result<Self::SeqAccess, DeserializeError>

#[compiler_item("deserializer_begin_seq")]

fn begin_map(&mut self) -> Result<Self::MapAccess, DeserializeError>

fn begin_struct<S: AsStrSlice>(&mut self, name: S, num_fields: i32) -> Result<Self::StructAccess, DeserializeError>

#[compiler_item("deserializer_begin_struct")]

fn begin_variant<S: AsStrSlice>(&mut self, type_name: S, num_cases: i32) -> Result<Self::VariantAccess, DeserializeError>

#[compiler_item("deserializer_begin_variant")]

fn deserialize_any<V: Visitor>(&mut self, visitor: &mut V) -> Result<V::Value, DeserializeError>

pub trait Deserialize with ()

fn deserialize<D: Deserializer>(d: &mut D) -> Result<Self, DeserializeError>

fn deserialize_next_element_from_seq<S: DeserializeSeq>(seq: &mut S) -> Result<Self, DeserializeError>

Deserialize the next element from a sequence access. Used by variadic tuple deserialization via type pack expansion.

Structs

pub struct SerializeError

kind: SerializeErrorKind

message: String

pub fn unsupported_value<S: AsStrSlice>(msg: S) -> SerializeError

A value the format has no way to write.

pub fn non_scalar_key() -> SerializeError

A map key whose type is not a scalar, which a text format has no one string to spell as.

impl Display for SerializeError

fn fmt(&self, f: &mut Formatter)

pub struct DeserializeError

kind: DeserializeErrorKind

message: String

offset: i64

Byte offset into the input, or -1 where the failure has no position — a whole-value rejection, or a format that counts nothing.

pub fn unexpected_type<S: AsStrSlice>(msg: S, offset: i64 = -1) -> DeserializeError

pub fn missing_field<S: AsStrSlice>(name: S) -> DeserializeError

pub fn invalid_value<S: AsStrSlice>(msg: S, offset: i64 = -1) -> DeserializeError

pub fn malformed<S: AsStrSlice>(msg: S, offset: i64 = -1) -> DeserializeError

pub fn eof(offset: i64) -> DeserializeError

pub fn trailing(offset: i64) -> DeserializeError

pub fn overflow<S: AsStrSlice>(msg: S, offset: i64 = -1) -> DeserializeError

pub fn depth_limit<S: AsStrSlice>(msg: S, offset: i64 = -1) -> DeserializeError

pub fn duplicate_field<K: Inspect>(key: &K) -> DeserializeError

A repeated field or key, named as Inspect spells it: a string key is quoted, so it reads apart from a number.

pub fn non_scalar_key(offset: i64) -> DeserializeError

SerializeError::non_scalar_key, for a key being read.

impl Display for DeserializeError

fn fmt(&self, f: &mut Formatter)

pub struct IgnoredAny

A value of any shape, read and thrown away.

It is how a schema requires a member's absence: an Option<T> field cannot, because it reads an explicit null as the member never sent.

impl Deserialize for IgnoredAny

fn deserialize<D: Deserializer>(d: &mut D) -> Result<IgnoredAny, DeserializeError>

pub struct NoCompound

The compound access of a serializer or deserializer that has none, such as a map key's. The private field keeps it unbuilt outside this module, so the begin_* that would hand one out always fails first.

Fields are private.

impl SerializeSeq for NoCompound

fn element<T: Serialize>(&mut self, value: &T) -> Result<(), SerializeError>
fn end(&mut self) -> Result<(), SerializeError>

impl SerializeMap for NoCompound

fn key<T: Serialize>(&mut self, key: &T) -> Result<(), SerializeError>
fn value<T: Serialize>(&mut self, value: &T) -> Result<(), SerializeError>
fn end(&mut self) -> Result<(), SerializeError>

impl SerializeStruct for NoCompound

fn field<T: Serialize, S: AsStrSlice>(&mut self, name: S, value: &T) -> Result<(), SerializeError>
fn end(&mut self) -> Result<(), SerializeError>

impl SerializeVariant for NoCompound

fn payload<T: Serialize>(&mut self, value: &T) -> Result<(), SerializeError>
fn end(&mut self) -> Result<(), SerializeError>

impl DeserializeSeq for NoCompound

fn next_element<T: Deserialize>(&mut self) -> Result<Option<T>, DeserializeError>
fn end(&mut self) -> Result<(), DeserializeError>

impl DeserializeMap for NoCompound

fn next_key<K: Deserialize>(&mut self) -> Result<Option<K>, DeserializeError>
fn next_value<V: Deserialize>(&mut self) -> Result<V, DeserializeError>
fn end(&mut self) -> Result<(), DeserializeError>

impl DeserializeStruct for NoCompound

fn next_field<S: FieldSchema>(&mut self) -> Result<Option<i32>, DeserializeError>
fn value<T: Deserialize>(&mut self) -> Result<T, DeserializeError>
fn skip(&mut self) -> Result<(), DeserializeError>
fn end(&mut self) -> Result<(), DeserializeError>

impl DeserializeVariant for NoCompound

fn variant_name(&mut self) -> Result<String, DeserializeError>
fn disc(&mut self) -> Result<i32, DeserializeError>
fn payload<T: Deserialize>(&mut self) -> Result<T, DeserializeError>
fn is_unit(&mut self) -> Result<bool, DeserializeError>
fn end(&mut self) -> Result<(), DeserializeError>

pub struct TextKeySerializer

Spells a map key as the text TextKeyDeserializer reads back: a string as itself, any other scalar as its Display.

out: &mut String

impl Serializer for TextKeySerializer

fn serialize_i32(&mut self, v: i32) -> Result<(), SerializeError>
fn serialize_i64(&mut self, v: i64) -> Result<(), SerializeError>
fn serialize_u32(&mut self, v: u32) -> Result<(), SerializeError>
fn serialize_u64(&mut self, v: u64) -> Result<(), SerializeError>
fn serialize_i128(&mut self, v: i128) -> Result<(), SerializeError>
fn serialize_u128(&mut self, v: u128) -> Result<(), SerializeError>
fn serialize_f32(&mut self, v: f32) -> Result<(), SerializeError>
fn serialize_f64(&mut self, v: f64) -> Result<(), SerializeError>
fn serialize_bool(&mut self, v: bool) -> Result<(), SerializeError>
fn serialize_char(&mut self, v: char) -> Result<(), SerializeError>
fn serialize_string<S: AsStrSlice>(&mut self, v: S) -> Result<(), SerializeError>
fn serialize_null(&mut self) -> Result<(), SerializeError>
fn begin_seq(&mut self, len: i32) -> Result<NoCompound, SerializeError>
fn begin_map(&mut self, len: i32) -> Result<NoCompound, SerializeError>
fn begin_struct<S: AsStrSlice>(&mut self, name: S, fields: i32) -> Result<NoCompound, SerializeError>
fn serialize_unit_variant<S: AsStrSlice, S1: AsStrSlice>(&mut self, type_name: S, variant_name: S1, disc: i32) -> Result<(), SerializeError>
fn begin_variant<S: AsStrSlice, S1: AsStrSlice>(&mut self, type_name: S, variant_name: S1, disc: i32) -> Result<NoCompound, SerializeError>

pub struct TextKeyDeserializer

Reads a map key a text format spelled as a string, parsed as the scalar the key type asks for.

key: String

offset: i64

Where the key starts in the input, or -1 where it has no position.

impl Deserializer for TextKeyDeserializer

fn deserialize_i32(&mut self) -> Result<i32, DeserializeError>
fn deserialize_i64(&mut self) -> Result<i64, DeserializeError>
fn deserialize_u32(&mut self) -> Result<u32, DeserializeError>
fn deserialize_u64(&mut self) -> Result<u64, DeserializeError>
fn deserialize_i128(&mut self) -> Result<i128, DeserializeError>
fn deserialize_u128(&mut self) -> Result<u128, DeserializeError>
fn deserialize_f32(&mut self) -> Result<f32, DeserializeError>
fn deserialize_f64(&mut self) -> Result<f64, DeserializeError>
fn deserialize_f16(&mut self) -> Result<f16, DeserializeError>
fn deserialize_bf16(&mut self) -> Result<bf16, DeserializeError>
fn deserialize_bool(&mut self) -> Result<bool, DeserializeError>
fn deserialize_char(&mut self) -> Result<char, DeserializeError>
fn deserialize_string(&mut self) -> Result<String, DeserializeError>
fn is_null(&mut self) -> Result<bool, DeserializeError>
fn begin_seq(&mut self) -> Result<NoCompound, DeserializeError>
fn begin_map(&mut self) -> Result<NoCompound, DeserializeError>
fn begin_struct<S: AsStrSlice>(&mut self, name: S, num_fields: i32) -> Result<NoCompound, DeserializeError>
fn begin_variant<S: AsStrSlice>(&mut self, type_name: S, num_cases: i32) -> Result<NoCompound, DeserializeError>
fn deserialize_any<V: Visitor>(&mut self, visitor: &mut V) -> Result<V::Value, DeserializeError>

Enums

pub enum SerializeErrorKind

UnsupportedValue

Custom

pub enum DeserializeErrorKind

UnexpectedType

MissingField

UnknownVariant

DuplicateField

InvalidValue

Overflow

MalformedInput

TrailingData

Eof

Custom

DepthLimitExceeded

Nesting ran past the deserializer's depth limit. A resource bound, not a spec violation: the input may be perfectly well-formed.

pub enum DuplicateFieldPolicy

What to do when the wire repeats a field or key. A repeat is how two readers of the same bytes are made to disagree, so rejecting is the default. Warn and PassThru both keep the last value, and a map key or set element as first read.

Error

Warn

PassThru