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, Principal, Subaccount,
5        Timestamp, U256, Ulid, Unit,
6    },
7    value::{PublicEnumValue, PublicValue, Value},
8};
9
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 a recursive list from caller-authored input values.
104    ///
105    /// The owned root wrappers move into the shared recursive kernel without
106    /// cloning nested values.
107    #[must_use]
108    pub fn list(values: Vec<Self>) -> Self {
109        Self(PublicValue::List(
110            values.into_iter().map(Self::into_public).collect(),
111        ))
112    }
113
114    /// Build a recursive map from caller-authored input key/value pairs.
115    ///
116    /// The owned root wrappers move into the shared recursive kernel without
117    /// cloning nested values.
118    #[must_use]
119    pub fn map(entries: Vec<(Self, Self)>) -> Self {
120        Self(PublicValue::Map(
121            entries
122                .into_iter()
123                .map(|(key, value)| (key.into_public(), value.into_public()))
124                .collect(),
125        ))
126    }
127
128    /// Build a null input value.
129    #[must_use]
130    pub const fn null() -> Self {
131        Self(PublicValue::Null)
132    }
133
134    /// Report whether this input is null.
135    #[must_use]
136    pub const fn is_null(&self) -> bool {
137        matches!(self.0, PublicValue::Null)
138    }
139
140    /// Build a principal input value.
141    #[must_use]
142    pub const fn principal(value: Principal) -> Self {
143        Self(PublicValue::Principal(value))
144    }
145
146    /// Build a fixed subaccount input value.
147    #[must_use]
148    pub const fn subaccount(value: Subaccount) -> Self {
149        Self(PublicValue::Subaccount(value))
150    }
151
152    /// Build a text input value.
153    #[must_use]
154    pub const fn text(value: String) -> Self {
155        Self(PublicValue::Text(value))
156    }
157
158    /// Build a timestamp input value.
159    #[must_use]
160    pub const fn timestamp(value: Timestamp) -> Self {
161        Self(PublicValue::Timestamp(value))
162    }
163
164    /// Build a 64-bit natural input value.
165    #[must_use]
166    pub const fn nat64(value: u64) -> Self {
167        Self(PublicValue::Nat64(value))
168    }
169
170    /// Build a 128-bit natural input value.
171    #[must_use]
172    pub const fn nat128(value: u128) -> Self {
173        Self(PublicValue::Nat128(value))
174    }
175
176    /// Build a ULID input value.
177    #[must_use]
178    pub const fn ulid(value: Ulid) -> Self {
179        Self(PublicValue::Ulid(value))
180    }
181
182    /// Build a unit input value.
183    #[must_use]
184    pub const fn unit() -> Self {
185        Self(PublicValue::Unit)
186    }
187
188    /// Build a fixed-width unsigned integer input value.
189    #[must_use]
190    pub const fn u256(value: U256) -> Self {
191        Self(PublicValue::U256(value))
192    }
193
194    /// Build an enum input with an optional schema-visible type path.
195    #[must_use]
196    pub fn enum_value(variant: &str, path: Option<&str>) -> Self {
197        Self(PublicValue::Enum(PublicEnumValue::new(variant, path)))
198    }
199
200    /// Build an enum input whose type is resolved from its expected contract.
201    #[must_use]
202    pub fn loose_enum(variant: impl Into<String>) -> Self {
203        Self(PublicValue::Enum(PublicEnumValue::loose(variant)))
204    }
205
206    /// Attach one recursive payload to an enum input.
207    #[must_use]
208    pub fn with_enum_payload(self, payload: Self) -> Option<Self> {
209        let PublicValue::Enum(value) = self.0 else {
210            return None;
211        };
212        Some(Self(PublicValue::Enum(value.with_payload(payload.0))))
213    }
214
215    /// Lower an input that cannot require accepted enum admission.
216    ///
217    /// Enum input, including nested enum input, stays unresolved and must use
218    /// the accepted catalog admission boundary instead.
219    pub(crate) fn try_into_runtime_non_enum(self) -> Option<Value> {
220        self.0.try_into_runtime_non_enum()
221    }
222
223    /// Lift a runtime value without canonical enum IDs into authored input.
224    pub(crate) fn try_from_runtime_non_enum(value: &Value) -> Option<Self> {
225        PublicValue::try_from_runtime_non_enum(value).map(Self)
226    }
227}
228
229impl<'de> Deserialize<'de> for InputValue {
230    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
231    where
232        D: Deserializer<'de>,
233    {
234        PublicValue::deserialize(deserializer).map(Self)
235    }
236}
237
238impl From<PublicValue> for InputValue {
239    fn from(value: PublicValue) -> Self {
240        Self(value)
241    }
242}
243
244#[cfg(test)]
245impl From<Value> for InputValue {
246    fn from(value: Value) -> Self {
247        Self::try_from_runtime_non_enum(&value)
248            .expect("test runtime-to-input conversion must not contain canonical enum IDs")
249    }
250}
251
252#[cfg(test)]
253impl From<&Value> for InputValue {
254    fn from(value: &Value) -> Self {
255        Self::try_from_runtime_non_enum(value)
256            .expect("test runtime-to-input conversion must not contain canonical enum IDs")
257    }
258}
259
260impl From<&str> for InputValue {
261    fn from(value: &str) -> Self {
262        Self(PublicValue::Text(value.to_string()))
263    }
264}
265
266impl From<String> for InputValue {
267    fn from(value: String) -> Self {
268        Self(PublicValue::Text(value))
269    }
270}
271
272impl From<Vec<u8>> for InputValue {
273    fn from(value: Vec<u8>) -> Self {
274        Self(PublicValue::Blob(value))
275    }
276}
277
278impl From<Blob> for InputValue {
279    fn from(value: Blob) -> Self {
280        Self(PublicValue::Blob(value.to_vec()))
281    }
282}
283
284impl From<bool> for InputValue {
285    fn from(value: bool) -> Self {
286        Self(PublicValue::Bool(value))
287    }
288}
289
290impl From<Account> for InputValue {
291    fn from(value: Account) -> Self {
292        Self(PublicValue::Account(value))
293    }
294}
295
296impl From<Date> for InputValue {
297    fn from(value: Date) -> Self {
298        Self(PublicValue::Date(value))
299    }
300}
301
302impl From<Decimal> for InputValue {
303    fn from(value: Decimal) -> Self {
304        Self(PublicValue::Decimal(value))
305    }
306}
307
308impl From<Duration> for InputValue {
309    fn from(value: Duration) -> Self {
310        Self(PublicValue::Duration(value))
311    }
312}
313
314impl From<Float32> for InputValue {
315    fn from(value: Float32) -> Self {
316        Self(PublicValue::Float32(value))
317    }
318}
319
320impl From<Float64> for InputValue {
321    fn from(value: Float64) -> Self {
322        Self(PublicValue::Float64(value))
323    }
324}
325
326impl From<i128> for InputValue {
327    fn from(value: i128) -> Self {
328        Self(PublicValue::Int128(value))
329    }
330}
331
332impl From<u128> for InputValue {
333    fn from(value: u128) -> Self {
334        Self(PublicValue::Nat128(value))
335    }
336}
337
338impl From<Principal> for InputValue {
339    fn from(value: Principal) -> Self {
340        Self(PublicValue::Principal(value))
341    }
342}
343
344impl From<Subaccount> for InputValue {
345    fn from(value: Subaccount) -> Self {
346        Self(PublicValue::Subaccount(value))
347    }
348}
349
350impl From<Timestamp> for InputValue {
351    fn from(value: Timestamp) -> Self {
352        Self(PublicValue::Timestamp(value))
353    }
354}
355
356impl From<Ulid> for InputValue {
357    fn from(value: Ulid) -> Self {
358        Self(PublicValue::Ulid(value))
359    }
360}
361
362impl From<U256> for InputValue {
363    fn from(value: U256) -> Self {
364        Self(PublicValue::U256(value))
365    }
366}
367
368impl From<()> for InputValue {
369    fn from((): ()) -> Self {
370        Self(PublicValue::Unit)
371    }
372}
373
374impl From<Unit> for InputValue {
375    fn from(_value: Unit) -> Self {
376        Self(PublicValue::Unit)
377    }
378}
379
380impl<T> From<Option<T>> for InputValue
381where
382    T: Into<Self>,
383{
384    fn from(value: Option<T>) -> Self {
385        match value {
386            Some(value) => value.into(),
387            None => Self(PublicValue::Null),
388        }
389    }
390}
391
392impl<T> From<Box<T>> for InputValue
393where
394    T: Into<Self>,
395{
396    fn from(value: Box<T>) -> Self {
397        (*value).into()
398    }
399}
400
401impl<E> From<Id<E>> for InputValue
402where
403    E: EntityKey,
404    E::Key: Into<Self>,
405{
406    fn from(value: Id<E>) -> Self {
407        value.into_key().into()
408    }
409}
410
411impl<E> From<&Id<E>> for InputValue
412where
413    E: EntityKey,
414    E::Key: Into<Self>,
415{
416    fn from(value: &Id<E>) -> Self {
417        value.key().into()
418    }
419}
420
421macro_rules! impl_input_value_int {
422    ($($ty:ty),* $(,)?) => {
423        $(
424            impl From<$ty> for InputValue {
425                fn from(value: $ty) -> Self {
426                    Self(PublicValue::Int64(i64::from(value)))
427                }
428            }
429        )*
430    };
431}
432
433macro_rules! impl_input_value_nat {
434    ($($ty:ty),* $(,)?) => {
435        $(
436            impl From<$ty> for InputValue {
437                fn from(value: $ty) -> Self {
438                    Self(PublicValue::Nat64(u64::from(value)))
439                }
440            }
441        )*
442    };
443}
444
445impl_input_value_int!(i8, i16, i32, i64);
446impl_input_value_nat!(u8, u16, u32, u64);
447
448impl serde::Serialize for InputValue {
449    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
450        serde::Serialize::serialize(&self.0, serializer)
451    }
452}
453
454///
455/// TESTS
456///
457
458#[cfg(test)]
459mod tests {
460    use crate::value::{InputValue, Value, hash_value};
461    use std::cmp::Ordering;
462
463    #[test]
464    fn input_maps_lower_to_canonical_runtime_values() {
465        let entries = vec![
466            (Value::Text("z".to_string()), Value::Null),
467            (Value::Nat64(1), Value::List(vec![Value::Bool(true)])),
468            (Value::Int64(1), Value::Text("signed".to_string())),
469            (Value::Text("a".to_string()), Value::Unit),
470        ];
471        let expected = Value::from_map(entries.clone()).expect("valid map");
472        for rotation in 0..entries.len() {
473            let mut permuted = entries.clone();
474            permuted.rotate_left(rotation);
475            for reverse in [false, true] {
476                if reverse {
477                    permuted.reverse();
478                }
479                let input = InputValue::map(
480                    permuted
481                        .iter()
482                        .map(|(key, value)| (InputValue::from(key), InputValue::from(value)))
483                        .collect(),
484                );
485                let actual = input.try_into_runtime_non_enum().expect("valid map");
486                assert_eq!(actual, expected);
487                assert_eq!(Value::canonical_cmp(&actual, &expected), Ordering::Equal);
488                assert_eq!(hash_value(&actual).unwrap(), hash_value(&expected).unwrap());
489            }
490        }
491        assert_eq!(
492            InputValue::map(Vec::new()).try_into_runtime_non_enum(),
493            Some(Value::from_map(Vec::new()).unwrap()),
494        );
495    }
496
497    #[test]
498    fn input_map_lowering_normalizes_nested_maps() {
499        let nested = InputValue::map(vec![
500            (InputValue::from("z"), InputValue::null()),
501            (InputValue::from("a"), InputValue::nat64(7)),
502        ]);
503        let expected = Value::from_map(vec![
504            (Value::Text("a".to_string()), Value::Nat64(7)),
505            (Value::Text("z".to_string()), Value::Null),
506        ])
507        .unwrap();
508        assert_eq!(
509            InputValue::list(vec![nested.clone()]).try_into_runtime_non_enum(),
510            Some(Value::List(vec![expected.clone()])),
511        );
512        assert_eq!(
513            InputValue::map(vec![(InputValue::from("nested"), nested)]).try_into_runtime_non_enum(),
514            Some(Value::from_map(vec![(Value::Text("nested".to_string()), expected)]).unwrap()),
515        );
516    }
517
518    #[test]
519    fn input_map_lowering_rejects_invalid_keys_and_unresolved_enums_recursively() {
520        let invalid = [
521            vec![(InputValue::null(), InputValue::nat64(1))],
522            vec![(InputValue::list(Vec::new()), InputValue::nat64(1))],
523            vec![(InputValue::map(Vec::new()), InputValue::nat64(1))],
524            vec![
525                (InputValue::from("a"), InputValue::nat64(1)),
526                (InputValue::from("a"), InputValue::nat64(2)),
527            ],
528            vec![(InputValue::loose_enum("Active"), InputValue::nat64(1))],
529            vec![(InputValue::from("a"), InputValue::loose_enum("Active"))],
530        ];
531        for entries in invalid {
532            let input = InputValue::map(entries);
533            assert_eq!(input.clone().try_into_runtime_non_enum(), None);
534            assert_eq!(
535                InputValue::list(vec![input.clone()]).try_into_runtime_non_enum(),
536                None,
537            );
538            assert_eq!(
539                InputValue::map(vec![(InputValue::from("nested"), input)])
540                    .try_into_runtime_non_enum(),
541                None,
542            );
543        }
544    }
545
546    #[test]
547    fn runtime_to_input_value_keeps_recursive_collection_shape() {
548        let runtime = Value::List(vec![
549            Value::Nat64(7),
550            Value::Map(vec![(Value::Text("x".to_string()), Value::Bool(true))]),
551        ]);
552
553        assert_eq!(
554            InputValue::from(runtime),
555            InputValue::list(vec![
556                InputValue::nat64(7),
557                InputValue::map(vec![(InputValue::from("x"), InputValue::boolean(true))]),
558            ]),
559        );
560    }
561
562    #[test]
563    fn unresolved_enum_input_cannot_lower_without_admission() {
564        let direct = InputValue::loose_enum("Active");
565        let nested = InputValue::list(vec![direct.clone()]);
566
567        assert_eq!(direct.try_into_runtime_non_enum(), None);
568        assert_eq!(nested.try_into_runtime_non_enum(), None);
569    }
570}