Skip to main content

icydb_core/value/
input.rs

1use crate::{
2    traits::EntityKey,
3    types::{
4        Account, Date, Decimal, Duration, Float32, Float64, Id, IntBig, NatBig, Principal,
5        Subaccount, Timestamp, Ulid,
6    },
7    value::{Value, ValueEnum},
8};
9use candid::CandidType;
10use serde::Deserialize;
11
12//
13// InputValue
14//
15// Public input-side value boundary used by literal-taking API surfaces.
16// This stays separate from runtime `Value` so public write/query inputs can
17// move off the internal execution representation incrementally.
18//
19
20#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
21pub enum InputValue {
22    Account(Account),
23    Blob(Vec<u8>),
24    Bool(bool),
25    Date(Date),
26    Decimal(Decimal),
27    Duration(Duration),
28    Enum(InputValueEnum),
29    Float32(Float32),
30    Float64(Float64),
31    #[serde(rename = "Int")]
32    Int64(i64),
33    Int128(i128),
34    IntBig(IntBig),
35    List(Vec<Self>),
36    Map(Vec<(Self, Self)>),
37    Null,
38    Principal(Principal),
39    Subaccount(Subaccount),
40    Text(String),
41    Timestamp(Timestamp),
42    #[serde(rename = "Nat")]
43    Nat64(u64),
44    Nat128(u128),
45    NatBig(NatBig),
46    Ulid(Ulid),
47    Unit,
48}
49
50//
51// InputValueEnum
52//
53// Input-side enum payload contract paired with `InputValue`.
54// Payload stays recursive through `InputValue` so explicit enum-valued public
55// inputs can cross the boundary without using runtime `Value` directly.
56//
57
58#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
59pub struct InputValueEnum {
60    variant: String,
61    path: Option<String>,
62    payload: Option<Box<InputValue>>,
63}
64
65impl InputValueEnum {
66    #[must_use]
67    pub const fn variant(&self) -> &str {
68        self.variant.as_str()
69    }
70
71    #[must_use]
72    pub fn path(&self) -> Option<&str> {
73        self.path.as_deref()
74    }
75
76    #[must_use]
77    pub fn payload(&self) -> Option<&InputValue> {
78        self.payload.as_deref()
79    }
80}
81
82impl From<Value> for InputValue {
83    fn from(value: Value) -> Self {
84        Self::from(&value)
85    }
86}
87
88impl From<&Value> for InputValue {
89    fn from(value: &Value) -> Self {
90        match value {
91            Value::Account(value) => Self::Account(*value),
92            Value::Blob(value) => Self::Blob(value.clone()),
93            Value::Bool(value) => Self::Bool(*value),
94            Value::Date(value) => Self::Date(*value),
95            Value::Decimal(value) => Self::Decimal(*value),
96            Value::Duration(value) => Self::Duration(*value),
97            Value::Enum(value) => Self::Enum(InputValueEnum::from(value)),
98            Value::Float32(value) => Self::Float32(*value),
99            Value::Float64(value) => Self::Float64(*value),
100            Value::Int64(value) => Self::Int64(*value),
101            Value::Int128(value) => Self::Int128(*value),
102            Value::IntBig(value) => Self::IntBig(value.clone()),
103            Value::List(values) => Self::List(values.iter().map(Self::from).collect()),
104            Value::Map(entries) => Self::Map(
105                entries
106                    .iter()
107                    .map(|(key, value)| (Self::from(key), Self::from(value)))
108                    .collect(),
109            ),
110            Value::Null => Self::Null,
111            Value::Principal(value) => Self::Principal(*value),
112            Value::Subaccount(value) => Self::Subaccount(*value),
113            Value::Text(value) => Self::Text(value.clone()),
114            Value::Timestamp(value) => Self::Timestamp(*value),
115            Value::Nat64(value) => Self::Nat64(*value),
116            Value::Nat128(value) => Self::Nat128(*value),
117            Value::NatBig(value) => Self::NatBig(value.clone()),
118            Value::Ulid(value) => Self::Ulid(*value),
119            Value::Unit => Self::Unit,
120        }
121    }
122}
123
124impl From<InputValue> for Value {
125    fn from(value: InputValue) -> Self {
126        Self::from(&value)
127    }
128}
129
130impl From<&InputValue> for Value {
131    fn from(value: &InputValue) -> Self {
132        match value {
133            InputValue::Account(value) => Self::Account(*value),
134            InputValue::Blob(value) => Self::Blob(value.clone()),
135            InputValue::Bool(value) => Self::Bool(*value),
136            InputValue::Date(value) => Self::Date(*value),
137            InputValue::Decimal(value) => Self::Decimal(*value),
138            InputValue::Duration(value) => Self::Duration(*value),
139            InputValue::Enum(value) => Self::Enum(ValueEnum::from(value)),
140            InputValue::Float32(value) => Self::Float32(*value),
141            InputValue::Float64(value) => Self::Float64(*value),
142            InputValue::Int64(value) => Self::Int64(*value),
143            InputValue::Int128(value) => Self::Int128(*value),
144            InputValue::IntBig(value) => Self::IntBig(value.clone()),
145            InputValue::List(values) => Self::List(values.iter().map(Self::from).collect()),
146            InputValue::Map(entries) => Self::Map(
147                entries
148                    .iter()
149                    .map(|(key, value)| (Self::from(key), Self::from(value)))
150                    .collect(),
151            ),
152            InputValue::Null => Self::Null,
153            InputValue::Principal(value) => Self::Principal(*value),
154            InputValue::Subaccount(value) => Self::Subaccount(*value),
155            InputValue::Text(value) => Self::Text(value.clone()),
156            InputValue::Timestamp(value) => Self::Timestamp(*value),
157            InputValue::Nat64(value) => Self::Nat64(*value),
158            InputValue::Nat128(value) => Self::Nat128(*value),
159            InputValue::NatBig(value) => Self::NatBig(value.clone()),
160            InputValue::Ulid(value) => Self::Ulid(*value),
161            InputValue::Unit => Self::Unit,
162        }
163    }
164}
165
166impl From<ValueEnum> for InputValueEnum {
167    fn from(value: ValueEnum) -> Self {
168        Self::from(&value)
169    }
170}
171
172impl From<&ValueEnum> for InputValueEnum {
173    fn from(value: &ValueEnum) -> Self {
174        Self {
175            variant: value.variant().to_string(),
176            path: value.path().map(ToString::to_string),
177            payload: value
178                .payload()
179                .map(|payload| Box::new(InputValue::from(payload))),
180        }
181    }
182}
183
184impl From<InputValueEnum> for ValueEnum {
185    fn from(value: InputValueEnum) -> Self {
186        Self::from(&value)
187    }
188}
189
190impl From<&InputValueEnum> for ValueEnum {
191    fn from(value: &InputValueEnum) -> Self {
192        let mut runtime = Self::new(value.variant(), value.path());
193        if let Some(payload) = value.payload() {
194            runtime = runtime.with_payload(Value::from(payload));
195        }
196
197        runtime
198    }
199}
200
201impl From<&str> for InputValue {
202    fn from(value: &str) -> Self {
203        Self::Text(value.to_string())
204    }
205}
206
207impl From<String> for InputValue {
208    fn from(value: String) -> Self {
209        Self::Text(value)
210    }
211}
212
213impl From<Vec<u8>> for InputValue {
214    fn from(value: Vec<u8>) -> Self {
215        Self::Blob(value)
216    }
217}
218
219impl From<bool> for InputValue {
220    fn from(value: bool) -> Self {
221        Self::Bool(value)
222    }
223}
224
225impl From<Account> for InputValue {
226    fn from(value: Account) -> Self {
227        Self::Account(value)
228    }
229}
230
231impl From<Date> for InputValue {
232    fn from(value: Date) -> Self {
233        Self::Date(value)
234    }
235}
236
237impl From<Decimal> for InputValue {
238    fn from(value: Decimal) -> Self {
239        Self::Decimal(value)
240    }
241}
242
243impl From<Duration> for InputValue {
244    fn from(value: Duration) -> Self {
245        Self::Duration(value)
246    }
247}
248
249impl From<Float32> for InputValue {
250    fn from(value: Float32) -> Self {
251        Self::Float32(value)
252    }
253}
254
255impl From<Float64> for InputValue {
256    fn from(value: Float64) -> Self {
257        Self::Float64(value)
258    }
259}
260
261impl From<IntBig> for InputValue {
262    fn from(value: IntBig) -> Self {
263        Self::IntBig(value)
264    }
265}
266
267impl From<i128> for InputValue {
268    fn from(value: i128) -> Self {
269        Self::Int128(value)
270    }
271}
272
273impl From<NatBig> for InputValue {
274    fn from(value: NatBig) -> Self {
275        Self::NatBig(value)
276    }
277}
278
279impl From<u128> for InputValue {
280    fn from(value: u128) -> Self {
281        Self::Nat128(value)
282    }
283}
284
285impl From<Principal> for InputValue {
286    fn from(value: Principal) -> Self {
287        Self::Principal(value)
288    }
289}
290
291impl From<Subaccount> for InputValue {
292    fn from(value: Subaccount) -> Self {
293        Self::Subaccount(value)
294    }
295}
296
297impl From<Timestamp> for InputValue {
298    fn from(value: Timestamp) -> Self {
299        Self::Timestamp(value)
300    }
301}
302
303impl From<Ulid> for InputValue {
304    fn from(value: Ulid) -> Self {
305        Self::Ulid(value)
306    }
307}
308
309impl From<()> for InputValue {
310    fn from((): ()) -> Self {
311        Self::Unit
312    }
313}
314
315impl<T> From<Option<T>> for InputValue
316where
317    T: Into<Self>,
318{
319    fn from(value: Option<T>) -> Self {
320        match value {
321            Some(value) => value.into(),
322            None => Self::Null,
323        }
324    }
325}
326
327impl<E> From<Id<E>> for InputValue
328where
329    E: EntityKey,
330    E::Key: Into<Self>,
331{
332    fn from(value: Id<E>) -> Self {
333        value.into_key().into()
334    }
335}
336
337impl<E> From<&Id<E>> for InputValue
338where
339    E: EntityKey,
340    E::Key: Into<Self>,
341{
342    fn from(value: &Id<E>) -> Self {
343        value.key().into()
344    }
345}
346
347macro_rules! impl_input_value_int {
348    ($($ty:ty),* $(,)?) => {
349        $(
350            impl From<$ty> for InputValue {
351                fn from(value: $ty) -> Self {
352                    Self::Int64(i64::from(value))
353                }
354            }
355        )*
356    };
357}
358
359macro_rules! impl_input_value_nat {
360    ($($ty:ty),* $(,)?) => {
361        $(
362            impl From<$ty> for InputValue {
363                fn from(value: $ty) -> Self {
364                    Self::Nat64(u64::from(value))
365                }
366            }
367        )*
368    };
369}
370
371impl_input_value_int!(i8, i16, i32, i64);
372impl_input_value_nat!(u8, u16, u32, u64);
373
374///
375/// TESTS
376///
377
378#[cfg(test)]
379mod tests {
380    use crate::value::{InputValue, InputValueEnum, Value, ValueEnum};
381
382    #[test]
383    fn input_value_round_trip_keeps_recursive_collection_shape() {
384        let runtime = Value::List(vec![
385            Value::Nat64(7),
386            Value::Map(vec![(Value::Text("x".to_string()), Value::Bool(true))]),
387        ]);
388
389        assert_eq!(Value::from(InputValue::from(runtime.clone())), runtime);
390    }
391
392    #[test]
393    fn input_value_enum_round_trip_keeps_payload() {
394        let runtime =
395            ValueEnum::new("Example", Some("test::InputEnum")).with_payload(Value::Nat64(9));
396
397        assert_eq!(
398            InputValueEnum::from(runtime.clone()),
399            InputValueEnum {
400                variant: "Example".to_string(),
401                path: Some("test::InputEnum".to_string()),
402                payload: Some(Box::new(InputValue::Nat64(9))),
403            },
404        );
405        assert_eq!(
406            ValueEnum::from(InputValueEnum::from(runtime.clone())),
407            runtime
408        );
409    }
410}