Skip to main content

icydb_core/value/
output.rs

1use crate::{
2    types::{
3        Account, Date, Decimal, Duration, Float32, Float64, Int, Int128, Nat, Nat128, Principal,
4        Subaccount, Timestamp, Ulid,
5    },
6    value::{Value, ValueEnum},
7};
8use candid::CandidType;
9use serde::Deserialize;
10
11//
12// OutputValue
13//
14// Public output-side value boundary used by API and wire surfaces.
15// This stays separate from runtime `Value` so public result payloads can move
16// off the internal execution representation without forcing a storage or
17// planner rewrite in the same slice.
18//
19
20#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
21pub enum OutputValue {
22    Account(Account),
23    Blob(Vec<u8>),
24    Bool(bool),
25    Date(Date),
26    Decimal(Decimal),
27    Duration(Duration),
28    Enum(OutputValueEnum),
29    Float32(Float32),
30    Float64(Float64),
31    Int(i64),
32    Int128(Int128),
33    IntBig(Int),
34    List(Vec<Self>),
35    Map(Vec<(Self, Self)>),
36    Null,
37    Principal(Principal),
38    Subaccount(Subaccount),
39    Text(String),
40    Timestamp(Timestamp),
41    Uint(u64),
42    Uint128(Nat128),
43    UintBig(Nat),
44    Ulid(Ulid),
45    Unit,
46}
47
48//
49// OutputValueEnum
50//
51// Output-side enum payload contract paired with `OutputValue`.
52// Payload stays recursive through `OutputValue` so public boundary conversion
53// remains total for data-carrying enum values already representable by
54// runtime `Value`.
55//
56
57#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
58pub struct OutputValueEnum {
59    variant: String,
60    path: Option<String>,
61    payload: Option<Box<OutputValue>>,
62}
63
64impl OutputValueEnum {
65    #[must_use]
66    pub const fn variant(&self) -> &str {
67        self.variant.as_str()
68    }
69
70    #[must_use]
71    pub fn path(&self) -> Option<&str> {
72        self.path.as_deref()
73    }
74
75    #[must_use]
76    pub fn payload(&self) -> Option<&OutputValue> {
77        self.payload.as_deref()
78    }
79}
80
81impl From<Value> for OutputValue {
82    fn from(value: Value) -> Self {
83        Self::from(&value)
84    }
85}
86
87impl From<&Value> for OutputValue {
88    fn from(value: &Value) -> Self {
89        match value {
90            Value::Account(value) => Self::Account(*value),
91            Value::Blob(value) => Self::Blob(value.clone()),
92            Value::Bool(value) => Self::Bool(*value),
93            Value::Date(value) => Self::Date(*value),
94            Value::Decimal(value) => Self::Decimal(*value),
95            Value::Duration(value) => Self::Duration(*value),
96            Value::Enum(value) => Self::Enum(OutputValueEnum::from(value)),
97            Value::Float32(value) => Self::Float32(*value),
98            Value::Float64(value) => Self::Float64(*value),
99            Value::Int(value) => Self::Int(*value),
100            Value::Int128(value) => Self::Int128(*value),
101            Value::IntBig(value) => Self::IntBig(value.clone()),
102            Value::List(items) => Self::List(items.iter().map(Self::from).collect()),
103            Value::Map(entries) => Self::Map(
104                entries
105                    .iter()
106                    .map(|(key, value)| (Self::from(key), Self::from(value)))
107                    .collect(),
108            ),
109            Value::Null => Self::Null,
110            Value::Principal(value) => Self::Principal(*value),
111            Value::Subaccount(value) => Self::Subaccount(*value),
112            Value::Text(value) => Self::Text(value.clone()),
113            Value::Timestamp(value) => Self::Timestamp(*value),
114            Value::Uint(value) => Self::Uint(*value),
115            Value::Uint128(value) => Self::Uint128(*value),
116            Value::UintBig(value) => Self::UintBig(value.clone()),
117            Value::Ulid(value) => Self::Ulid(*value),
118            Value::Unit => Self::Unit,
119        }
120    }
121}
122
123impl From<ValueEnum> for OutputValueEnum {
124    fn from(value: ValueEnum) -> Self {
125        Self::from(&value)
126    }
127}
128
129impl From<&ValueEnum> for OutputValueEnum {
130    fn from(value: &ValueEnum) -> Self {
131        Self {
132            variant: value.variant().to_string(),
133            path: value.path().map(ToString::to_string),
134            payload: value
135                .payload()
136                .map(|payload| Box::new(OutputValue::from(payload))),
137        }
138    }
139}
140
141///
142/// TESTS
143///
144
145#[cfg(test)]
146mod tests {
147    use crate::value::{OutputValue, OutputValueEnum, Value, ValueEnum};
148
149    #[test]
150    fn output_value_from_runtime_value_keeps_recursive_collection_shape() {
151        let runtime = Value::List(vec![
152            Value::Uint(7),
153            Value::Map(vec![(Value::Text("x".to_string()), Value::Bool(true))]),
154        ]);
155
156        assert_eq!(
157            OutputValue::from(runtime),
158            OutputValue::List(vec![
159                OutputValue::Uint(7),
160                OutputValue::Map(vec![(
161                    OutputValue::Text("x".to_string()),
162                    OutputValue::Bool(true),
163                )]),
164            ]),
165        );
166    }
167
168    #[test]
169    fn output_value_enum_from_runtime_enum_keeps_payload() {
170        let runtime =
171            ValueEnum::new("Example", Some("test::OutputEnum")).with_payload(Value::Uint(9));
172
173        assert_eq!(
174            OutputValueEnum::from(runtime),
175            OutputValueEnum {
176                variant: "Example".to_string(),
177                path: Some("test::OutputEnum".to_string()),
178                payload: Some(Box::new(OutputValue::Uint(9))),
179            },
180        );
181    }
182}