pydantic/monty · error

NamedTupleBody is decode-only

Error message

NamedTupleBody is decode-only

What it means

`NamedTupleBody` (decoded wire form of named-tuple fields/values inside nested `MontyObject`s) follows the same decode-only pattern in `crates/monty-proto/src/wire.rs`: `encode_raw` and `encoded_len` trap with `unreachable!("NamedTupleBody is decode-only")`. Named tuples are merged during decoding (`merge_object_item` into `self.values`) and converted to `MontyObject`s, never re-serialized. Firing signals internal misuse of a decode-only aggregate in `monty-proto`.

Source

Thrown at crates/monty-proto/src/wire.rs:1010

impl Message for NamedTupleBody {
    fn merge_field(
        &mut self,
        tag: u32,
        wire_type: WireType,
        buf: &mut impl Buf,
        ctx: DecodeContext,
    ) -> Result<(), DecodeError> {
        // Field numbers from `NamedTuple` in monty.proto; unknown → skip.
        match tag {
            1 => encoding::string::merge(wire_type, &mut self.type_name, buf, ctx),
            2 => encoding::string::merge_repeated(wire_type, &mut self.field_names, buf, ctx),
            3 => merge_object_item(wire_type, buf, ctx, &mut self.values),
            _ => skip_field(wire_type, tag, buf, ctx),
        }
    }

    fn encode_raw(&self, _buf: &mut impl BufMut) {
        unreachable!("NamedTupleBody is decode-only")
    }

    fn encoded_len(&self) -> usize {
        unreachable!("NamedTupleBody is decode-only")
    }

    fn clear(&mut self) {
        self.type_name.clear();
        self.field_names.clear();
        self.values.clear();
    }
}

/// Decode-only `prost::Message` for `ClassInstance`, decoding the `attrs`
/// field (a `Dict`) straight into [`DictPairs`] via [`PairList`] rather
/// than the `Vec<pb::Pair>` wrapper the generated `pb::ClassInstance` would
/// build and then unwrap. The message fields stay `Option` so an absent one
/// is rejected by the caller (presence, not a default). Decode-only.

View on GitHub (pinned to adc986b362)

Solutions

  1. Re-encode from the originating `MontyObject`/`WireObject`, never from the decoded `NamedTupleBody`
  2. Implement genuine `encode_raw`/`encoded_len` if bidirectional serialization is ever needed
  3. Ensure no encode-side message struct contains a `NamedTupleBody` field
  4. Run `make check-proto` and `cargo test -p monty-proto` differential tests

Example fix

// before
named_tuple_body.encode_to_vec() // panics: decode-only
// after
WireObject::from(&monty_object).encode_to_vec()
Defensive patterns

Strategy: type-guard

Validate before calling

// Convert decoded NamedTupleBody into MontyObject immediately after merge
let obj: MontyObject = named_tuple_body.into();

Type guard

fn is_decode_only_aggregate(msg: &dyn std::any::Any) -> bool { msg.is::<NamedTupleBody>() }

Try / catch

// Avoid statically; re-encode from WireObject, not NamedTupleBody

Prevention

When it happens

Trigger: Passing a `NamedTupleBody` to prost encoding (`encode_to_vec`, frame-size computation) or embedding it in a serialized message field.

Common situations: Seen while extending wire value representation (e.g. adding named-tuple support to more message kinds) or in differential tests that re-encode decoded nested values.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13). Data as JSON: /api/errors/f38f0de1fa8db254. Report an issue: GitHub.