1use crate::{
2 traits::EntityKey,
3 types::{
4 Account, Blob, Date, Decimal, Duration, Float32, Float64, Id, IntBig, NatBig, Principal,
5 Subaccount, Timestamp, Ulid, Unit,
6 },
7 value::Value,
8};
9use candid::CandidType;
10use serde::Deserialize;
11
12#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
21pub enum InputValue {
22 Account(Account),
23 Blob(Vec<u8>),
24 Bool(bool),
25 Date(Date),
26 Decimal(Decimal),
27 Duration(Duration),
28 Enum(InputValueEnum),
29 Float32(Float32),
30 Float64(Float64),
31 #[serde(rename = "Int")]
32 Int64(i64),
33 Int128(i128),
34 IntBig(IntBig),
35 List(Vec<Self>),
36 Map(Vec<(Self, Self)>),
37 Null,
38 Principal(Principal),
39 Subaccount(Subaccount),
40 Text(String),
41 Timestamp(Timestamp),
42 #[serde(rename = "Nat")]
43 Nat64(u64),
44 Nat128(u128),
45 NatBig(NatBig),
46 Ulid(Ulid),
47 Unit,
48}
49
50#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
59pub struct InputValueEnum {
60 variant: String,
61 path: Option<String>,
62 payload: Option<Box<InputValue>>,
63}
64
65impl InputValueEnum {
66 #[must_use]
68 pub fn new(variant: &str, path: Option<&str>) -> Self {
69 Self {
70 variant: variant.to_string(),
71 path: path.map(ToString::to_string),
72 payload: None,
73 }
74 }
75
76 #[must_use]
78 pub fn loose(variant: impl Into<String>) -> Self {
79 Self {
80 variant: variant.into(),
81 path: None,
82 payload: None,
83 }
84 }
85
86 #[must_use]
88 pub fn with_payload(mut self, payload: InputValue) -> Self {
89 self.payload = Some(Box::new(payload));
90 self
91 }
92
93 #[must_use]
94 pub const fn variant(&self) -> &str {
95 self.variant.as_str()
96 }
97
98 #[must_use]
99 pub fn path(&self) -> Option<&str> {
100 self.path.as_deref()
101 }
102
103 #[must_use]
104 pub fn payload(&self) -> Option<&InputValue> {
105 self.payload.as_deref()
106 }
107
108 pub(crate) fn into_parts(self) -> (String, Option<String>, Option<InputValue>) {
109 (
110 self.variant,
111 self.path,
112 self.payload.map(|payload| *payload),
113 )
114 }
115}
116
117impl InputValue {
118 pub(crate) fn try_into_runtime_non_enum(self) -> Option<Value> {
123 Some(match self {
124 Self::Account(value) => Value::Account(value),
125 Self::Blob(value) => Value::Blob(value),
126 Self::Bool(value) => Value::Bool(value),
127 Self::Date(value) => Value::Date(value),
128 Self::Decimal(value) => Value::Decimal(value),
129 Self::Duration(value) => Value::Duration(value),
130 Self::Enum(_) => return None,
131 Self::Float32(value) => Value::Float32(value),
132 Self::Float64(value) => Value::Float64(value),
133 Self::Int64(value) => Value::Int64(value),
134 Self::Int128(value) => Value::Int128(value),
135 Self::IntBig(value) => Value::IntBig(value),
136 Self::List(values) => Value::List(
137 values
138 .into_iter()
139 .map(Self::try_into_runtime_non_enum)
140 .collect::<Option<Vec<_>>>()?,
141 ),
142 Self::Map(entries) => Value::Map(
143 entries
144 .into_iter()
145 .map(|(key, value)| {
146 Some((
147 key.try_into_runtime_non_enum()?,
148 value.try_into_runtime_non_enum()?,
149 ))
150 })
151 .collect::<Option<Vec<_>>>()?,
152 ),
153 Self::Null => Value::Null,
154 Self::Principal(value) => Value::Principal(value),
155 Self::Subaccount(value) => Value::Subaccount(value),
156 Self::Text(value) => Value::Text(value),
157 Self::Timestamp(value) => Value::Timestamp(value),
158 Self::Nat64(value) => Value::Nat64(value),
159 Self::Nat128(value) => Value::Nat128(value),
160 Self::NatBig(value) => Value::NatBig(value),
161 Self::Ulid(value) => Value::Ulid(value),
162 Self::Unit => Value::Unit,
163 })
164 }
165
166 pub(crate) fn try_from_runtime_non_enum(value: &Value) -> Option<Self> {
168 Some(match value {
169 Value::Account(value) => Self::Account(*value),
170 Value::Blob(value) => Self::Blob(value.clone()),
171 Value::Bool(value) => Self::Bool(*value),
172 Value::Date(value) => Self::Date(*value),
173 Value::Decimal(value) => Self::Decimal(*value),
174 Value::Duration(value) => Self::Duration(*value),
175 Value::Enum(_) => return None,
176 Value::Float32(value) => Self::Float32(*value),
177 Value::Float64(value) => Self::Float64(*value),
178 Value::Int64(value) => Self::Int64(*value),
179 Value::Int128(value) => Self::Int128(*value),
180 Value::IntBig(value) => Self::IntBig(value.clone()),
181 Value::List(values) => Self::List(
182 values
183 .iter()
184 .map(Self::try_from_runtime_non_enum)
185 .collect::<Option<Vec<_>>>()?,
186 ),
187 Value::Map(entries) => Self::Map(
188 entries
189 .iter()
190 .map(|(key, value)| {
191 Some((
192 Self::try_from_runtime_non_enum(key)?,
193 Self::try_from_runtime_non_enum(value)?,
194 ))
195 })
196 .collect::<Option<Vec<_>>>()?,
197 ),
198 Value::Null => Self::Null,
199 Value::Principal(value) => Self::Principal(*value),
200 Value::Subaccount(value) => Self::Subaccount(*value),
201 Value::Text(value) => Self::Text(value.clone()),
202 Value::Timestamp(value) => Self::Timestamp(*value),
203 Value::Nat64(value) => Self::Nat64(*value),
204 Value::Nat128(value) => Self::Nat128(*value),
205 Value::NatBig(value) => Self::NatBig(value.clone()),
206 Value::Ulid(value) => Self::Ulid(*value),
207 Value::Unit => Self::Unit,
208 })
209 }
210}
211
212#[cfg(test)]
213impl From<Value> for InputValue {
214 fn from(value: Value) -> Self {
215 Self::try_from_runtime_non_enum(&value)
216 .expect("test runtime-to-input conversion must not contain canonical enum IDs")
217 }
218}
219
220#[cfg(test)]
221impl From<&Value> for InputValue {
222 fn from(value: &Value) -> Self {
223 Self::try_from_runtime_non_enum(value)
224 .expect("test runtime-to-input conversion must not contain canonical enum IDs")
225 }
226}
227
228impl From<&str> for InputValue {
229 fn from(value: &str) -> Self {
230 Self::Text(value.to_string())
231 }
232}
233
234impl From<String> for InputValue {
235 fn from(value: String) -> Self {
236 Self::Text(value)
237 }
238}
239
240impl From<Vec<u8>> for InputValue {
241 fn from(value: Vec<u8>) -> Self {
242 Self::Blob(value)
243 }
244}
245
246impl From<Blob> for InputValue {
247 fn from(value: Blob) -> Self {
248 Self::Blob(value.to_vec())
249 }
250}
251
252impl From<bool> for InputValue {
253 fn from(value: bool) -> Self {
254 Self::Bool(value)
255 }
256}
257
258impl From<Account> for InputValue {
259 fn from(value: Account) -> Self {
260 Self::Account(value)
261 }
262}
263
264impl From<Date> for InputValue {
265 fn from(value: Date) -> Self {
266 Self::Date(value)
267 }
268}
269
270impl From<Decimal> for InputValue {
271 fn from(value: Decimal) -> Self {
272 Self::Decimal(value)
273 }
274}
275
276impl From<Duration> for InputValue {
277 fn from(value: Duration) -> Self {
278 Self::Duration(value)
279 }
280}
281
282impl From<Float32> for InputValue {
283 fn from(value: Float32) -> Self {
284 Self::Float32(value)
285 }
286}
287
288impl From<Float64> for InputValue {
289 fn from(value: Float64) -> Self {
290 Self::Float64(value)
291 }
292}
293
294impl From<IntBig> for InputValue {
295 fn from(value: IntBig) -> Self {
296 Self::IntBig(value)
297 }
298}
299
300impl From<i128> for InputValue {
301 fn from(value: i128) -> Self {
302 Self::Int128(value)
303 }
304}
305
306impl From<NatBig> for InputValue {
307 fn from(value: NatBig) -> Self {
308 Self::NatBig(value)
309 }
310}
311
312impl From<u128> for InputValue {
313 fn from(value: u128) -> Self {
314 Self::Nat128(value)
315 }
316}
317
318impl From<Principal> for InputValue {
319 fn from(value: Principal) -> Self {
320 Self::Principal(value)
321 }
322}
323
324impl From<Subaccount> for InputValue {
325 fn from(value: Subaccount) -> Self {
326 Self::Subaccount(value)
327 }
328}
329
330impl From<Timestamp> for InputValue {
331 fn from(value: Timestamp) -> Self {
332 Self::Timestamp(value)
333 }
334}
335
336impl From<Ulid> for InputValue {
337 fn from(value: Ulid) -> Self {
338 Self::Ulid(value)
339 }
340}
341
342impl From<()> for InputValue {
343 fn from((): ()) -> Self {
344 Self::Unit
345 }
346}
347
348impl From<Unit> for InputValue {
349 fn from(_value: Unit) -> Self {
350 Self::Unit
351 }
352}
353
354impl<T> From<Option<T>> for InputValue
355where
356 T: Into<Self>,
357{
358 fn from(value: Option<T>) -> Self {
359 match value {
360 Some(value) => value.into(),
361 None => Self::Null,
362 }
363 }
364}
365
366impl<T> From<Box<T>> for InputValue
367where
368 T: Into<Self>,
369{
370 fn from(value: Box<T>) -> Self {
371 (*value).into()
372 }
373}
374
375impl<E> From<Id<E>> for InputValue
376where
377 E: EntityKey,
378 E::Key: Into<Self>,
379{
380 fn from(value: Id<E>) -> Self {
381 value.into_key().into()
382 }
383}
384
385impl<E> From<&Id<E>> for InputValue
386where
387 E: EntityKey,
388 E::Key: Into<Self>,
389{
390 fn from(value: &Id<E>) -> Self {
391 value.key().into()
392 }
393}
394
395macro_rules! impl_input_value_int {
396 ($($ty:ty),* $(,)?) => {
397 $(
398 impl From<$ty> for InputValue {
399 fn from(value: $ty) -> Self {
400 Self::Int64(i64::from(value))
401 }
402 }
403 )*
404 };
405}
406
407macro_rules! impl_input_value_nat {
408 ($($ty:ty),* $(,)?) => {
409 $(
410 impl From<$ty> for InputValue {
411 fn from(value: $ty) -> Self {
412 Self::Nat64(u64::from(value))
413 }
414 }
415 )*
416 };
417}
418
419impl_input_value_int!(i8, i16, i32, i64);
420impl_input_value_nat!(u8, u16, u32, u64);
421
422#[cfg(test)]
427mod tests {
428 use crate::value::{InputValue, InputValueEnum, Value};
429
430 #[test]
431 fn runtime_to_input_value_keeps_recursive_collection_shape() {
432 let runtime = Value::List(vec![
433 Value::Nat64(7),
434 Value::Map(vec![(Value::Text("x".to_string()), Value::Bool(true))]),
435 ]);
436
437 assert_eq!(
438 InputValue::from(runtime),
439 InputValue::List(vec![
440 InputValue::Nat64(7),
441 InputValue::Map(vec![(
442 InputValue::Text("x".to_string()),
443 InputValue::Bool(true),
444 )]),
445 ]),
446 );
447 }
448
449 #[test]
450 fn unresolved_enum_input_cannot_lower_without_admission() {
451 let direct = InputValue::Enum(InputValueEnum::loose("Active"));
452 let nested = InputValue::List(vec![direct.clone()]);
453
454 assert_eq!(direct.try_into_runtime_non_enum(), None);
455 assert_eq!(nested.try_into_runtime_non_enum(), None);
456 }
457}