{"record":{"id":"de803b86ecd75de3","repo":"pydantic/monty","slug":"pairlist-is-decode-only","errorCode":null,"errorMessage":"PairList is decode-only","messagePattern":"PairList is decode-only","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/monty-proto/src/wire.rs","lineNumber":919,"sourceCode":"\nimpl Message for PairList {\n    fn merge_field(\n        &mut self,\n        tag: u32,\n        wire_type: WireType,\n        buf: &mut impl Buf,\n        ctx: DecodeContext,\n    ) -> Result<(), DecodeError> {\n        // `Dict.pairs` is field 1; any other tag is unknown → skip.\n        if tag == 1 {\n            merge_pair_item(wire_type, buf, ctx, &mut self.0)\n        } else {\n            skip_field(wire_type, tag, buf, ctx)\n        }\n    }\n\n    fn encode_raw(&self, _buf: &mut impl BufMut) {\n        unreachable!(\"PairList is decode-only\")\n    }\n\n    fn encoded_len(&self) -> usize {\n        unreachable!(\"PairList is decode-only\")\n    }\n\n    fn clear(&mut self) {\n        self.0.clear();\n    }\n}\n\n/// Decode-only `prost::Message` for a wire `Type`, materializing `attrs`\n/// straight into a [`PairList`] (each value charged as it decodes) instead of\n/// the `Vec<pb::Pair>` the generated `pb::Type` would force. Decode-only;\n/// types encode via [`monty_type_to_pb`].\n#[derive(Default)]\nstruct TypeBody {\n    name: String,","sourceCodeStart":901,"sourceCodeEnd":937,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty-proto/src/wire.rs#L901-L937","documentation":"`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.","triggerScenarios":"Passing a `PairList` to any prost encoding routine (`encode_to_vec`, `encode_raw` invocation, or embedding it in a serialized message field).","commonSituations":"Hit during wire-protocol development: round-tripping decoded values in tests, or refactoring the encoder to share aggregate types between directions.","solutions":["Encode pair data from the owning `MontyObject`/`WireObject`, never from the decoded `PairList`","Implement real encoding in `PairList` if bidirectional use is ever required","Ensure no serialized message field holds a `PairList` value on the encode side","Run `cargo test -p monty-proto` (differential oracle tests) to validate"],"exampleFix":"// before\npair_list.encode_to_vec() // panics: decode-only\n// after\nWireObject::from(&monty_object).encode_to_vec()","handlingStrategy":"type-guard","validationCode":"// Encode pair data via the owning object, never the decoded list\nlet bytes = WireObject::from(&monty_object).encode_to_vec();","typeGuard":"fn is_decode_only_aggregate(msg: &dyn std::any::Any) -> bool { msg.is::<PairList>() }","tryCatchPattern":"// Avoid statically; convert decoded aggregates back to MontyObject/WireObject before encoding","preventionTips":["Treat `PairList` as a decoding intermediate only — convert immediately to `MontyObject`","Never store `PairList` in encoded message structs","Document the decode-only contract on the type","Run `cargo test -p monty-proto` differential tests after wire changes"],"tags":["rust","protobuf","wire-protocol","internal-assert"],"backgroundTag":"internal-invariant-violation","analyzedSha":"adc986b362e3961f407868cb118a99fe831b9e61","analyzedAt":"2026-09-13T19:19:18.698Z","contentChangedAt":"2026-09-13T19:19:18.698Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}