1use base64::Engine;
2use serde_json::{Map, Value, json};
3
4const TAG: &str = "__codemode_type";
5
6#[derive(Debug, Clone, PartialEq)]
8pub enum CodeValue {
9 Undefined,
11 Json(Value),
13 BigInt(String),
15 Binary(Vec<u8>),
17}
18
19pub fn encode_value(value: &CodeValue) -> Value {
21 match value {
22 CodeValue::Undefined => json!({ TAG: "undefined" }),
23 CodeValue::Json(value) => encode_json(value),
24 CodeValue::BigInt(value) => json!({ TAG: "bigint", "value": value }),
25 CodeValue::Binary(value) => json!({
26 TAG: "binary",
27 "value": base64::engine::general_purpose::STANDARD.encode(value),
28 }),
29 }
30}
31
32pub fn decode_value(value: Value) -> Result<CodeValue, String> {
34 if let Value::Object(map) = &value
35 && let Some(Value::String(kind)) = map.get(TAG)
36 {
37 return match kind.as_str() {
38 "undefined" => Ok(CodeValue::Undefined),
39 "bigint" => string_value(map).map(CodeValue::BigInt),
40 "binary" => base64::engine::general_purpose::STANDARD
41 .decode(string_value(map)?)
42 .map(CodeValue::Binary)
43 .map_err(|error| error.to_string()),
44 _ => Ok(CodeValue::Json(decode_json(value)?)),
45 };
46 }
47 Ok(CodeValue::Json(decode_json(value)?))
48}
49
50fn string_value(map: &Map<String, Value>) -> Result<String, String> {
51 map.get("value")
52 .and_then(Value::as_str)
53 .map(str::to_string)
54 .ok_or_else(|| "Tagged Code Mode value is missing a string value".to_string())
55}
56
57fn encode_json(value: &Value) -> Value {
58 match value {
59 Value::Array(values) => Value::Array(values.iter().map(encode_json).collect()),
60 Value::Object(values) => Value::Object(
61 values
62 .iter()
63 .map(|(key, value)| (key.clone(), encode_json(value)))
64 .collect(),
65 ),
66 value => value.clone(),
67 }
68}
69
70fn decode_json(value: Value) -> Result<Value, String> {
71 match value {
72 Value::Array(values) => values
73 .into_iter()
74 .map(decode_json)
75 .collect::<Result<Vec<_>, _>>()
76 .map(Value::Array),
77 Value::Object(values) => values
78 .into_iter()
79 .map(|(key, value)| decode_json(value).map(|value| (key, value)))
80 .collect::<Result<Map<_, _>, _>>()
81 .map(Value::Object),
82 value => Ok(value),
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn round_trips_extended_values() {
92 for value in [
93 CodeValue::Undefined,
94 CodeValue::BigInt("9007199254740993".to_string()),
95 CodeValue::Binary(vec![0, 1, 255]),
96 CodeValue::Json(json!({"ok": [true, null]})),
97 ] {
98 assert_eq!(decode_value(encode_value(&value)).unwrap(), value);
99 }
100 }
101}