pydantic/monty · error

PairList is decode-only

Error message

PairList is decode-only

What it means

`PairList` (used to decode recursive lists of key/value pairs, e.g. dict and attribute items in nested MontyObject values) is decode-only in `monty-proto`'s hand-written wire layer; its `encode_raw` traps with `unreachable!("PairList is decode-only")`. Pair lists are produced by validation-while-decoding and converted directly into `MontyObject` structures, so they are never re-encoded. Firing indicates `monty-proto` internal misuse of a decode-only type.

Source

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

impl Message for PairList {
    fn merge_field(
        &mut self,
        tag: u32,
        wire_type: WireType,
        buf: &mut impl Buf,
        ctx: DecodeContext,
    ) -> Result<(), DecodeError> {
        // `Dict.pairs` is field 1; any other tag is unknown → skip.
        if tag == 1 {
            merge_pair_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!("PairList is decode-only")
    }

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

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

/// Decode-only `prost::Message` for a wire `Type`, materializing `attrs`
/// straight into a [`PairList`] (each value charged as it decodes) instead of
/// the `Vec<pb::Pair>` the generated `pb::Type` would force. Decode-only;
/// types encode via [`monty_type_to_pb`].
#[derive(Default)]
struct TypeBody {
    name: String,

View on GitHub (pinned to adc986b362)

Solutions

  1. Encode pair data from the owning `MontyObject`/`WireObject`, never from the decoded `PairList`
  2. Implement real encoding in `PairList` if bidirectional use is ever required
  3. Ensure no serialized message field holds a `PairList` value on the encode side
  4. Run `cargo test -p monty-proto` (differential oracle tests) to validate

Example fix

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

Strategy: type-guard

Validate before calling

// Encode pair data via the owning object, never the decoded list
let bytes = WireObject::from(&monty_object).encode_to_vec();

Type guard

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

Try / catch

// Avoid statically; convert decoded aggregates back to MontyObject/WireObject before encoding

Prevention

When it happens

Trigger: Passing a `PairList` to any prost encoding routine (`encode_to_vec`, `encode_raw` invocation, or embedding it in a serialized message field).

Common situations: Hit during wire-protocol development: round-tripping decoded values in tests, or refactoring the encoder to share aggregate types between 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/de803b86ecd75de3. Report an issue: GitHub.