Skip to main content

icydb_core/value/
output.rs

1use crate::{
2    types::{
3        Account, Date, Decimal, Duration, Float32, Float64, IntBig, NatBig, Principal, Subaccount,
4        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    #[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//
51// OutputValueEnum
52//
53// Output-side enum payload contract paired with `OutputValue`.
54// Payload stays recursive through `OutputValue` so public boundary conversion
55// remains total for data-carrying enum values already representable by
56// runtime `Value`.
57//
58
59#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
60pub struct OutputValueEnum {
61    variant: String,
62    path: Option<String>,
63    payload: Option<Box<OutputValue>>,
64}
65
66impl OutputValueEnum {
67    #[must_use]
68    pub const fn variant(&self) -> &str {
69        self.variant.as_str()
70    }
71
72    #[must_use]
73    pub fn path(&self) -> Option<&str> {
74        self.path.as_deref()
75    }
76
77    #[must_use]
78    pub fn payload(&self) -> Option<&OutputValue> {
79        self.payload.as_deref()
80    }
81}
82
83impl From<Value> for OutputValue {
84    fn from(value: Value) -> Self {
85        Self::from(&value)
86    }
87}
88
89impl From<&Value> for OutputValue {
90    fn from(value: &Value) -> Self {
91        match value {
92            Value::Account(value) => Self::Account(*value),
93            Value::Blob(value) => Self::Blob(value.clone()),
94            Value::Bool(value) => Self::Bool(*value),
95            Value::Date(value) => Self::Date(*value),
96            Value::Decimal(value) => Self::Decimal(*value),
97            Value::Duration(value) => Self::Duration(*value),
98            Value::Enum(value) => Self::Enum(OutputValueEnum::from(value)),
99            Value::Float32(value) => Self::Float32(*value),
100            Value::Float64(value) => Self::Float64(*value),
101            Value::Int64(value) => Self::Int64(*value),
102            Value::Int128(value) => Self::Int128(*value),
103            Value::IntBig(value) => Self::IntBig(value.clone()),
104            Value::List(items) => Self::List(items.iter().map(Self::from).collect()),
105            Value::Map(entries) => Self::Map(
106                entries
107                    .iter()
108                    .map(|(key, value)| (Self::from(key), Self::from(value)))
109                    .collect(),
110            ),
111            Value::Null => Self::Null,
112            Value::Principal(value) => Self::Principal(*value),
113            Value::Subaccount(value) => Self::Subaccount(*value),
114            Value::Text(value) => Self::Text(value.clone()),
115            Value::Timestamp(value) => Self::Timestamp(*value),
116            Value::Nat64(value) => Self::Nat64(*value),
117            Value::Nat128(value) => Self::Nat128(*value),
118            Value::NatBig(value) => Self::NatBig(value.clone()),
119            Value::Ulid(value) => Self::Ulid(*value),
120            Value::Unit => Self::Unit,
121        }
122    }
123}
124
125impl From<ValueEnum> for OutputValueEnum {
126    fn from(value: ValueEnum) -> Self {
127        Self::from(&value)
128    }
129}
130
131impl From<&ValueEnum> for OutputValueEnum {
132    fn from(value: &ValueEnum) -> Self {
133        Self {
134            variant: value.variant().to_string(),
135            path: value.path().map(ToString::to_string),
136            payload: value
137                .payload()
138                .map(|payload| Box::new(OutputValue::from(payload))),
139        }
140    }
141}
142
143///
144/// TESTS
145///
146
147#[cfg(test)]
148mod tests {
149    use crate::value::{OutputValue, OutputValueEnum, Value, ValueEnum};
150
151    #[test]
152    fn output_value_from_runtime_value_keeps_recursive_collection_shape() {
153        let runtime = Value::List(vec![
154            Value::Nat64(7),
155            Value::Map(vec![(Value::Text("x".to_string()), Value::Bool(true))]),
156        ]);
157
158        assert_eq!(
159            OutputValue::from(runtime),
160            OutputValue::List(vec![
161                OutputValue::Nat64(7),
162                OutputValue::Map(vec![(
163                    OutputValue::Text("x".to_string()),
164                    OutputValue::Bool(true),
165                )]),
166            ]),
167        );
168    }
169
170    #[test]
171    fn output_value_enum_from_runtime_enum_keeps_payload() {
172        let runtime =
173            ValueEnum::new("Example", Some("test::OutputEnum")).with_payload(Value::Nat64(9));
174
175        assert_eq!(
176            OutputValueEnum::from(runtime),
177            OutputValueEnum {
178                variant: "Example".to_string(),
179                path: Some("test::OutputEnum".to_string()),
180                payload: Some(Box::new(OutputValue::Nat64(9))),
181            },
182        );
183    }
184}