pallas_codec/lib.rs
1//! The encoding foundation the rest of the Pallas workspace builds on.
2//!
3//! Provides [`minicbor`] for CBOR (re-exported as-is) and a Rust port of the
4//! Plutus Core [flat] format. Most users won't depend on this crate directly
5//! — they'll get its types transitively through `pallas-primitives`,
6//! `pallas-traverse`, `pallas-txbuilder`, and so on. Reach for it when you
7//! need to define your own minicbor-encoded type, or when you need the
8//! round-trip helpers ([`utils::KeepRaw`], [`utils::Nullable`],
9//! [`utils::Set`], …) used by the higher-level era types.
10//!
11//! [flat]: https://github.com/Quid2/flat
12//!
13//! # Usage
14//!
15//! ```
16//! use pallas_codec::minicbor;
17//!
18//! #[derive(minicbor::Encode, minicbor::Decode, Debug, PartialEq)]
19//! struct Pair(#[n(0)] u64, #[n(1)] String);
20//!
21//! let bytes = minicbor::to_vec(Pair(1, "hi".into()))?;
22//! let back: Pair = minicbor::decode(&bytes)?;
23//! assert_eq!(back, Pair(1, "hi".into()));
24//! # Ok::<_, Box<dyn std::error::Error>>(())
25//! ```
26//!
27//! # Overview
28//!
29//! - [`minicbor`] — re-exported as-is; this is the workspace's single source
30//! of truth for CBOR.
31//! - [`flat`] — Rust port of the Haskell [flat] reference implementation,
32//! used for Plutus Core scripts.
33//! - [`tree`] — stack-safe decoding of recursive types via [`tree::TreeDecode`]
34//! and [`tree::decode_tree`].
35//! - [`utils`] — round-trip-friendly helper types ([`utils::KeepRaw`],
36//! [`utils::KeyValuePairs`], [`utils::MaybeIndefArray`],
37//! [`utils::NonEmptySet`], [`utils::Nullable`], [`utils::PositiveCoin`],
38//! …) reused by the higher-level era types.
39//! - [`Fragment`] trait — blanket-implemented for any type that is both
40//! [`minicbor::Encode`] and [`minicbor::Decode`]; used as a bound where
41//! the workspace wants "any CBOR-roundtrippable type".
42//! - [`codec_by_datatype!`] macro — derives a tag-free CBOR codec for enums
43//! whose variants are distinguished by their data-type rather than a
44//! discriminant.
45//!
46//! # Usage as part of `pallas`
47//!
48//! When depending on the umbrella [`pallas`] crate, this crate is re-exported
49//! as `pallas::codec`.
50//!
51//! [`pallas`]: https://crates.io/crates/pallas
52
53/// Flat encoding/decoding for Plutus Core.
54pub mod flat;
55
56/// Shared re-export of `minicbor` across all Pallas crates.
57pub use minicbor;
58
59/// Stack-safe decoding of recursive CBOR structures.
60pub mod tree;
61
62/// Round-trip friendly common helper structs (`Bytes`, `Nullable`, `Set`, …).
63pub mod utils;
64
65/// Blanket trait for any type that can be CBOR-encoded and decoded with
66/// [`minicbor`]. Implemented automatically for every such type.
67pub trait Fragment: Sized + for<'b> minicbor::Decode<'b, ()> + minicbor::Encode<()> {}
68
69impl<T> Fragment for T where T: for<'b> minicbor::Decode<'b, ()> + minicbor::Encode<()> + Sized {}
70
71/// Derive a `minicbor` [`Decode`]/[`Encode`] implementation for an enum by
72/// dispatching on the incoming CBOR datatype.
73///
74/// Useful for sum types whose variants carry distinct CBOR shapes (e.g. one
75/// variant is an array, another is a map). The macro maps each CBOR datatype
76/// to a single-payload variant, and an `Array` fallback handles a many-field
77/// variant.
78///
79/// [`Decode`]: minicbor::Decode
80/// [`Encode`]: minicbor::Encode
81#[macro_export]
82macro_rules! codec_by_datatype {
83 (
84 $enum_name:ident $( < $lifetime:lifetime > )?,
85 $( $( $cbortype:ident )|* => $one_f:ident ),*,
86 ($( $( $vars:ident ),+ => $many_f:ident )?)
87 ) => {
88 impl<$( $lifetime, )? '__b $(:$lifetime)?, C> minicbor::decode::Decode<'__b, C> for $enum_name $(<$lifetime>)? {
89 fn decode(d: &mut minicbor::Decoder<'__b>, ctx: &mut C) -> Result<Self, minicbor::decode::Error> {
90 match d.datatype()? {
91 $( minicbor::data::Type::Array => {
92 d.array()?;
93 // Using the identifiers trivially to ensure repetition.
94 Ok($enum_name::$many_f($({ let $vars = d.decode_with(ctx)?; $vars }, )+ ))
95 }, )?
96 $( $( minicbor::data::Type::$cbortype )|* => Ok($enum_name::$one_f(d.decode_with(ctx)?)), )*
97 _ => Err(minicbor::decode::Error::message(
98 "Unknown cbor data type for this macro-defined enum.")
99 ),
100 }
101 }
102 }
103
104 impl< $( $lifetime, )? C> minicbor::encode::Encode<C> for $enum_name $(<$lifetime>)? {
105 fn encode<W: minicbor::encode::Write>(
106 &self,
107 e: &mut minicbor::Encoder<W>,
108 ctx: &mut C,
109 ) -> Result<(), minicbor::encode::Error<W::Error>> {
110 match self {
111 $( $enum_name::$many_f ($( $vars ),+) => {
112 // Counting the number of `$vars`:
113 let length: u64 = 0 $(+ { let _ = $vars; 1 })+;
114 e.array(length)?;
115 $( e.encode_with($vars, ctx)?; )+
116 }, )?
117 $( $enum_name::$one_f(__x666) => {
118 e.encode_with(__x666, ctx)?;
119 } )*
120 };
121
122 Ok(())
123 }
124 }
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::minicbor::{self, Decode, Encode, decode, encode};
131
132 #[derive(Clone, Debug)]
133 enum Thing {
134 Coin(u32),
135 Change(bool),
136 Multiasset(bool, u64, i32),
137 }
138
139 codec_by_datatype! {
140 Thing,
141 U8 | U16 | U32 => Coin,
142 Bool => Change,
143 (b, u, i => Multiasset)
144 }
145
146 #[cfg(test)]
147 pub fn roundtrip_codec<T: Encode<()> + for<'a> Decode<'a, ()> + std::fmt::Debug>(query: T) {
148 let mut cbor = Vec::new();
149 match encode(query, &mut cbor) {
150 Ok(_) => (),
151 Err(err) => panic!("Unable to encode data ({:?})", err),
152 };
153 println!("{:-<70}\nResulting CBOR: {:02x?}", "", cbor);
154
155 let query: T = decode(&cbor).unwrap();
156 println!("Decoded data: {:?}", query);
157 }
158
159 #[test]
160 fn roundtrip_codec_by_datatype() {
161 roundtrip_codec(Thing::Coin(0xfafa));
162 roundtrip_codec(Thing::Change(false));
163 roundtrip_codec(Thing::Multiasset(true, 10, -20));
164 }
165}