Skip to main content

icydb_core/value/
input.rs

1use crate::{
2    db::EntityKey,
3    types::{
4        Account, Blob, Date, Decimal, Duration, Float32, Float64, Id, IntBig, NatBig, Principal,
5        Subaccount, Timestamp, U256, Ulid, Unit,
6    },
7    value::{PublicEnumValue, PublicValue, Value},
8};
9use candid::{CandidType, types::Serializer};
10use serde::{Deserialize, Deserializer};
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#[repr(transparent)]
21#[derive(Clone, Debug, Eq, PartialEq)]
22pub struct InputValue(PublicValue);
23
24impl InputValue {
25    /// Wrap one caller-authored public value without conversion.
26    #[must_use]
27    pub const fn from_public(value: PublicValue) -> Self {
28        Self(value)
29    }
30
31    /// Borrow the canonical recursive public value.
32    #[must_use]
33    pub const fn as_public(&self) -> &PublicValue {
34        &self.0
35    }
36
37    /// Consume this boundary wrapper without conversion.
38    #[must_use]
39    pub fn into_public(self) -> PublicValue {
40        self.0
41    }
42
43    /// Build an account input value.
44    #[must_use]
45    pub const fn account(value: Account) -> Self {
46        Self(PublicValue::Account(value))
47    }
48
49    /// Build a blob input value.
50    #[must_use]
51    pub const fn blob(value: Vec<u8>) -> Self {
52        Self(PublicValue::Blob(value))
53    }
54
55    /// Build a boolean input value.
56    #[must_use]
57    pub const fn boolean(value: bool) -> Self {
58        Self(PublicValue::Bool(value))
59    }
60
61    /// Build a date input value.
62    #[must_use]
63    pub const fn date(value: Date) -> Self {
64        Self(PublicValue::Date(value))
65    }
66
67    /// Build a decimal input value.
68    #[must_use]
69    pub const fn decimal(value: Decimal) -> Self {
70        Self(PublicValue::Decimal(value))
71    }
72
73    /// Build a duration input value.
74    #[must_use]
75    pub const fn duration(value: Duration) -> Self {
76        Self(PublicValue::Duration(value))
77    }
78
79    /// Build a finite 32-bit floating-point input value.
80    #[must_use]
81    pub const fn float32(value: Float32) -> Self {
82        Self(PublicValue::Float32(value))
83    }
84
85    /// Build a finite 64-bit floating-point input value.
86    #[must_use]
87    pub const fn float64(value: Float64) -> Self {
88        Self(PublicValue::Float64(value))
89    }
90
91    /// Build a 64-bit signed integer input value.
92    #[must_use]
93    pub const fn int64(value: i64) -> Self {
94        Self(PublicValue::Int64(value))
95    }
96
97    /// Build a 128-bit signed integer input value.
98    #[must_use]
99    pub const fn int128(value: i128) -> Self {
100        Self(PublicValue::Int128(value))
101    }
102
103    /// Build an unbounded signed integer input value.
104    #[must_use]
105    pub const fn int_big(value: IntBig) -> Self {
106        Self(PublicValue::IntBig(value))
107    }
108
109    /// Build a recursive list from caller-authored input values.
110    ///
111    /// The owned root wrappers move into the shared recursive kernel without
112    /// cloning nested values.
113    #[must_use]
114    pub fn list(values: Vec<Self>) -> Self {
115        Self(PublicValue::List(
116            values.into_iter().map(Self::into_public).collect(),
117        ))
118    }
119
120    /// Build a recursive map from caller-authored input key/value pairs.
121    ///
122    /// The owned root wrappers move into the shared recursive kernel without
123    /// cloning nested values.
124    #[must_use]
125    pub fn map(entries: Vec<(Self, Self)>) -> Self {
126        Self(PublicValue::Map(
127            entries
128                .into_iter()
129                .map(|(key, value)| (key.into_public(), value.into_public()))
130                .collect(),
131        ))
132    }
133
134    /// Build a null input value.
135    #[must_use]
136    pub const fn null() -> Self {
137        Self(PublicValue::Null)
138    }
139
140    /// Report whether this input is null.
141    #[must_use]
142    pub const fn is_null(&self) -> bool {
143        matches!(self.0, PublicValue::Null)
144    }
145
146    /// Build a principal input value.
147    #[must_use]
148    pub const fn principal(value: Principal) -> Self {
149        Self(PublicValue::Principal(value))
150    }
151
152    /// Build a fixed subaccount input value.
153    #[must_use]
154    pub const fn subaccount(value: Subaccount) -> Self {
155        Self(PublicValue::Subaccount(value))
156    }
157
158    /// Build a text input value.
159    #[must_use]
160    pub const fn text(value: String) -> Self {
161        Self(PublicValue::Text(value))
162    }
163
164    /// Build a timestamp input value.
165    #[must_use]
166    pub const fn timestamp(value: Timestamp) -> Self {
167        Self(PublicValue::Timestamp(value))
168    }
169
170    /// Build a 64-bit natural input value.
171    #[must_use]
172    pub const fn nat64(value: u64) -> Self {
173        Self(PublicValue::Nat64(value))
174    }
175
176    /// Build a 128-bit natural input value.
177    #[must_use]
178    pub const fn nat128(value: u128) -> Self {
179        Self(PublicValue::Nat128(value))
180    }
181
182    /// Build an unbounded natural input value.
183    #[must_use]
184    pub const fn nat_big(value: NatBig) -> Self {
185        Self(PublicValue::NatBig(value))
186    }
187
188    /// Build a ULID input value.
189    #[must_use]
190    pub const fn ulid(value: Ulid) -> Self {
191        Self(PublicValue::Ulid(value))
192    }
193
194    /// Build a unit input value.
195    #[must_use]
196    pub const fn unit() -> Self {
197        Self(PublicValue::Unit)
198    }
199
200    /// Build a fixed-width unsigned integer input value.
201    #[must_use]
202    pub const fn u256(value: U256) -> Self {
203        Self(PublicValue::U256(value))
204    }
205
206    /// Build an enum input with an optional schema-visible type path.
207    #[must_use]
208    pub fn enum_value(variant: &str, path: Option<&str>) -> Self {
209        Self(PublicValue::Enum(PublicEnumValue::new(variant, path)))
210    }
211
212    /// Build an enum input whose type is resolved from its expected contract.
213    #[must_use]
214    pub fn loose_enum(variant: impl Into<String>) -> Self {
215        Self(PublicValue::Enum(PublicEnumValue::loose(variant)))
216    }
217
218    /// Attach one recursive payload to an enum input.
219    #[must_use]
220    pub fn with_enum_payload(self, payload: Self) -> Option<Self> {
221        let PublicValue::Enum(value) = self.0 else {
222            return None;
223        };
224        Some(Self(PublicValue::Enum(value.with_payload(payload.0))))
225    }
226
227    /// Lower an input that cannot require accepted enum admission.
228    ///
229    /// Enum input, including nested enum input, stays unresolved and must use
230    /// the accepted catalog admission boundary instead.
231    pub(crate) fn try_into_runtime_non_enum(self) -> Option<Value> {
232        self.0.try_into_runtime_non_enum()
233    }
234
235    /// Lift a runtime value without canonical enum IDs into authored input.
236    pub(crate) fn try_from_runtime_non_enum(value: &Value) -> Option<Self> {
237        PublicValue::try_from_runtime_non_enum(value).map(Self)
238    }
239}
240
241impl CandidType for InputValue {
242    fn ty() -> candid::types::Type {
243        PublicValue::ty()
244    }
245
246    fn _ty() -> candid::types::Type {
247        PublicValue::_ty()
248    }
249
250    fn idl_serialize<S>(&self, serializer: S) -> Result<(), S::Error>
251    where
252        S: Serializer,
253    {
254        self.0.idl_serialize(serializer)
255    }
256}
257
258impl<'de> Deserialize<'de> for InputValue {
259    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
260    where
261        D: Deserializer<'de>,
262    {
263        PublicValue::deserialize(deserializer).map(Self)
264    }
265}
266
267impl From<PublicValue> for InputValue {
268    fn from(value: PublicValue) -> Self {
269        Self(value)
270    }
271}
272
273#[cfg(test)]
274impl From<Value> for InputValue {
275    fn from(value: Value) -> Self {
276        Self::try_from_runtime_non_enum(&value)
277            .expect("test runtime-to-input conversion must not contain canonical enum IDs")
278    }
279}
280
281#[cfg(test)]
282impl From<&Value> for InputValue {
283    fn from(value: &Value) -> Self {
284        Self::try_from_runtime_non_enum(value)
285            .expect("test runtime-to-input conversion must not contain canonical enum IDs")
286    }
287}
288
289impl From<&str> for InputValue {
290    fn from(value: &str) -> Self {
291        Self(PublicValue::Text(value.to_string()))
292    }
293}
294
295impl From<String> for InputValue {
296    fn from(value: String) -> Self {
297        Self(PublicValue::Text(value))
298    }
299}
300
301impl From<Vec<u8>> for InputValue {
302    fn from(value: Vec<u8>) -> Self {
303        Self(PublicValue::Blob(value))
304    }
305}
306
307impl From<Blob> for InputValue {
308    fn from(value: Blob) -> Self {
309        Self(PublicValue::Blob(value.to_vec()))
310    }
311}
312
313impl From<bool> for InputValue {
314    fn from(value: bool) -> Self {
315        Self(PublicValue::Bool(value))
316    }
317}
318
319impl From<Account> for InputValue {
320    fn from(value: Account) -> Self {
321        Self(PublicValue::Account(value))
322    }
323}
324
325impl From<Date> for InputValue {
326    fn from(value: Date) -> Self {
327        Self(PublicValue::Date(value))
328    }
329}
330
331impl From<Decimal> for InputValue {
332    fn from(value: Decimal) -> Self {
333        Self(PublicValue::Decimal(value))
334    }
335}
336
337impl From<Duration> for InputValue {
338    fn from(value: Duration) -> Self {
339        Self(PublicValue::Duration(value))
340    }
341}
342
343impl From<Float32> for InputValue {
344    fn from(value: Float32) -> Self {
345        Self(PublicValue::Float32(value))
346    }
347}
348
349impl From<Float64> for InputValue {
350    fn from(value: Float64) -> Self {
351        Self(PublicValue::Float64(value))
352    }
353}
354
355impl From<IntBig> for InputValue {
356    fn from(value: IntBig) -> Self {
357        Self(PublicValue::IntBig(value))
358    }
359}
360
361impl From<i128> for InputValue {
362    fn from(value: i128) -> Self {
363        Self(PublicValue::Int128(value))
364    }
365}
366
367impl From<NatBig> for InputValue {
368    fn from(value: NatBig) -> Self {
369        Self(PublicValue::NatBig(value))
370    }
371}
372
373impl From<u128> for InputValue {
374    fn from(value: u128) -> Self {
375        Self(PublicValue::Nat128(value))
376    }
377}
378
379impl From<Principal> for InputValue {
380    fn from(value: Principal) -> Self {
381        Self(PublicValue::Principal(value))
382    }
383}
384
385impl From<Subaccount> for InputValue {
386    fn from(value: Subaccount) -> Self {
387        Self(PublicValue::Subaccount(value))
388    }
389}
390
391impl From<Timestamp> for InputValue {
392    fn from(value: Timestamp) -> Self {
393        Self(PublicValue::Timestamp(value))
394    }
395}
396
397impl From<Ulid> for InputValue {
398    fn from(value: Ulid) -> Self {
399        Self(PublicValue::Ulid(value))
400    }
401}
402
403impl From<U256> for InputValue {
404    fn from(value: U256) -> Self {
405        Self(PublicValue::U256(value))
406    }
407}
408
409impl From<()> for InputValue {
410    fn from((): ()) -> Self {
411        Self(PublicValue::Unit)
412    }
413}
414
415impl From<Unit> for InputValue {
416    fn from(_value: Unit) -> Self {
417        Self(PublicValue::Unit)
418    }
419}
420
421impl<T> From<Option<T>> for InputValue
422where
423    T: Into<Self>,
424{
425    fn from(value: Option<T>) -> Self {
426        match value {
427            Some(value) => value.into(),
428            None => Self(PublicValue::Null),
429        }
430    }
431}
432
433impl<T> From<Box<T>> for InputValue
434where
435    T: Into<Self>,
436{
437    fn from(value: Box<T>) -> Self {
438        (*value).into()
439    }
440}
441
442impl<E> From<Id<E>> for InputValue
443where
444    E: EntityKey,
445    E::Key: Into<Self>,
446{
447    fn from(value: Id<E>) -> Self {
448        value.into_key().into()
449    }
450}
451
452impl<E> From<&Id<E>> for InputValue
453where
454    E: EntityKey,
455    E::Key: Into<Self>,
456{
457    fn from(value: &Id<E>) -> Self {
458        value.key().into()
459    }
460}
461
462macro_rules! impl_input_value_int {
463    ($($ty:ty),* $(,)?) => {
464        $(
465            impl From<$ty> for InputValue {
466                fn from(value: $ty) -> Self {
467                    Self(PublicValue::Int64(i64::from(value)))
468                }
469            }
470        )*
471    };
472}
473
474macro_rules! impl_input_value_nat {
475    ($($ty:ty),* $(,)?) => {
476        $(
477            impl From<$ty> for InputValue {
478                fn from(value: $ty) -> Self {
479                    Self(PublicValue::Nat64(u64::from(value)))
480                }
481            }
482        )*
483    };
484}
485
486impl_input_value_int!(i8, i16, i32, i64);
487impl_input_value_nat!(u8, u16, u32, u64);
488
489///
490/// TESTS
491///
492
493#[cfg(test)]
494mod tests {
495    use crate::value::{InputValue, Value};
496
497    #[test]
498    fn runtime_to_input_value_keeps_recursive_collection_shape() {
499        let runtime = Value::List(vec![
500            Value::Nat64(7),
501            Value::Map(vec![(Value::Text("x".to_string()), Value::Bool(true))]),
502        ]);
503
504        assert_eq!(
505            InputValue::from(runtime),
506            InputValue::list(vec![
507                InputValue::nat64(7),
508                InputValue::map(vec![(InputValue::from("x"), InputValue::boolean(true))]),
509            ]),
510        );
511    }
512
513    #[test]
514    fn unresolved_enum_input_cannot_lower_without_admission() {
515        let direct = InputValue::loose_enum("Active");
516        let nested = InputValue::list(vec![direct.clone()]);
517
518        assert_eq!(direct.try_into_runtime_non_enum(), None);
519        assert_eq!(nested.try_into_runtime_non_enum(), None);
520    }
521}