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#[repr(transparent)]
21#[derive(Clone, Debug, Eq, PartialEq)]
22pub struct InputValue(PublicValue);
23
24impl InputValue {
25 impl_public_value_scalar_constructors!("input");
26
27 #[must_use]
29 pub const fn from_public(value: PublicValue) -> Self {
30 Self(value)
31 }
32
33 #[must_use]
35 pub const fn as_public(&self) -> &PublicValue {
36 &self.0
37 }
38
39 #[must_use]
41 pub fn into_public(self) -> PublicValue {
42 self.0
43 }
44
45 #[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 #[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 #[must_use]
72 pub const fn is_null(&self) -> bool {
73 matches!(self.0, PublicValue::Null)
74 }
75
76 #[must_use]
78 pub fn enum_value(variant: &str, path: Option<&str>) -> Self {
79 Self(PublicValue::Enum(PublicEnumValue::new(variant, path)))
80 }
81
82 #[must_use]
84 pub fn loose_enum(variant: impl Into<String>) -> Self {
85 Self(PublicValue::Enum(PublicEnumValue::loose(variant)))
86 }
87
88 #[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 pub(crate) fn try_into_runtime_non_enum(self) -> Option<Value> {
102 self.0.try_into_runtime_non_enum()
103 }
104
105 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#[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}