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 #[must_use]
27 pub const fn from_public(value: PublicValue) -> Self {
28 Self(value)
29 }
30
31 #[must_use]
33 pub const fn as_public(&self) -> &PublicValue {
34 &self.0
35 }
36
37 #[must_use]
39 pub fn into_public(self) -> PublicValue {
40 self.0
41 }
42
43 #[must_use]
45 pub const fn account(value: Account) -> Self {
46 Self(PublicValue::Account(value))
47 }
48
49 #[must_use]
51 pub const fn blob(value: Vec<u8>) -> Self {
52 Self(PublicValue::Blob(value))
53 }
54
55 #[must_use]
57 pub const fn boolean(value: bool) -> Self {
58 Self(PublicValue::Bool(value))
59 }
60
61 #[must_use]
63 pub const fn date(value: Date) -> Self {
64 Self(PublicValue::Date(value))
65 }
66
67 #[must_use]
69 pub const fn decimal(value: Decimal) -> Self {
70 Self(PublicValue::Decimal(value))
71 }
72
73 #[must_use]
75 pub const fn duration(value: Duration) -> Self {
76 Self(PublicValue::Duration(value))
77 }
78
79 #[must_use]
81 pub const fn float32(value: Float32) -> Self {
82 Self(PublicValue::Float32(value))
83 }
84
85 #[must_use]
87 pub const fn float64(value: Float64) -> Self {
88 Self(PublicValue::Float64(value))
89 }
90
91 #[must_use]
93 pub const fn int64(value: i64) -> Self {
94 Self(PublicValue::Int64(value))
95 }
96
97 #[must_use]
99 pub const fn int128(value: i128) -> Self {
100 Self(PublicValue::Int128(value))
101 }
102
103 #[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 #[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 #[must_use]
130 pub const fn null() -> Self {
131 Self(PublicValue::Null)
132 }
133
134 #[must_use]
136 pub const fn is_null(&self) -> bool {
137 matches!(self.0, PublicValue::Null)
138 }
139
140 #[must_use]
142 pub const fn principal(value: Principal) -> Self {
143 Self(PublicValue::Principal(value))
144 }
145
146 #[must_use]
148 pub const fn subaccount(value: Subaccount) -> Self {
149 Self(PublicValue::Subaccount(value))
150 }
151
152 #[must_use]
154 pub const fn text(value: String) -> Self {
155 Self(PublicValue::Text(value))
156 }
157
158 #[must_use]
160 pub const fn timestamp(value: Timestamp) -> Self {
161 Self(PublicValue::Timestamp(value))
162 }
163
164 #[must_use]
166 pub const fn nat64(value: u64) -> Self {
167 Self(PublicValue::Nat64(value))
168 }
169
170 #[must_use]
172 pub const fn nat128(value: u128) -> Self {
173 Self(PublicValue::Nat128(value))
174 }
175
176 #[must_use]
178 pub const fn ulid(value: Ulid) -> Self {
179 Self(PublicValue::Ulid(value))
180 }
181
182 #[must_use]
184 pub const fn unit() -> Self {
185 Self(PublicValue::Unit)
186 }
187
188 #[must_use]
190 pub const fn u256(value: U256) -> Self {
191 Self(PublicValue::U256(value))
192 }
193
194 #[must_use]
196 pub fn enum_value(variant: &str, path: Option<&str>) -> Self {
197 Self(PublicValue::Enum(PublicEnumValue::new(variant, path)))
198 }
199
200 #[must_use]
202 pub fn loose_enum(variant: impl Into<String>) -> Self {
203 Self(PublicValue::Enum(PublicEnumValue::loose(variant)))
204 }
205
206 #[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 pub(crate) fn try_into_runtime_non_enum(self) -> Option<Value> {
220 self.0.try_into_runtime_non_enum()
221 }
222
223 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#[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}