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#[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 #[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#[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 #[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 #[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 #[must_use]
200 pub fn with_payload(mut self, payload: PublicValue) -> Self {
201 self.payload = Some(Box::new(payload));
202 self
203 }
204
205 #[must_use]
207 pub const fn variant(&self) -> &str {
208 self.variant.as_str()
209 }
210
211 #[must_use]
213 pub fn path(&self) -> Option<&str> {
214 self.path.as_deref()
215 }
216
217 #[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 #[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}