plutus-parser 0.6.0

A simple crate to derive `from_plutus` and `to_plutus` on Rust types.
Documentation
mod primitives;

#[cfg(feature = "derive")]
pub use plutus_parser_derive::*;

#[cfg(feature = "pallas-v0_32")]
pub use minicbor_v0_25 as minicbor;
#[cfg(feature = "pallas-v0_32")]
pub use pallas_v0_32::{
    BigInt, BoundedBytes, Constr, Hash, Int, KeyValuePairs, MaybeIndefArray, PlutusData,
};

#[cfg(feature = "pallas-v0_33")]
pub use minicbor_v0_25 as minicbor;
#[cfg(feature = "pallas-v0_33")]
pub use pallas_v0_33::{
    BigInt, BoundedBytes, Constr, Hash, Int, KeyValuePairs, MaybeIndefArray, PlutusData,
};

#[cfg(feature = "pallas-v0_34")]
pub use minicbor_v0_25 as minicbor;
#[cfg(feature = "pallas-v0_34")]
pub use pallas_v0_34::{
    BigInt, BoundedBytes, Constr, Hash, Int, KeyValuePairs, MaybeIndefArray, PlutusData,
};

#[cfg(feature = "pallas-v1")]
pub use minicbor_v0_26 as minicbor;
#[cfg(feature = "pallas-v1")]
pub use pallas_v1::{
    BigInt, BoundedBytes, Constr, Hash, Int, KeyValuePairs, MaybeIndefArray, PlutusData,
};

use thiserror::Error;

#[derive(Error, Debug, PartialEq, Eq)]
#[error("decode error at {path}: {kind}")]
pub struct DecodeError {
    kind: DecodeErrorKind,
    path: String,
}

impl DecodeError {
    pub fn new(kind: DecodeErrorKind) -> Self {
        Self {
            kind,
            path: String::new(),
        }
    }

    pub fn unexpected_variant(variant: u64) -> Self {
        Self::new(DecodeErrorKind::UnexpectedVariant { variant })
    }

    pub fn unexpected_type<E: Into<String>, A: Into<String>>(expected: E, actual: A) -> Self {
        Self::new(DecodeErrorKind::UnexpectedType {
            expected: expected.into(),
            actual: actual.into(),
        })
    }

    pub fn wrong_length(expected: usize, actual: usize) -> Self {
        Self::new(DecodeErrorKind::WrongLength { expected, actual })
    }

    pub fn wrong_tuple_field_count(expected: usize, actual: usize) -> Self {
        Self::new(DecodeErrorKind::WrongTupleFieldCount { expected, actual })
    }

    pub fn wrong_variant_field_count(variant: u64, expected: usize, actual: usize) -> Self {
        Self::new(DecodeErrorKind::WrongVariantFieldCount {
            variant,
            expected,
            actual,
        })
    }

    pub fn out_of_range(value: impl std::fmt::Display) -> Self {
        Self::new(DecodeErrorKind::OutOfRange {
            value: value.to_string(),
        })
    }

    pub fn invalid_cbor(error: minicbor::decode::Error) -> Self {
        Self::new(DecodeErrorKind::InvalidCbor(MinicborDecodeError(error)))
    }

    pub fn custom(message: impl Into<String>) -> Self {
        Self::new(DecodeErrorKind::Custom(message.into()))
    }

    pub fn with_field_name(mut self, name: impl std::fmt::Display) -> Self {
        if self.path.is_empty() {
            self.path = name.to_string();
        } else if self.path.starts_with("[") || self.path.starts_with(":") {
            self.path = format!("{name}{}", self.path);
        } else {
            self.path = format!("{name}.{}", self.path);
        }
        self
    }
}

#[derive(Error, Debug, PartialEq, Eq)]
pub enum DecodeErrorKind {
    #[error("unexpected variant {variant}")]
    UnexpectedVariant { variant: u64 },
    #[error("unexpected type (expected {expected}, found {actual})")]
    UnexpectedType { expected: String, actual: String },
    #[error("unexpected length for array (expected {expected}, found {actual})")]
    WrongLength { expected: usize, actual: usize },
    #[error("unexpected field count for tuple (expected {expected}, found {actual})")]
    WrongTupleFieldCount { expected: usize, actual: usize },
    #[error("unexpected field count for variant {variant} (expected {expected}, found {actual})")]
    WrongVariantFieldCount {
        variant: u64,
        expected: usize,
        actual: usize,
    },
    #[error("value {value} out of range")]
    OutOfRange { value: String },
    #[error("invalid cbor: {0}")]
    InvalidCbor(MinicborDecodeError),
    #[error("{0}")]
    Custom(String),
}

#[derive(Error, Debug)]
#[error("{0}")]
pub struct MinicborDecodeError(minicbor::decode::Error);
impl PartialEq for MinicborDecodeError {
    fn eq(&self, other: &Self) -> bool {
        self.0.to_string() == other.0.to_string()
    }
}
impl Eq for MinicborDecodeError {}

