1#[cfg(not(feature = "used_from_wasm"))]
2use noop_proc_macro::wasm_bindgen;
3#[cfg(feature = "used_from_wasm")]
4use wasm_bindgen::prelude::wasm_bindgen;
5
6use crate::{
7 auxdata::{MetadatumMap, TransactionMetadatum},
8 json::json_serialize::{JsonParseError, Value as JSONValue},
9 utils::BigInteger,
10};
11
12use cml_core::{DeserializeError, Int};
13
14use std::collections::BTreeMap;
15use std::convert::TryFrom;
16
17#[wasm_bindgen]
18#[derive(Copy, Clone, Eq, PartialEq)]
19pub enum MetadataJsonSchema {
25 NoConversions,
35 BasicConversions,
47 DetailedSchema,
57}
58
59#[derive(Debug, thiserror::Error)]
60pub enum MetadataJsonError {
61 #[error("JSON Parsing: {0}")]
62 JsonParse(#[from] JsonParseError),
63 #[error("JSON printing: {0}")]
64 JsonPrinting(#[from] serde_json::Error),
65 #[error("null not allowed in metadatums")]
66 NullFound,
67 #[error("bools not allowed in metadatums")]
68 BoolFound,
69 #[error("DetailedSchema key {0} does not match type {1:?}")]
70 DetailedKeyMismatch(String, JSONValue),
71 #[error("entry format in detailed schema map object not correct. Needs to be of form {{\"k\": \"key\", \"v\": value}}")]
72 InvalidMapEntry,
73 #[error("key '{0}' in tagged object not valid")]
74 InvalidTag(String),
75 #[error(
76 "DetailedSchema requires ALL JSON to be tagged objects, found: {:?}",
77 0
78 )]
79 DetailedNonObject(JSONValue),
80 #[error("Invalid hex string: {0}")]
81 InvalidHex(#[from] hex::FromHexError),
82 #[error("Bytes not allowed in BasicConversions schema")]
83 BytesInNoConversions,
84 #[error("Metadatum ints must fit in 8 bytes: {0}")]
85 IntTooBig(BigInteger),
86 #[error("key type {0:?} not allowed in JSON under specified schema")]
87 InvalidKeyType(TransactionMetadatum),
88 #[error("Metadatum structure error (e.g. too big for bounds): {0}")]
89 InvalidStructure(#[from] DeserializeError),
90}
91
92fn supports_tagged_values(schema: MetadataJsonSchema) -> bool {
93 match schema {
94 MetadataJsonSchema::NoConversions | MetadataJsonSchema::BasicConversions => false,
95 MetadataJsonSchema::DetailedSchema => true,
96 }
97}
98
99fn hex_string_to_bytes(hex: &str) -> Option<Vec<u8>> {
100 if let Some(stripped) = hex.strip_prefix("0x") {
101 hex::decode(stripped).ok()
102 } else {
103 None
104 }
105}
106
107fn bytes_to_hex_string(bytes: &[u8]) -> String {
108 format!("0x{}", hex::encode(bytes))
109}
110
111pub fn encode_json_str_to_metadatum(
113 json: &str,
114 schema: MetadataJsonSchema,
115) -> Result<TransactionMetadatum, MetadataJsonError> {
116 let value = JSONValue::from_string(json)?;
117 encode_json_value_to_metadatum(value, schema)
118}
119
120pub fn encode_json_value_to_metadatum(
121 value: JSONValue,
122 schema: MetadataJsonSchema,
123) -> Result<TransactionMetadatum, MetadataJsonError> {
124 fn encode_string(
125 s: String,
126 schema: MetadataJsonSchema,
127 ) -> Result<TransactionMetadatum, DeserializeError> {
128 if schema == MetadataJsonSchema::BasicConversions {
129 match hex_string_to_bytes(&s) {
130 Some(bytes) => TransactionMetadatum::new_bytes(bytes),
131 None => TransactionMetadatum::new_text(s),
132 }
133 } else {
134 TransactionMetadatum::new_text(s)
135 }
136 }
137 fn encode_array(
138 json_arr: Vec<JSONValue>,
139 schema: MetadataJsonSchema,
140 ) -> Result<TransactionMetadatum, MetadataJsonError> {
141 json_arr
142 .into_iter()
143 .map(|value| encode_json_value_to_metadatum(value, schema))
144 .collect::<Result<Vec<_>, MetadataJsonError>>()
145 .map(TransactionMetadatum::new_list)
146 }
147 match schema {
148 MetadataJsonSchema::NoConversions | MetadataJsonSchema::BasicConversions => match value {
149 JSONValue::Null => Err(MetadataJsonError::NullFound),
150 JSONValue::Bool(_) => Err(MetadataJsonError::BoolFound),
151 JSONValue::Number(x) => Ok(TransactionMetadatum::new_int(
152 x.as_int().ok_or(MetadataJsonError::IntTooBig(x.clone()))?,
153 )),
154 JSONValue::String(s) => encode_string(s, schema).map_err(Into::into),
155 JSONValue::Array(json_arr) => encode_array(json_arr, schema),
156 JSONValue::Object(json_obj) => {
157 let mut map = MetadatumMap::new();
158 for (raw_key, value) in json_obj {
159 let key =
160 if schema == MetadataJsonSchema::BasicConversions {
161 match raw_key.parse::<i128>() {
162 Ok(x) => TransactionMetadatum::new_int(Int::try_from(x).map_err(
163 |_e| MetadataJsonError::IntTooBig(BigInteger::from(x)),
164 )?),
165 Err(_) => encode_string(raw_key, schema)?,
166 }
167 } else {
168 TransactionMetadatum::new_text(raw_key)?
169 };
170 map.set(key, encode_json_value_to_metadatum(value, schema)?);
171 }
172 Ok(TransactionMetadatum::new_map(map))
173 }
174 },
175 MetadataJsonSchema::DetailedSchema => match value {
177 JSONValue::Object(obj) if obj.len() == 1 => {
178 let (k, v) = obj.into_iter().next().unwrap();
179 match k.as_str() {
180 "int" => match v {
181 JSONValue::Number(x) => Ok(TransactionMetadatum::new_int(
182 x.as_int().ok_or(MetadataJsonError::IntTooBig(x.clone()))?,
183 )),
184 _ => Err(MetadataJsonError::DetailedKeyMismatch(k, v)),
185 },
186 "string" => match v {
187 JSONValue::String(string) => {
188 encode_string(string, schema).map_err(Into::into)
189 }
190 _ => Err(MetadataJsonError::DetailedKeyMismatch(k, v)),
191 },
192 "bytes" => match v {
193 JSONValue::String(string) => hex::decode(string)
194 .map_err(Into::into)
195 .and_then(|b| TransactionMetadatum::new_bytes(b).map_err(Into::into)),
196 _ => Err(MetadataJsonError::DetailedKeyMismatch(k, v)),
197 },
198 "list" => match v {
199 JSONValue::Array(array) => encode_array(array, schema),
200 _ => Err(MetadataJsonError::DetailedKeyMismatch(k, v)),
201 },
202 "map" => {
203 let mut map = MetadatumMap::new();
204
205 let array = match v {
206 JSONValue::Array(array) => Ok(array),
207 _ => Err(MetadataJsonError::DetailedKeyMismatch(k, v)),
208 }?;
209 for entry in array {
210 let entry_obj = match entry {
211 JSONValue::Object(obj) => Ok(obj),
212 _ => Err(MetadataJsonError::InvalidMapEntry),
213 }?;
214 let raw_key = entry_obj
215 .get("k")
216 .ok_or(MetadataJsonError::InvalidMapEntry)?;
217 let value = entry_obj
218 .get("v")
219 .ok_or(MetadataJsonError::InvalidMapEntry)?;
220 let key = encode_json_value_to_metadatum(raw_key.clone(), schema)?;
221 map.set(key, encode_json_value_to_metadatum(value.clone(), schema)?);
222 }
223 Ok(TransactionMetadatum::new_map(map))
224 }
225 _invalid_key => Err(MetadataJsonError::InvalidTag(k)),
226 }
227 }
228 _ => Err(MetadataJsonError::DetailedNonObject(value)),
229 },
230 }
231}
232
233pub fn decode_metadatum_to_json_str(
235 metadatum: &TransactionMetadatum,
236 schema: MetadataJsonSchema,
237) -> Result<String, MetadataJsonError> {
238 let value = decode_metadatum_to_json_value(metadatum, schema)?;
239 value.to_string().map_err(Into::into)
240}
241
242pub fn decode_metadatum_to_json_value(
243 metadatum: &TransactionMetadatum,
244 schema: MetadataJsonSchema,
245) -> Result<JSONValue, MetadataJsonError> {
246 fn decode_key(
247 key: &TransactionMetadatum,
248 schema: MetadataJsonSchema,
249 ) -> Result<String, MetadataJsonError> {
250 match key {
251 TransactionMetadatum::Text { text, .. } => Ok(text.clone()),
252 TransactionMetadatum::Bytes { bytes, .. }
253 if schema != MetadataJsonSchema::NoConversions =>
254 {
255 Ok(bytes_to_hex_string(bytes.as_ref()))
256 }
257 TransactionMetadatum::Int(i) if schema != MetadataJsonSchema::NoConversions => {
258 Ok(i.to_string())
259 }
260 TransactionMetadatum::List { elements, .. }
261 if schema == MetadataJsonSchema::DetailedSchema =>
262 {
263 decode_metadatum_to_json_str(
264 &TransactionMetadatum::new_list(elements.clone()),
265 schema,
266 )
267 }
268 TransactionMetadatum::Map(map) if schema == MetadataJsonSchema::DetailedSchema => {
269 decode_metadatum_to_json_str(&TransactionMetadatum::new_map(map.clone()), schema)
270 }
271 _ => Err(MetadataJsonError::InvalidKeyType(key.clone())),
272 }
273 }
274 let (type_key, value) = match metadatum {
275 TransactionMetadatum::Map(map) => match schema {
276 MetadataJsonSchema::NoConversions | MetadataJsonSchema::BasicConversions => {
277 let mut json_map = BTreeMap::new();
279 for (key, value) in map.entries.iter() {
280 json_map.insert(
281 decode_key(key, schema)?,
282 decode_metadatum_to_json_value(value, schema)?,
283 );
284 }
285 ("map", JSONValue::from(json_map))
286 }
287
288 MetadataJsonSchema::DetailedSchema => (
289 "map",
290 JSONValue::from(
291 map.entries
292 .iter()
293 .map(|(key, value)| {
294 let k = decode_metadatum_to_json_value(key, schema)?;
297 let v = decode_metadatum_to_json_value(value, schema)?;
298 let mut kv_obj = BTreeMap::new();
299 kv_obj.insert(String::from("k"), k);
300 kv_obj.insert(String::from("v"), v);
301 Ok(JSONValue::from(kv_obj))
302 })
303 .collect::<Result<Vec<_>, MetadataJsonError>>()?,
304 ),
305 ),
306 },
307 TransactionMetadatum::List { elements, .. } => (
308 "list",
309 JSONValue::from(
310 elements
311 .iter()
312 .map(|e| decode_metadatum_to_json_value(e, schema))
313 .collect::<Result<Vec<_>, MetadataJsonError>>()?,
314 ),
315 ),
316 TransactionMetadatum::Int(x) => ("int", JSONValue::Number(BigInteger::from_int(x))),
317 TransactionMetadatum::Bytes { bytes, .. } => (
318 "bytes",
319 match schema {
320 MetadataJsonSchema::NoConversions => Err(MetadataJsonError::BytesInNoConversions),
321 MetadataJsonSchema::BasicConversions => {
323 Ok(JSONValue::from(bytes_to_hex_string(bytes.as_ref())))
324 }
325 MetadataJsonSchema::DetailedSchema => Ok(JSONValue::from(hex::encode(bytes))),
327 }?,
328 ),
329 TransactionMetadatum::Text { text, .. } => ("string", JSONValue::from(text.clone())),
330 };
331 if supports_tagged_values(schema) {
333 let mut wrapper = BTreeMap::new();
334 wrapper.insert(String::from(type_key), value);
335 Ok(JSONValue::from(wrapper))
336 } else {
337 Ok(value)
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344
345 #[test]
346 fn json_encoding_no_conversions() {
347 let input_str = "{\"receiver_id\": \"SJKdj34k3jjKFDKfjFUDfdjkfd\",\"sender_id\": \"jkfdsufjdk34h3Sdfjdhfduf873\",\"comment\": \"happy birthday\",\"tags\": [0, 264, -1024, 32]}";
348 let metadata = encode_json_str_to_metadatum(input_str, MetadataJsonSchema::NoConversions)
349 .expect("encode failed");
350 let map = metadata.as_map().unwrap();
351 assert_eq!(
352 map.get_str("receiver_id").unwrap().as_text().unwrap(),
353 "SJKdj34k3jjKFDKfjFUDfdjkfd"
354 );
355 assert_eq!(
356 map.get_str("sender_id").unwrap().as_text().unwrap(),
357 "jkfdsufjdk34h3Sdfjdhfduf873"
358 );
359 assert_eq!(
360 map.get_str("comment").unwrap().as_text().unwrap(),
361 "happy birthday"
362 );
363 let tags = map.get_str("tags").unwrap().as_list().unwrap();
364 let tags_i32 = tags
365 .iter()
366 .map(|md| md.as_int().unwrap().into())
367 .collect::<Vec<i128>>();
368 assert_eq!(tags_i32, vec![0, 264, -1024, 32]);
369 let output_str = decode_metadatum_to_json_str(&metadata, MetadataJsonSchema::NoConversions)
370 .expect("decode failed");
371 let input_json: serde_json::Value = serde_json::from_str(input_str).unwrap();
372 let output_json: serde_json::Value = serde_json::from_str(&output_str).unwrap();
373 assert_eq!(input_json, output_json);
374 }
375
376 #[test]
377 fn json_encoding_basic() {
378 let input_str =
379 "{\"0x8badf00d\": \"0xdeadbeef\",\"9\": 5,\"obj\": {\"a\":[{\"5\": 2},{}]}}";
380 let metadata =
381 encode_json_str_to_metadatum(input_str, MetadataJsonSchema::BasicConversions)
382 .expect("encode failed");
383 json_encoding_check_example_metadatum(&metadata);
384 let output_str =
385 decode_metadatum_to_json_str(&metadata, MetadataJsonSchema::BasicConversions)
386 .expect("decode failed");
387 let input_json: serde_json::Value = serde_json::from_str(input_str).unwrap();
388 let output_json: serde_json::Value = serde_json::from_str(&output_str).unwrap();
389 assert_eq!(input_json, output_json);
390 }
391
392 #[test]
393 fn json_encoding_detailed() {
394 let input_str = "{\"map\":[
395 {
396 \"k\":{\"bytes\":\"8badf00d\"},
397 \"v\":{\"bytes\":\"deadbeef\"}
398 },
399 {
400 \"k\":{\"int\":9},
401 \"v\":{\"int\":5}
402 },
403 {
404 \"k\":{\"string\":\"obj\"},
405 \"v\":{\"map\":[
406 {
407 \"k\":{\"string\":\"a\"},
408 \"v\":{\"list\":[
409 {\"map\":[
410 {
411 \"k\":{\"int\":5},
412 \"v\":{\"int\":2}
413 }
414 ]},
415 {\"map\":[
416 ]}
417 ]}
418 }
419 ]}
420 }
421 ]}";
422 let metadata = encode_json_str_to_metadatum(input_str, MetadataJsonSchema::DetailedSchema)
423 .expect("encode failed");
424 json_encoding_check_example_metadatum(&metadata);
425 let output_str =
426 decode_metadatum_to_json_str(&metadata, MetadataJsonSchema::DetailedSchema)
427 .expect("decode failed");
428 let input_json: serde_json::Value = serde_json::from_str(input_str).unwrap();
429 let output_json: serde_json::Value = serde_json::from_str(&output_str).unwrap();
430 assert_eq!(input_json, output_json);
431 }
432
433 fn json_encoding_check_example_metadatum(metadata: &TransactionMetadatum) {
434 let map = metadata.as_map().unwrap();
435 assert_eq!(
436 *map.get(&TransactionMetadatum::new_bytes(hex::decode("8badf00d").unwrap()).unwrap())
437 .unwrap()
438 .as_bytes()
439 .unwrap(),
440 hex::decode("deadbeef").unwrap()
441 );
442 assert_eq!(
443 i128::from(
444 map.get(&TransactionMetadatum::new_int(Int::from(9u64)))
445 .unwrap()
446 .as_int()
447 .unwrap()
448 ),
449 5
450 );
451 let inner_map = map.get_str("obj").unwrap().as_map().unwrap();
452 let a = inner_map.get_str("a").unwrap().as_list().unwrap();
453 let a1 = a[0].as_map().unwrap();
454 assert_eq!(
455 i128::from(
456 a1.get(&TransactionMetadatum::new_int(Int::from(5u64)))
457 .unwrap()
458 .as_int()
459 .unwrap()
460 ),
461 2
462 );
463 let a2 = a[1].as_map().unwrap();
464 assert_eq!(a2.len(), 0);
465 }
466
467 #[test]
468 fn json_encoding_detailed_complex_key() {
469 let input_str = "{\"map\":[
470 {
471 \"k\":{\"list\":[
472 {\"map\": [
473 {
474 \"k\": {\"int\": 5},
475 \"v\": {\"int\": -7}
476 },
477 {
478 \"k\": {\"string\": \"hello\"},
479 \"v\": {\"string\": \"world\"}
480 }
481 ]},
482 {\"bytes\": \"ff00ff00\"}
483 ]},
484 \"v\":{\"int\":5}
485 }
486 ]}";
487 let metadata = encode_json_str_to_metadatum(input_str, MetadataJsonSchema::DetailedSchema)
488 .expect("encode failed");
489
490 let map = metadata.as_map().unwrap();
491 let (k, v) = map.entries.first().unwrap();
492 assert_eq!(i128::from(v.as_int().unwrap()), 5i128);
493 let key_list = k.as_list().unwrap();
494 assert_eq!(key_list.len(), 2);
495 let key_map = key_list[0].as_map().unwrap();
496 assert_eq!(
497 i128::from(
498 key_map
499 .get(&TransactionMetadatum::new_int(Int::from(5u64)))
500 .unwrap()
501 .as_int()
502 .unwrap()
503 ),
504 -7i128
505 );
506 assert_eq!(
507 key_map.get_str("hello").unwrap().as_text().unwrap(),
508 "world"
509 );
510 let key_bytes = key_list[1].as_bytes().unwrap();
511 assert_eq!(*key_bytes, hex::decode("ff00ff00").unwrap());
512
513 let output_str =
514 decode_metadatum_to_json_str(&metadata, MetadataJsonSchema::DetailedSchema)
515 .expect("decode failed");
516 let input_json: serde_json::Value = serde_json::from_str(input_str).unwrap();
517 let output_json: serde_json::Value = serde_json::from_str(&output_str).unwrap();
518 assert_eq!(input_json, output_json);
519 }
520}