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