plutus_parser/
lib.rs

1mod primitives;
2
3#[cfg(feature = "derive")]
4pub use plutus_parser_derive::*;
5
6#[cfg(feature = "pallas-v0_32")]
7pub use minicbor_v0_25 as minicbor;
8#[cfg(feature = "pallas-v0_32")]
9pub use pallas_v0_32::{
10    BigInt, BoundedBytes, Constr, Int, KeyValuePairs, MaybeIndefArray, PlutusData,
11};
12
13#[cfg(feature = "pallas-v0_33")]
14pub use minicbor_v0_25 as minicbor;
15#[cfg(feature = "pallas-v0_33")]
16pub use pallas_v0_33::{
17    BigInt, BoundedBytes, Constr, Int, KeyValuePairs, MaybeIndefArray, PlutusData,
18};
19
20#[cfg(feature = "pallas-v0_34")]
21pub use minicbor_v0_25 as minicbor;
22#[cfg(feature = "pallas-v0_34")]
23pub use pallas_v0_34::{
24    BigInt, BoundedBytes, Constr, Int, KeyValuePairs, MaybeIndefArray, PlutusData,
25};
26
27#[cfg(feature = "pallas-v1")]
28pub use minicbor_v0_26 as minicbor;
29#[cfg(feature = "pallas-v1")]
30pub use pallas_v1::{
31    BigInt, BoundedBytes, Constr, Int, KeyValuePairs, MaybeIndefArray, PlutusData,
32};
33
34use thiserror::Error;
35
36#[derive(Error, Debug, PartialEq, Eq)]
37#[error("decode error at {path}: {kind}")]
38pub struct DecodeError {
39    kind: DecodeErrorKind,
40    path: String,
41}
42
43impl DecodeError {
44    pub fn new(kind: DecodeErrorKind) -> Self {
45        Self {
46            kind,
47            path: String::new(),
48        }
49    }
50
51    pub fn unexpected_variant(variant: u64) -> Self {
52        Self::new(DecodeErrorKind::UnexpectedVariant { variant })
53    }
54
55    pub fn unexpected_type<E: Into<String>, A: Into<String>>(expected: E, actual: A) -> Self {
56        Self::new(DecodeErrorKind::UnexpectedType {
57            expected: expected.into(),
58            actual: actual.into(),
59        })
60    }
61
62    pub fn wrong_tuple_field_count(expected: usize, actual: usize) -> Self {
63        Self::new(DecodeErrorKind::WrongTupleFieldCount { expected, actual })
64    }
65
66    pub fn wrong_variant_field_count(variant: u64, expected: usize, actual: usize) -> Self {
67        Self::new(DecodeErrorKind::WrongVariantFieldCount {
68            variant,
69            expected,
70            actual,
71        })
72    }
73
74    pub fn out_of_range(value: impl std::fmt::Display) -> Self {
75        Self::new(DecodeErrorKind::OutOfRange {
76            value: value.to_string(),
77        })
78    }
79
80    pub fn invalid_cbor(error: minicbor::decode::Error) -> Self {
81        Self::new(DecodeErrorKind::InvalidCbor(MinicborDecodeError(error)))
82    }
83
84    pub fn custom(message: impl Into<String>) -> Self {
85        Self::new(DecodeErrorKind::Custom(message.into()))
86    }
87
88    pub fn with_field_name(mut self, name: impl std::fmt::Display) -> Self {
89        if self.path.is_empty() {
90            self.path = name.to_string();
91        } else if self.path.starts_with("[") || self.path.starts_with(":") {
92            self.path = format!("{name}{}", self.path);
93        } else {
94            self.path = format!("{name}.{}", self.path);
95        }
96        self
97    }
98}
99
100#[derive(Error, Debug, PartialEq, Eq)]
101pub enum DecodeErrorKind {
102    #[error("unexpected variant {variant}")]
103    UnexpectedVariant { variant: u64 },
104    #[error("unexpected type (expected {expected}, found {actual})")]
105    UnexpectedType { expected: String, actual: String },
106    #[error("unexpected field count for tuple (expected {expected}, found {actual})")]
107    WrongTupleFieldCount { expected: usize, actual: usize },
108    #[error("unexpected field count for variant {variant} (expected {expected}, found {actual})")]
109    WrongVariantFieldCount {
110        variant: u64,
111        expected: usize,
112        actual: usize,
113    },
114    #[error("value {value} out of range")]
115    OutOfRange { value: String },
116    #[error("invalid cbor: {0}")]
117    InvalidCbor(MinicborDecodeError),
118    #[error("{0}")]
119    Custom(String),
120}
121
122#[derive(Error, Debug)]
123#[error("{0}")]
124pub struct MinicborDecodeError(minicbor::decode::Error);
125impl PartialEq for MinicborDecodeError {
126    fn eq(&self, other: &Self) -> bool {
127        self.0.to_string() == other.0.to_string()
128    }
129}
130impl Eq for MinicborDecodeError {}
131
132pub trait AsPlutus: Sized {
133    fn from_plutus(data: PlutusData) -> Result<Self, DecodeError>;
134    fn to_plutus(self) -> PlutusData;
135
136    fn from_plutus_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
137        let data = minicbor::decode::<PlutusData>(bytes).map_err(DecodeError::invalid_cbor)?;
138        Self::from_plutus(data)
139    }
140
141    fn to_plutus_bytes(self) -> Vec<u8> {
142        let data = self.to_plutus();
143        minicbor::to_vec(data).expect("infallible")
144    }
145
146    fn vec_from_plutus(data: PlutusData) -> Result<Vec<Self>, DecodeError> {
147        let items = parse_array(data)?;
148        items
149            .into_iter()
150            .enumerate()
151            .map(|(index, item)| {
152                Self::from_plutus(item).map_err(|e| e.with_field_name(format!("[{index}]")))
153            })
154            .collect()
155    }
156
157    fn vec_to_plutus(value: Vec<Self>) -> PlutusData {
158        create_array(value.into_iter().map(Self::to_plutus).collect())
159    }
160}
161
162pub fn parse_array(data: PlutusData) -> Result<Vec<PlutusData>, DecodeError> {
163    let array = match data {
164        PlutusData::Array(array) => array,
165        other => {
166            return Err(DecodeError::unexpected_type("Array", type_name(&other)));
167        }
168    };
169    Ok(array.to_vec())
170}
171
172pub fn parse_tuple<const N: usize>(data: PlutusData) -> Result<[PlutusData; N], DecodeError> {
173    let array = parse_array(data)?;
174    array
175        .try_into()
176        .map_err(|f: Vec<PlutusData>| DecodeError::wrong_tuple_field_count(N, f.len()))
177}
178
179pub fn parse_constr(data: PlutusData) -> Result<(u64, Vec<PlutusData>), DecodeError> {
180    let constr = match data {
181        PlutusData::Constr(constr) => constr,
182        other => {
183            return Err(DecodeError::unexpected_type("Constr", type_name(&other)));
184        }
185    };
186    let Some(variant) = constr.constructor_value() else {
187        return Err(DecodeError::custom("value has invalid tag"));
188    };
189    Ok((variant, constr.fields.to_vec()))
190}
191
192pub fn parse_variant<const N: usize>(
193    variant: u64,
194    fields: Vec<PlutusData>,
195) -> Result<[PlutusData; N], DecodeError> {
196    fields
197        .try_into()
198        .map_err(|f: Vec<PlutusData>| DecodeError::wrong_variant_field_count(variant, N, f.len()))
199}
200
201pub fn parse_map(data: PlutusData) -> Result<Vec<(PlutusData, PlutusData)>, DecodeError> {
202    let kvps = match data {
203        PlutusData::Map(kvps) => kvps,
204        other => {
205            return Err(DecodeError::unexpected_type("Map", type_name(&other)));
206        }
207    };
208    Ok(kvps.to_vec())
209}
210
211pub fn create_constr(variant: u64, fields: Vec<PlutusData>) -> PlutusData {
212    let (tag, any_constructor) = match variant {
213        0..=6 => (variant + 121, None),
214        7..=127 => (variant + 1280 - 7, None),
215        x => (102, Some(x)),
216    };
217    PlutusData::Constr(Constr {
218        tag,
219        any_constructor,
220        fields: if !fields.is_empty() {
221            MaybeIndefArray::Indef(fields)
222        } else {
223            MaybeIndefArray::Def(Vec::new())
224        },
225    })
226}
227
228pub fn create_array(fields: Vec<PlutusData>) -> PlutusData {
229    PlutusData::Array(if !fields.is_empty() {
230        MaybeIndefArray::Indef(fields)
231    } else {
232        MaybeIndefArray::Def(Vec::new())
233    })
234}
235
236pub fn create_map(kvps: Vec<(PlutusData, PlutusData)>) -> PlutusData {
237    PlutusData::Map(KeyValuePairs::Def(kvps))
238}
239
240pub(crate) fn type_name(data: &PlutusData) -> &str {
241    match data {
242        PlutusData::Array(_) => "Array",
243        PlutusData::BigInt(_) => "BigInt",
244        PlutusData::BoundedBytes(_) => "BoundedBytes",
245        PlutusData::Constr(_) => "Constr",
246        PlutusData::Map(_) => "Map",
247    }
248}