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, Hash, 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, Hash, 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, Hash, 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, Hash, 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_length(expected: usize, actual: usize) -> Self {
63        Self::new(DecodeErrorKind::WrongLength { expected, actual })
64    }
65
66    pub fn wrong_tuple_field_count(expected: usize, actual: usize) -> Self {
67        Self::new(DecodeErrorKind::WrongTupleFieldCount { expected, actual })
68    }
69
70    pub fn wrong_variant_field_count(variant: u64, expected: usize, actual: usize) -> Self {
71        Self::new(DecodeErrorKind::WrongVariantFieldCount {
72            variant,
73            expected,
74            actual,
75        })
76    }
77
78    pub fn out_of_range(value: impl std::fmt::Display) -> Self {
79        Self::new(DecodeErrorKind::OutOfRange {
80            value: value.to_string(),
81        })
82    }
83
84    pub fn invalid_cbor(error: minicbor::decode::Error) -> Self {
85        Self::new(DecodeErrorKind::InvalidCbor(MinicborDecodeError(error)))
86    }
87
88    pub fn custom(message: impl Into<String>) -> Self {
89        Self::new(DecodeErrorKind::Custom(message.into()))
90    }
91
92    pub fn with_field_name(mut self, name: impl std::fmt::Display) -> Self {
93        if self.path.is_empty() {
94            self.path = name.to_string();
95        } else if self.path.starts_with("[") || self.path.starts_with(":") {
96            self.path = format!("{name}{}", self.path);
97        } else {
98            self.path = format!("{name}.{}", self.path);
99        }
100        self
101    }
102}
103
104#[derive(Error, Debug, PartialEq, Eq)]
105pub enum DecodeErrorKind {
106    #[error("unexpected variant {variant}")]
107    UnexpectedVariant { variant: u64 },
108    #[error("unexpected type (expected {expected}, found {actual})")]
109    UnexpectedType { expected: String, actual: String },
110    #[error("unexpected length for array (expected {expected}, found {actual})")]
111    WrongLength { expected: usize, actual: usize },
112    #[error("unexpected field count for tuple (expected {expected}, found {actual})")]
113    WrongTupleFieldCount { expected: usize, actual: usize },
114    #[error("unexpected field count for variant {variant} (expected {expected}, found {actual})")]
115    WrongVariantFieldCount {
116        variant: u64,
117        expected: usize,
118        actual: usize,
119    },
120    #[error("value {value} out of range")]
121    OutOfRange { value: String },
122    #[error("invalid cbor: {0}")]
123    InvalidCbor(MinicborDecodeError),
124    #[error("{0}")]
125    Custom(String),
126}
127
128#[derive(Error, Debug)]
129#[error("{0}")]
130pub struct MinicborDecodeError(minicbor::decode::Error);
131impl PartialEq for MinicborDecodeError {
132    fn eq(&self, other: &Self) -> bool {
133        self.0.to_string() == other.0.to_string()
134    }
135}
136impl Eq for MinicborDecodeError {}
137
138pub trait AsPlutus: Sized {
139    fn from_plutus(data: PlutusData) -> Result<Self, DecodeError>;
140    fn to_plutus(self) -> PlutusData;
141
142    fn from_plutus_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
143        let data = minicbor::decode::<PlutusData>(bytes).map_err(DecodeError::invalid_cbor)?;
144        Self::from_plutus(data)
145    }
146
147    fn to_plutus_bytes(self) -> Vec<u8> {
148        let data = self.to_plutus();
149        minicbor::to_vec(data).expect("infallible")
150    }
151
152    fn vec_from_plutus(data: PlutusData) -> Result<Vec<Self>, DecodeError> {
153        let items = parse_array(data)?;
154        items
155            .into_iter()
156            .enumerate()
157            .map(|(index, item)| {
158                Self::from_plutus(item).map_err(|e| e.with_field_name(format!("[{index}]")))
159            })
160            .collect()
161    }
162
163    fn vec_to_plutus(value: Vec<Self>) -> PlutusData {
164        create_array(value.into_iter().map(Self::to_plutus).collect())
165    }
166
167    fn array_from_plutus<const N: usize>(data: PlutusData) -> Result<[Self; N], DecodeError> {
168        let items = parse_array(data)?;
169        if items.len() != N {
170            return Err(DecodeError::wrong_length(N, items.len()));
171        }
172        let result = items
173            .into_iter()
174            .enumerate()
175            .map(|(index, item)| {
176                Self::from_plutus(item).map_err(|e| e.with_field_name(format!("[{index}]")))
177            })
178            .collect::<Result<Vec<Self>, DecodeError>>()?;
179
180        result
181            .try_into()
182            .map_err(|e: Vec<_>| DecodeError::wrong_length(N, e.len()))
183    }
184
185    fn array_to_plutus<const N: usize>(value: [Self; N]) -> PlutusData {
186        create_array(value.into_iter().map(Self::to_plutus).collect())
187    }
188}
189
190pub fn parse_array(data: PlutusData) -> Result<Vec<PlutusData>, DecodeError> {
191    let array = match data {
192        PlutusData::Array(array) => array,
193        other => {
194            return Err(DecodeError::unexpected_type("Array", type_name(&other)));
195        }
196    };
197    Ok(array.to_vec())
198}
199
200pub fn parse_tuple<const N: usize>(data: PlutusData) -> Result<[PlutusData; N], DecodeError> {
201    let array = parse_array(data)?;
202    array
203        .try_into()
204        .map_err(|f: Vec<PlutusData>| DecodeError::wrong_tuple_field_count(N, f.len()))
205}
206
207pub fn parse_constr(data: PlutusData) -> Result<(u64, Vec<PlutusData>), DecodeError> {
208    let constr = match data {
209        PlutusData::Constr(constr) => constr,
210        other => {
211            return Err(DecodeError::unexpected_type("Constr", type_name(&other)));
212        }
213    };
214    let Some(variant) = constr.constructor_value() else {
215        return Err(DecodeError::custom("value has invalid tag"));
216    };
217    Ok((variant, constr.fields.to_vec()))
218}
219
220pub fn parse_variant<const N: usize>(
221    variant: u64,
222    fields: Vec<PlutusData>,
223) -> Result<[PlutusData; N], DecodeError> {
224    fields
225        .try_into()
226        .map_err(|f: Vec<PlutusData>| DecodeError::wrong_variant_field_count(variant, N, f.len()))
227}
228
229pub fn parse_map(data: PlutusData) -> Result<Vec<(PlutusData, PlutusData)>, DecodeError> {
230    let kvps = match data {
231        PlutusData::Map(kvps) => kvps,
232        other => {
233            return Err(DecodeError::unexpected_type("Map", type_name(&other)));
234        }
235    };
236    Ok(kvps.to_vec())
237}
238
239pub fn create_constr(variant: u64, fields: Vec<PlutusData>) -> PlutusData {
240    let (tag, any_constructor) = match variant {
241        0..=6 => (variant + 121, None),
242        7..=127 => (variant + 1280 - 7, None),
243        x => (102, Some(x)),
244    };
245    PlutusData::Constr(Constr {
246        tag,
247        any_constructor,
248        fields: if !fields.is_empty() {
249            MaybeIndefArray::Indef(fields)
250        } else {
251            MaybeIndefArray::Def(Vec::new())
252        },
253    })
254}
255
256pub fn create_array(fields: Vec<PlutusData>) -> PlutusData {
257    PlutusData::Array(if !fields.is_empty() {
258        MaybeIndefArray::Indef(fields)
259    } else {
260        MaybeIndefArray::Def(Vec::new())
261    })
262}
263
264pub fn create_map(kvps: Vec<(PlutusData, PlutusData)>) -> PlutusData {
265    PlutusData::Map(KeyValuePairs::Def(kvps))
266}
267
268pub(crate) fn type_name(data: &PlutusData) -> &str {
269    match data {
270        PlutusData::Array(_) => "Array",
271        PlutusData::BigInt(_) => "BigInt",
272        PlutusData::BoundedBytes(_) => "BoundedBytes",
273        PlutusData::Constr(_) => "Constr",
274        PlutusData::Map(_) => "Map",
275    }
276}