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