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#[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 #[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#[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 #[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 #[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 #[must_use]
195 pub fn with_payload(mut self, payload: PublicValue) -> Self {
196 self.payload = Some(Box::new(payload));
197 self
198 }
199
200 #[must_use]
202 pub const fn variant(&self) -> &str {
203 self.variant.as_str()
204 }
205
206 #[must_use]
208 pub fn path(&self) -> Option<&str> {
209 self.path.as_deref()
210 }
211
212 #[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 #[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}