pub trait AsPlutus: Sized {
    fn from_plutus(data: PlutusData) -> Result<Self, DecodeError>;
    fn to_plutus(self) -> PlutusData;

    fn from_plutus_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
        let data = minicbor::decode::<PlutusData>(bytes).map_err(DecodeError::invalid_cbor)?;
        Self::from_plutus(data)
    }

    fn to_plutus_bytes(self) -> Vec<u8> {
        let data = self.to_plutus();
        minicbor::to_vec(data).expect("infallible")
    }

    fn vec_from_plutus(data: PlutusData) -> Result<Vec<Self>, DecodeError> {
        let items = parse_array(data)?;
        items
            .into_iter()
            .enumerate()
            .map(|(index, item)| {
                Self::from_plutus(item).map_err(|e| e.with_field_name(format!("[{index}]")))
            })
            .collect()
    }

    fn vec_to_plutus(value: Vec<Self>) -> PlutusData {
        create_array(value.into_iter().map(Self::to_plutus).collect())
    }

    fn array_from_plutus<const N: usize>(data: PlutusData) -> Result<[Self; N], DecodeError> {
        let items = parse_array(data)?;
        if items.len() != N {
            return Err(DecodeError::wrong_length(N, items.len()));
        }
        let result = items
            .into_iter()
            .enumerate()
            .map(|(index, item)| {
                Self::from_plutus(item).map_err(|e| e.with_field_name(format!("[{index}]")))
            })
            .collect::<Result<Vec<Self>, DecodeError>>()?;

        result
            .try_into()
            .map_err(|e: Vec<_>| DecodeError::wrong_length(N, e.len()))
    }

    fn array_to_plutus<const N: usize>(value: [Self; N]) -> PlutusData {
        create_array(value.into_iter().map(Self::to_plutus).collect())
    }
}

pub fn parse_array(data: PlutusData) -> Result<Vec<PlutusData>, DecodeError> {
    let array = match data {
        PlutusData::Array(array) => array,
        other => {
            return Err(DecodeError::unexpected_type("Array", type_name(&other)));
        }
    };
    Ok(array.to_vec())
}

pub fn parse_tuple<const N: usize>(data: PlutusData) -> Result<[PlutusData; N], DecodeError> {
    let array = parse_array(data)?;
    array
        .try_into()
        .map_err(|f: Vec<PlutusData>| DecodeError::wrong_tuple_field_count(N, f.len()))
}

pub fn parse_constr(data: PlutusData) -> Result<(u64, Vec<PlutusData>), DecodeError> {
    let constr = match data {
        PlutusData::Constr(constr) => constr,
        other => {
            return Err(DecodeError::unexpected_type("Constr", type_name(&other)));
        }
    };
    let Some(variant) = constr.constructor_value() else {
        return Err(DecodeError::custom("value has invalid tag"));
    };
    Ok((variant, constr.fields.to_vec()))
}

pub fn parse_variant<const N: usize>(
    variant: u64,
    fields: Vec<PlutusData>,
) -> Result<[PlutusData; N], DecodeError> {
    fields
        .try_into()
        .map_err(|f: Vec<PlutusData>| DecodeError::wrong_variant_field_count(variant, N, f.len()))
}

pub fn parse_map(data: PlutusData) -> Result<Vec<(PlutusData, PlutusData)>, DecodeError> {
    let kvps = match data {
        PlutusData::Map(kvps) => kvps,
        other => {
            return Err(DecodeError::unexpected_type("Map", type_name(&other)));
        }
    };
    Ok(kvps.to_vec())
}

pub fn create_constr(variant: u64, fields: Vec<PlutusData>) -> PlutusData {
    let (tag, any_constructor) = match variant {
        0..=6 => (variant + 121, None),
        7..=127 => (variant + 1280 - 7, None),
        x => (102, Some(x)),
    };
    PlutusData::Constr(Constr {
        tag,
        any_constructor,
        fields: if !fields.is_empty() {
            MaybeIndefArray::Indef(fields)
        } else {
            MaybeIndefArray::Def(Vec::new())
        },
    })
}

pub fn create_array(fields: Vec<PlutusData>) -> PlutusData {
    PlutusData::Array(if !fields.is_empty() {
        MaybeIndefArray::Indef(fields)
    } else {
        MaybeIndefArray::Def(Vec::new())
    })
}

pub fn create_map(kvps: Vec<(PlutusData, PlutusData)>) -> PlutusData {
    PlutusData::Map(KeyValuePairs::Def(kvps))
}

pub(crate) fn type_name(data: &PlutusData) -> &str {
    match data {
        PlutusData::Array(_) => "Array",
        PlutusData::BigInt(_) => "BigInt",
        PlutusData::BoundedBytes(_) => "BoundedBytes",
        PlutusData::Constr(_) => "Constr",
        PlutusData::Map(_) => "Map",
    }
}