pydantic/monty · error

ObjectList is decode-only

Error message

ObjectList is decode-only

What it means

`ObjectList` in `crates/monty-proto/src/wire.rs` is a hand-written `prost::Message` that exists only for decoding recursive nested lists (values are validated while decoding and mapped straight into `MontyObject`s). Its `encode_raw` is therefore never called: any object list travelling parent→child is encoded from the borrowed `MontyObject` itself, not from this type. The `unreachable!()` fires only if a decode-only wire type is accidentally used as an encode source — an internal `monty-proto` bug.

Source

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

impl Message for ObjectList {
    fn merge_field(
        &mut self,
        tag: u32,
        wire_type: WireType,
        buf: &mut impl Buf,
        ctx: DecodeContext,
    ) -> Result<(), DecodeError> {
        // `ObjectList.items` is field 1; any other tag is unknown → skip.
        if tag == 1 {
            merge_object_item(wire_type, buf, ctx, &mut self.0)
        } else {
            skip_field(wire_type, tag, buf, ctx)
        }
    }

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

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

    fn clear(&mut self) {
        self.0.clear();
    }
}

/// Decode-only `prost::Message` that materializes a `repeated Pair` field
/// directly into `(key, value)` tuples — the dict analogue of [`ObjectList`],
/// avoiding the `Vec<pb::Pair>` wrapper. Decode-only; encode is unreachable.
#[derive(Default)]
struct PairList(Vec<(MontyObject, MontyObject)>);

impl Message for PairList {

View on GitHub (pinned to adc986b362)

Solutions

  1. Never encode from `ObjectList`; re-encode from the original `MontyObject`/`WireObject` that the list was decoded from
  2. If a type must support both directions, implement real `encode_raw`/`encoded_len` instead of the decode-only stub
  3. Convert any test or code path holding an `ObjectList` back into `MontyObject` before encoding
  4. Run `make check-proto` and `cargo test -p monty-proto` (differential tests) after protocol changes

Example fix

// before
let bytes = ObjectList::from(items).encode_to_vec(); // panics: decode-only
// after
let wire_obj = WireObject::from(&monty_object);
let bytes = wire_obj.encode_to_vec();
Defensive patterns

Strategy: type-guard

Validate before calling

fn ensure_encodable<T: prost::Message + Encodable>(_: &T) {} // only pass WireObject/MontyObject-backed types to encoders

Type guard

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

Try / catch

// Prefer static avoidance: never call encode on decode-only types; if generic code must encode,
// convert to the canonical encodable type first
let wire = WireObject::from(&monty_object);
wire.encode_to_vec()

Prevention

When it happens

Trigger: Calling `encode_raw` (directly or via `Message::encode`/`encode_length_delimited`) on an `ObjectList` value, e.g. by routing decoded nested values back through the wire encoder in error paths or tests.

Common situations: Seen while developing the wire protocol: constructing differential-test messages from decoded values, or changing the encoder to reuse the aggregate types for both directions.

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/b481b0ad39930861. Report an issue: GitHub.