{"record":{"id":"b481b0ad39930861","repo":"pydantic/monty","slug":"objectlist-is-decode-only","errorCode":null,"errorMessage":"ObjectList is decode-only","messagePattern":"ObjectList is decode-only","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/monty-proto/src/wire.rs","lineNumber":884,"sourceCode":"\nimpl Message for ObjectList {\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        // `ObjectList.items` is field 1; any other tag is unknown → skip.\n        if tag == 1 {\n            merge_object_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!(\"ObjectList is decode-only\")\n    }\n\n    fn encoded_len(&self) -> usize {\n        unreachable!(\"ObjectList is decode-only\")\n    }\n\n    fn clear(&mut self) {\n        self.0.clear();\n    }\n}\n\n/// Decode-only `prost::Message` that materializes a `repeated Pair` field\n/// directly into `(key, value)` tuples — the dict analogue of [`ObjectList`],\n/// avoiding the `Vec<pb::Pair>` wrapper. Decode-only; encode is unreachable.\n#[derive(Default)]\nstruct PairList(Vec<(MontyObject, MontyObject)>);\n\nimpl Message for PairList {","sourceCodeStart":866,"sourceCodeEnd":902,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty-proto/src/wire.rs#L866-L902","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Never encode from `ObjectList`; re-encode from the original `MontyObject`/`WireObject` that the list was decoded from","If a type must support both directions, implement real `encode_raw`/`encoded_len` instead of the decode-only stub","Convert any test or code path holding an `ObjectList` back into `MontyObject` before encoding","Run `make check-proto` and `cargo test -p monty-proto` (differential tests) after protocol changes"],"exampleFix":"// before\nlet bytes = ObjectList::from(items).encode_to_vec(); // panics: decode-only\n// after\nlet wire_obj = WireObject::from(&monty_object);\nlet bytes = wire_obj.encode_to_vec();","handlingStrategy":"type-guard","validationCode":"fn ensure_encodable<T: prost::Message + Encodable>(_: &T) {} // only pass WireObject/MontyObject-backed types to encoders","typeGuard":"fn is_decode_only_aggregate(msg: &dyn std::any::Any) -> bool {\n    msg.is::<ObjectList>() || msg.is::<PairList>() || msg.is::<TypeBody>() || msg.is::<NamedTupleBody>()\n}","tryCatchPattern":"// Prefer static avoidance: never call encode on decode-only types; if generic code must encode,\n// convert to the canonical encodable type first\nlet wire = WireObject::from(&monty_object);\nwire.encode_to_vec()","preventionTips":["Encode only from `WireObject` (or borrowed `MontyObject`) in parent→child messages","Keep decode-only aggregates out of any field of serialized messages","Add a comment on the type (`/// decode-only — encode from WireObject`) and enforce via type boundaries","Run `make check-proto` and the differential tests when touching the wire layer"],"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"}