Skip to main content

whisky_common/data/
mod.rs

1mod aliases;
2pub mod blueprint;  // Public so blueprint types can be accessed via data::blueprint::
3mod credentials;
4mod primitives;
5mod value;
6use std::fmt::Debug;
7
8pub use aliases::*;
9// Note: Blueprint types are NOT wildcard exported to avoid conflicts
10// Access them via whisky::data::blueprint::TypeName
11pub use credentials::*;
12pub use primitives::*;
13pub use value::*;
14
15use crate::WError;
16
17pub trait PlutusDataJson: Clone + Debug + Sized {
18    fn to_json(&self) -> serde_json::Value;
19    fn to_json_string(&self) -> String {
20        self.to_json().to_string()
21    }
22    fn to_constr_field(&self) -> Vec<serde_json::Value> {
23        vec![self.to_json()]
24    }
25
26    fn from_json(value: &serde_json::Value) -> Result<Self, WError>;
27    fn from_json_string(json_str: &str) -> Result<Self, WError> {
28        let value: serde_json::Value = serde_json::from_str(json_str)
29            .map_err(WError::from_err("PlutusDataJson::from_json_string"))?;
30        Self::from_json(&value)
31    }
32
33    /// Parse from constructor fields array (the inverse of to_constr_field).
34    /// Default implementation handles single-element arrays by extracting the first element.
35    fn from_constr_field(fields: &serde_json::Value) -> Result<Self, WError> {
36        let arr = fields
37            .as_array()
38            .ok_or_else(|| WError::new("from_constr_field", "expected array"))?;
39        if arr.len() == 1 {
40            Self::from_json(&arr[0])
41        } else if arr.is_empty() {
42            Self::from_json(fields)
43        } else {
44            // For tuples and multi-field types, pass the array directly
45            Self::from_json(fields)
46        }
47    }
48}
49
50#[derive(Clone, Debug)]
51pub enum PlutusData {
52    Integer(Int),
53    ByteString(ByteString),
54    List(List<PlutusData>),
55    Map(Map<PlutusData, PlutusData>),
56    Bool(Bool),
57    Constr(Constr<Box<PlutusData>>),
58}
59
60impl PlutusDataJson for PlutusData {
61    fn to_json(&self) -> serde_json::Value {
62        match self {
63            PlutusData::Integer(int) => int.to_json(),
64            PlutusData::ByteString(bytes) => bytes.to_json(),
65            PlutusData::List(list) => list.to_json(),
66            PlutusData::Map(map) => map.to_json(),
67            PlutusData::Bool(bool) => bool.to_json(),
68            PlutusData::Constr(constr) => constr.to_json(),
69        }
70    }
71
72    fn to_json_string(&self) -> String {
73        self.to_json().to_string()
74    }
75
76    fn to_constr_field(&self) -> Vec<serde_json::Value> {
77        match self {
78            PlutusData::Integer(int) => vec![int.to_json()],
79            PlutusData::ByteString(bytes) => vec![bytes.to_json()],
80            PlutusData::List(list) => vec![list.to_json()],
81            PlutusData::Map(map) => vec![map.to_json()],
82            PlutusData::Bool(bool) => vec![bool.to_json()],
83            PlutusData::Constr(constr) => constr.fields.to_constr_field(),
84        }
85    }
86
87    fn from_json(value: &serde_json::Value) -> Result<Self, WError> {
88        // Detect type based on JSON structure
89        if value.get("int").is_some() {
90            Int::from_json(value).map(PlutusData::Integer)
91        } else if value.get("bytes").is_some() {
92            ByteString::from_json(value).map(PlutusData::ByteString)
93        } else if value.get("list").is_some() {
94            List::<PlutusData>::from_json(value).map(PlutusData::List)
95        } else if value.get("map").is_some() {
96            Map::<PlutusData, PlutusData>::from_json(value).map(PlutusData::Map)
97        } else if value.get("constructor").is_some() {
98            // Check if it's a Bool (constructor 0 or 1 with empty fields)
99            let constructor = value.get("constructor").and_then(|c| c.as_u64());
100            let fields = value.get("fields").and_then(|f| f.as_array());
101            if let (Some(tag), Some(f)) = (constructor, fields) {
102                if (tag == 0 || tag == 1) && f.is_empty() {
103                    return Bool::from_json(value).map(PlutusData::Bool);
104                }
105            }
106            // Otherwise it's a Constr
107            Constr::<Box<PlutusData>>::from_json(value).map(PlutusData::Constr)
108        } else {
109            Err(WError::new("PlutusData::from_json", "unrecognized JSON format"))
110        }
111    }
112}
113
114// Implementation for Box<PlutusData>
115impl PlutusDataJson for Box<PlutusData> {
116    fn to_json(&self) -> serde_json::Value {
117        self.as_ref().to_json()
118    }
119
120    fn to_json_string(&self) -> String {
121        self.to_json().to_string()
122    }
123
124    fn to_constr_field(&self) -> Vec<serde_json::Value> {
125        self.as_ref().to_constr_field()
126    }
127
128    fn from_json(value: &serde_json::Value) -> Result<Self, WError> {
129        PlutusData::from_json(value).map(Box::new)
130    }
131}
132
133// Implementation for Box<List<T>>
134impl<T: PlutusDataJson + Clone> PlutusDataJson for Box<List<T>> {
135    fn to_json(&self) -> serde_json::Value {
136        self.as_ref().to_json()
137    }
138
139    fn to_json_string(&self) -> String {
140        self.to_json().to_string()
141    }
142
143    fn to_constr_field(&self) -> Vec<serde_json::Value> {
144        vec![self.to_json()]
145    }
146
147    fn from_json(value: &serde_json::Value) -> Result<Self, WError> {
148        List::<T>::from_json(value).map(Box::new)
149    }
150}
151
152// Macro to implement PlutusDataJson for Box<SingleType>
153macro_rules! impl_box_plutus_data {
154    ($($ty:ty),+) => {
155        $(
156            impl PlutusDataJson for Box<$ty> {
157                fn to_json(&self) -> serde_json::Value {
158                    self.as_ref().to_json()
159                }
160
161                fn to_json_string(&self) -> String {
162                    self.to_json().to_string()
163                }
164
165                fn to_constr_field(&self) -> Vec<serde_json::Value> {
166                    vec![self.to_json()]
167                }
168
169                fn from_json(value: &serde_json::Value) -> Result<Self, WError> {
170                    <$ty>::from_json(value).map(Box::new)
171                }
172            }
173        )+
174    };
175}
176
177// Implement for common single types that are often boxed
178impl_box_plutus_data!(
179    ByteString,
180    Int,
181    Bool
182);