Skip to main content

icydb_core/value/
public.rs

1use crate::{
2    codec::hex::encode_hex_lower,
3    types::{
4        Account, Date, Decimal, Duration, Float32, Float64, Principal, Subaccount, Timestamp, U256,
5        Ulid,
6    },
7    value::Value,
8};
9
10use serde::Deserialize;
11
12/// Canonical recursive value carried by public input and output boundaries.
13///
14/// `InputValue` and `OutputValue` remain distinct root types. Their recursive
15/// payload is shared here so Wire and Serde derive one value visitor family.
16#[derive(Clone, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
17#[cfg_attr(not(target_family = "wasm"), derive(ic_wire::Wire))]
18pub enum PublicValue {
19    Account(Account),
20    Blob(#[serde(with = "serde_bytes")] Vec<u8>),
21    Bool(bool),
22    Date(Date),
23    Decimal(Decimal),
24    Duration(Duration),
25    Enum(PublicEnumValue),
26    Float32(Float32),
27    Float64(Float64),
28    #[serde(rename = "Int")]
29    Int64(i64),
30    Int128(i128),
31    List(Vec<Self>),
32    Map(Vec<(Self, Self)>),
33    Null,
34    Principal(Principal),
35    Subaccount(Subaccount),
36    Text(String),
37    Timestamp(Timestamp),
38    #[serde(rename = "Nat")]
39    Nat64(u64),
40    Nat128(u128),
41    Ulid(Ulid),
42    Unit,
43    U256(U256),
44}
45
46impl PublicValue {
47    /// Render one public value into the stable row-projection text form.
48    #[must_use]
49    pub fn render_text(&self) -> String {
50        match self {
51            Self::Account(value) => value.to_string(),
52            Self::Blob(value) => render_blob_value(value),
53            Self::Bool(value) => value.to_string(),
54            Self::Date(value) => value.to_string(),
55            Self::Decimal(value) => value.to_string(),
56            Self::Duration(value) => render_duration_value(value.as_millis()),
57            Self::Enum(value) => render_enum(value),
58            Self::Float32(value) => value.to_string(),
59            Self::Float64(value) => value.to_string(),
60            Self::Int64(value) => value.to_string(),
61            Self::Int128(value) => value.to_string(),
62            Self::List(items) => render_list_value(items),
63            Self::Map(entries) => render_map_value(entries),
64            Self::Null => "null".to_string(),
65            Self::Principal(value) => value.to_string(),
66            Self::Subaccount(value) => value.to_string(),
67            Self::Text(value) => value.clone(),
68            Self::Timestamp(value) => value.as_millis().to_string(),
69            Self::Nat64(value) => value.to_string(),
70            Self::Nat128(value) => value.to_string(),
71            Self::Ulid(value) => value.to_string(),
72            Self::Unit => "()".to_string(),
73            Self::U256(value) => value.to_string(),
74        }
75    }
76
77    pub(crate) fn try_into_runtime_non_enum(self) -> Option<Value> {
78        Some(match self {
79            Self::Account(value) => Value::Account(value),
80            Self::Blob(value) => Value::Blob(value),
81            Self::Bool(value) => Value::Bool(value),
82            Self::Date(value) => Value::Date(value),
83            Self::Decimal(value) => Value::Decimal(value),
84            Self::Duration(value) => Value::Duration(value),
85            Self::Enum(_) => return None,
86            Self::Float32(value) => Value::Float32(value),
87            Self::Float64(value) => Value::Float64(value),
88            Self::Int64(value) => Value::Int64(value),
89            Self::Int128(value) => Value::Int128(value),
90            Self::List(values) => Value::List(
91                values
92                    .into_iter()
93                    .map(Self::try_into_runtime_non_enum)
94                    .collect::<Option<Vec<_>>>()?,
95            ),
96            Self::Map(entries) => Value::from_map(
97                entries
98                    .into_iter()
99                    .map(|(key, value)| {
100                        Some((
101                            key.try_into_runtime_non_enum()?,
102                            value.try_into_runtime_non_enum()?,
103                        ))
104                    })
105                    .collect::<Option<Vec<_>>>()?,
106            )
107            .ok()?,
108            Self::Null => Value::Null,
109            Self::Principal(value) => Value::Principal(value),
110            Self::Subaccount(value) => Value::Subaccount(value),
111            Self::Text(value) => Value::Text(value),
112            Self::Timestamp(value) => Value::Timestamp(value),
113            Self::Nat64(value) => Value::Nat64(value),
114            Self::Nat128(value) => Value::Nat128(value),
115            Self::Ulid(value) => Value::Ulid(value),
116            Self::Unit => Value::Unit,
117            Self::U256(value) => Value::U256(value),
118        })
119    }
120
121    pub(crate) fn try_from_runtime_non_enum(value: &Value) -> Option<Self> {
122        Some(match value {
123            Value::Account(value) => Self::Account(*value),
124            Value::Blob(value) => Self::Blob(value.clone()),
125            Value::Bool(value) => Self::Bool(*value),
126            Value::Date(value) => Self::Date(*value),
127            Value::Decimal(value) => Self::Decimal(*value),
128            Value::Duration(value) => Self::Duration(*value),
129            Value::Enum(_) => return None,
130            Value::Float32(value) => Self::Float32(*value),
131            Value::Float64(value) => Self::Float64(*value),
132            Value::Int64(value) => Self::Int64(*value),
133            Value::Int128(value) => Self::Int128(*value),
134            Value::List(values) => Self::List(
135                values
136                    .iter()
137                    .map(Self::try_from_runtime_non_enum)
138                    .collect::<Option<Vec<_>>>()?,
139            ),
140            Value::Map(entries) => Self::Map(
141                entries
142                    .iter()
143                    .map(|(key, value)| {
144                        Some((
145                            Self::try_from_runtime_non_enum(key)?,
146                            Self::try_from_runtime_non_enum(value)?,
147                        ))
148                    })
149                    .collect::<Option<Vec<_>>>()?,
150            ),
151            Value::Null => Self::Null,
152            Value::Principal(value) => Self::Principal(*value),
153            Value::Subaccount(value) => Self::Subaccount(*value),
154            Value::Text(value) => Self::Text(value.clone()),
155            Value::Timestamp(value) => Self::Timestamp(*value),
156            Value::Nat64(value) => Self::Nat64(*value),
157            Value::Nat128(value) => Self::Nat128(*value),
158            Value::Ulid(value) => Self::Ulid(*value),
159            Value::Unit => Self::Unit,
160            Value::U256(value) => Self::U256(*value),
161        })
162    }
163}
164
165/// Enum metadata shared by public input and output values.
166#[derive(Clone, Debug, Deserialize, Eq, PartialEq, serde::Serialize)]
167#[cfg_attr(not(target_family = "wasm"), derive(ic_wire::Wire))]
168pub struct PublicEnumValue {
169    variant: String,
170    path: Option<String>,
171    payload: Option<Box<PublicValue>>,
172}
173
174impl PublicEnumValue {
175    /// Build enum metadata for caller-authored input.
176    #[must_use]
177    pub fn new(variant: &str, path: Option<&str>) -> Self {
178        Self {
179            variant: variant.to_string(),
180            path: path.map(ToString::to_string),
181            payload: None,
182        }
183    }
184
185    /// Build enum metadata whose type is resolved from its expected contract.
186    #[must_use]
187    pub fn loose(variant: impl Into<String>) -> Self {
188        Self {
189            variant: variant.into(),
190            path: None,
191            payload: None,
192        }
193    }
194
195    /// Attach one unresolved public payload value.
196    #[must_use]
197    pub fn with_payload(mut self, payload: PublicValue) -> Self {
198        self.payload = Some(Box::new(payload));
199        self
200    }
201
202    /// Return the enum variant name.
203    #[must_use]
204    pub const fn variant(&self) -> &str {
205        self.variant.as_str()
206    }
207
208    /// Return the optional schema-visible type path.
209    #[must_use]
210    pub fn path(&self) -> Option<&str> {
211        self.path.as_deref()
212    }
213
214    /// Return the optional recursive payload.
215    #[must_use]
216    pub fn payload(&self) -> Option<&PublicValue> {
217        self.payload.as_deref()
218    }
219
220    pub(crate) fn from_catalog_parts(
221        variant: &str,
222        path: &str,
223        payload: Option<PublicValue>,
224    ) -> Self {
225        Self {
226            variant: variant.to_string(),
227            path: Some(path.to_string()),
228            payload: payload.map(Box::new),
229        }
230    }
231
232    /// Consume enum metadata and its payload without copying.
233    #[must_use]
234    pub fn into_parts(self) -> (String, Option<String>, Option<PublicValue>) {
235        (
236            self.variant,
237            self.path,
238            self.payload.map(|payload| *payload),
239        )
240    }
241}
242
243fn render_blob_value(bytes: &[u8]) -> String {
244    let mut rendered = String::from("0x");
245    rendered.push_str(encode_hex_lower(bytes).as_str());
246    rendered
247}
248
249fn render_duration_value(millis: u64) -> String {
250    let mut rendered = millis.to_string();
251    rendered.push_str("ms");
252    rendered
253}
254
255fn render_list_value(items: &[PublicValue]) -> String {
256    let mut rendered = String::from("[");
257    for (index, item) in items.iter().enumerate() {
258        if index != 0 {
259            rendered.push_str(", ");
260        }
261        rendered.push_str(item.render_text().as_str());
262    }
263    rendered.push(']');
264    rendered
265}
266
267fn render_map_value(entries: &[(PublicValue, PublicValue)]) -> String {
268    let mut rendered = String::from("{");
269    for (index, (key, value)) in entries.iter().enumerate() {
270        if index != 0 {
271            rendered.push_str(", ");
272        }
273        rendered.push_str(key.render_text().as_str());
274        rendered.push_str(": ");
275        rendered.push_str(value.render_text().as_str());
276    }
277    rendered.push('}');
278    rendered
279}
280
281fn render_enum(value: &PublicEnumValue) -> String {
282    let mut rendered = String::new();
283    if let Some(path) = value.path() {
284        rendered.push_str(path);
285        rendered.push_str("::");
286    }
287    rendered.push_str(value.variant());
288    if let Some(payload) = value.payload() {
289        rendered.push('(');
290        rendered.push_str(payload.render_text().as_str());
291        rendered.push(')');
292    }
293    rendered
294}