Skip to main content

icydb_core/value/
public.rs

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