Skip to main content

whisky_common/data/primitives/
constructors.rs

1use serde_json::{json, Value};
2
3use crate::{data::PlutusDataJson, WError};
4
5#[derive(Clone, Debug)]
6pub struct Constr<T = ()>
7where
8    T: Clone + PlutusDataJson,
9{
10    pub tag: u64,
11    pub fields: T,
12}
13
14impl<T> Constr<T>
15where
16    T: Clone + PlutusDataJson,
17{
18    pub fn new(tag: u64, fields: T) -> Self {
19        Constr { tag, fields }
20    }
21}
22
23impl<T> PlutusDataJson for Constr<T>
24where
25    T: Clone + PlutusDataJson,
26{
27    fn to_json(&self) -> Value {
28        let fields_json = self.fields.to_constr_field();
29        constr(self.tag, fields_json)
30    }
31
32    fn from_json(value: &Value) -> Result<Self, WError> {
33        let tag = value
34            .get("constructor")
35            .ok_or_else(|| WError::new("Constr::from_json", "missing 'constructor' field"))?
36            .as_u64()
37            .ok_or_else(|| WError::new("Constr::from_json", "invalid 'constructor' value"))?;
38
39        let fields_json = value
40            .get("fields")
41            .ok_or_else(|| WError::new("Constr::from_json", "missing 'fields' field"))?;
42
43        let fields = T::from_constr_field(fields_json)
44            .map_err(WError::add_err_trace("Constr::from_json"))?;
45
46        Ok(Constr { tag, fields })
47    }
48}
49
50impl PlutusDataJson for () {
51    fn to_json(&self) -> Value {
52        json!([])
53    }
54    fn to_constr_field(&self) -> Vec<serde_json::Value> {
55        vec![]
56    }
57    fn from_json(value: &Value) -> Result<Self, WError> {
58        // Accept both empty array and empty fields
59        if value.is_array() {
60            let arr = value.as_array().unwrap();
61            if arr.is_empty() {
62                return Ok(());
63            }
64        }
65        Err(WError::new("()::from_json", "expected empty array"))
66    }
67}
68
69// value constructor
70
71pub fn constr<N: Into<Value>, T: Into<Value>>(constructor: N, fields: T) -> Value {
72    json!({ "constructor": constructor.into(), "fields": fields.into() })
73}
74
75pub fn constr0<T: Into<Value>>(fields: T) -> Value {
76    constr(0, fields)
77}
78
79pub fn constr1<T: Into<Value>>(fields: T) -> Value {
80    constr(1, fields)
81}
82
83pub fn constr2<T: Into<Value>>(fields: T) -> Value {
84    constr(2, fields)
85}
86
87/// Deprecated: Use `constr` instead.
88pub fn con_str<N: Into<Value>, T: Into<Value>>(constructor: N, fields: T) -> Value {
89    json!({ "constructor": constructor.into(), "fields": fields.into() })
90}
91
92/// Deprecated: Use `constr0` instead.
93pub fn con_str0<T: Into<Value>>(fields: T) -> Value {
94    con_str(0, fields)
95}
96
97/// Deprecated: Use `constr1` instead.
98pub fn con_str1<T: Into<Value>>(fields: T) -> Value {
99    con_str(1, fields)
100}
101
102/// Deprecated: Use `constr2` instead.
103pub fn con_str2<T: Into<Value>>(fields: T) -> Value {
104    con_str(2, fields)
105}
106
107/// Wrapper for tuples that provides Debug implementation for tuples larger than 12 elements
108/// (Rust's std only implements Debug for tuples up to 12 elements)
109/// Use this when you have constructor fields with 13+ elements that need Debug support.
110#[repr(transparent)]
111pub struct ConstrFields<T>(pub T);
112
113impl<T: Clone> Clone for ConstrFields<T> {
114    fn clone(&self) -> Self {
115        ConstrFields(self.0.clone())
116    }
117}
118
119#[macro_export]
120macro_rules! impl_constr_fields {
121    ( $( $name:ident )+ ) => {
122        // Implement PlutusDataJson for Box<(T1, T2, ...)>
123        // This only works for tuples up to 12 elements due to Rust's Debug trait limit
124        #[allow(non_snake_case)]
125        impl<$($name,)+> PlutusDataJson for Box<($($name,)+)>
126        where
127            ($($name,)+): ::core::fmt::Debug,
128            $($name: PlutusDataJson + Clone,)+
129        {
130            fn to_json(&self) -> Value {
131                json!(self.to_constr_field())
132            }
133
134            fn to_constr_field(&self) -> Vec<Value> {
135                let tuple = &**self;
136                let ($($name,)+) = tuple.clone();
137                vec![$($name.to_json(),)+]
138            }
139
140            fn from_json(value: &Value) -> Result<Self, WError> {
141                let arr = value
142                    .as_array()
143                    .ok_or_else(|| WError::new("Box<tuple>::from_json", "expected array"))?;
144
145                let mut iter = arr.iter();
146                $(
147                    let $name = {
148                        let item = iter.next()
149                            .ok_or_else(|| WError::new("Box<tuple>::from_json", "not enough elements"))?;
150                        $name::from_json(item)
151                            .map_err(WError::add_err_trace("Box<tuple>::from_json"))?
152                    };
153                )+
154
155                Ok(Box::new(($($name,)+)))
156            }
157        }
158
159        // Implement PlutusDataJson for Box<ConstrFields<(T1, T2, ...)>>
160        // Use this for tuples with 13+ elements
161        #[allow(non_snake_case)]
162        impl<$($name,)+> PlutusDataJson for Box<ConstrFields<($($name,)+)>>
163        where
164            $($name: PlutusDataJson + Clone,)+
165        {
166            fn to_json(&self) -> Value {
167                json!(self.to_constr_field())
168            }
169
170            fn to_constr_field(&self) -> Vec<Value> {
171                let tuple = &self.0;
172                let ($($name,)+) = tuple.clone();
173                vec![$($name.to_json(),)+]
174            }
175
176            fn from_json(value: &Value) -> Result<Self, WError> {
177                let arr = value
178                    .as_array()
179                    .ok_or_else(|| WError::new("Box<ConstrFields>::from_json", "expected array"))?;
180
181                let mut iter = arr.iter();
182                $(
183                    let $name = {
184                        let item = iter.next()
185                            .ok_or_else(|| WError::new("Box<ConstrFields>::from_json", "not enough elements"))?;
186                        $name::from_json(item)
187                            .map_err(WError::add_err_trace("Box<ConstrFields>::from_json"))?
188                    };
189                )+
190
191                Ok(Box::new(ConstrFields(($($name,)+))))
192            }
193        }
194
195        // Implement Debug for ConstrFields
196        #[allow(non_snake_case)]
197        impl<$($name,)+> ::core::fmt::Debug for ConstrFields<($($name,)+)>
198        where
199            $($name: ::core::fmt::Debug,)+
200        {
201            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
202                let tuple = &self.0;
203                let ($($name,)+) = tuple;
204                f.debug_tuple("")
205                    $(.field($name))+
206                    .finish()
207            }
208        }
209    }
210}
211
212// Implement for tuples 2-12 (these work with both Box<(...)> and Box<ConstrFields<(...)>>)
213impl_constr_fields!(T1 T2);
214impl_constr_fields!(T1 T2 T3);
215impl_constr_fields!(T1 T2 T3 T4);
216impl_constr_fields!(T1 T2 T3 T4 T5);
217impl_constr_fields!(T1 T2 T3 T4 T5 T6);
218impl_constr_fields!(T1 T2 T3 T4 T5 T6 T7);
219impl_constr_fields!(T1 T2 T3 T4 T5 T6 T7 T8);
220impl_constr_fields!(T1 T2 T3 T4 T5 T6 T7 T8 T9);
221impl_constr_fields!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10);
222impl_constr_fields!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11);
223impl_constr_fields!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12);
224
225// Implement for tuples 13-20 (these MUST use Box<ConstrFields<(...)>> instead of Box<(...)>)
226impl_constr_fields!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13);
227impl_constr_fields!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14);
228impl_constr_fields!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15);
229impl_constr_fields!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16);
230impl_constr_fields!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16 T17);
231impl_constr_fields!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16 T17 T18);
232impl_constr_fields!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16 T17 T18 T19);
233impl_constr_fields!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16 T17 T18 T19 T20);
234
235#[macro_export]
236macro_rules! impl_constr_n {
237    ($($name:ident: $tag:expr),+) => {
238        $(
239            #[derive(Clone, Debug, PartialEq)]
240            pub struct $name<T = ()>
241            where
242                T: Clone + PlutusDataJson,
243            {
244                pub fields: T,
245            }
246
247            impl<T> $name<T>
248            where
249                T: Clone + PlutusDataJson,
250            {
251                pub fn new(fields: T) -> Self {
252                    $name { fields }
253                }
254            }
255
256            impl<T> PlutusDataJson for $name<T>
257            where
258                T: Clone + PlutusDataJson,
259            {
260                fn to_json(&self) -> Value {
261                    let fields_json = self.fields.to_constr_field();
262                    constr($tag, fields_json)
263                }
264
265                fn from_json(value: &Value) -> Result<Self, WError> {
266                    let tag = value
267                        .get("constructor")
268                        .ok_or_else(|| WError::new(concat!(stringify!($name), "::from_json"), "missing 'constructor' field"))?
269                        .as_u64()
270                        .ok_or_else(|| WError::new(concat!(stringify!($name), "::from_json"), "invalid 'constructor' value"))?;
271
272                    if tag != $tag {
273                        return Err(WError::new(
274                            concat!(stringify!($name), "::from_json"),
275                            &format!("expected constructor tag {}, got {}", $tag, tag),
276                        ));
277                    }
278
279                    let fields_json = value
280                        .get("fields")
281                        .ok_or_else(|| WError::new(concat!(stringify!($name), "::from_json"), "missing 'fields' field"))?;
282
283                    let fields = T::from_constr_field(fields_json)
284                        .map_err(WError::add_err_trace(concat!(stringify!($name), "::from_json")))?;
285
286                    Ok($name { fields })
287                }
288            }
289        )+
290    }
291}
292
293impl_constr_n!(
294    Constr0: 0,
295    Constr1: 1,
296    Constr2: 2,
297    Constr3: 3,
298    Constr4: 4,
299    Constr5: 5,
300    Constr6: 6,
301    Constr7: 7,
302    Constr8: 8,
303    Constr9: 9,
304    Constr10: 10
305);
306
307// Implement PlutusDataJson for Box<ConstrN<T>> to support boxed wrapper types
308macro_rules! impl_box_constr {
309    ($($constr:ident),+) => {
310        $(
311            impl<T> PlutusDataJson for Box<$constr<T>>
312            where
313                T: Clone + PlutusDataJson,
314            {
315                fn to_json(&self) -> Value {
316                    self.as_ref().to_json()
317                }
318
319                fn to_json_string(&self) -> String {
320                    self.to_json().to_string()
321                }
322
323                fn to_constr_field(&self) -> Vec<Value> {
324                    vec![self.to_json()]
325                }
326
327                fn from_json(value: &Value) -> Result<Self, WError> {
328                    $constr::<T>::from_json(value).map(Box::new)
329                }
330            }
331        )+
332    };
333}
334
335impl_box_constr!(
336    Constr, Constr0, Constr1, Constr2, Constr3, Constr4, Constr5, Constr6, Constr7, Constr8,
337    Constr9, Constr10
338);