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