bc_components/
json.rs

1use bc_ur::prelude::*;
2
3use crate::tags;
4
5/// A CBOR-tagged container for UTF-8 JSON text.
6///
7/// The `JSON` type wraps UTF-8 JSON text as a CBOR byte string with tag 262.
8/// This allows JSON data to be embedded within CBOR structures while
9/// maintaining type information through the tag.
10///
11/// This implementation does not validate that the contained data is well-formed
12/// JSON. It simply provides a type-safe wrapper around byte data that is
13/// intended to contain JSON text.
14///
15/// # CBOR Serialization
16///
17/// `JSON` implements the `CBORTaggedCodable` trait, which means it can be
18/// serialized to and deserialized from CBOR with tag 262 (`TAG_JSON`).
19///
20/// # Examples
21///
22/// Creating JSON from a string:
23///
24/// ```
25/// use bc_components::JSON;
26///
27/// let json = JSON::from_string(r#"{"key": "value"}"#);
28/// assert_eq!(json.as_str(), r#"{"key": "value"}"#);
29/// ```
30///
31/// Creating JSON from bytes:
32///
33/// ```
34/// use bc_components::JSON;
35///
36/// let json = JSON::from_data(b"[1, 2, 3]");
37/// assert_eq!(json.len(), 9);
38/// ```
39#[derive(Clone, Eq, PartialEq)]
40pub struct JSON(Vec<u8>);
41
42impl JSON {
43    /// Return the length of the JSON data in bytes.
44    pub fn len(&self) -> usize { self.0.len() }
45
46    /// Return true if the JSON data is empty.
47    pub fn is_empty(&self) -> bool { self.0.is_empty() }
48
49    /// Create a new JSON instance from byte data.
50    pub fn from_data(data: impl AsRef<[u8]>) -> Self {
51        Self(data.as_ref().to_vec())
52    }
53
54    /// Create a new JSON instance from a string.
55    pub fn from_string(s: impl AsRef<str>) -> Self {
56        Self::from_data(s.as_ref().as_bytes())
57    }
58
59    /// Return the data as a byte slice.
60    pub fn as_bytes(&self) -> &[u8] { self.as_ref() }
61
62    /// Return the data as a UTF-8 string slice.
63    ///
64    /// # Panics
65    ///
66    /// Panics if the data is not valid UTF-8.
67    pub fn as_str(&self) -> &str {
68        std::str::from_utf8(&self.0).expect("Invalid UTF-8 in JSON data")
69    }
70
71    /// Create a new JSON instance from a hexadecimal string.
72    pub fn from_hex(hex: impl AsRef<str>) -> Self {
73        Self::from_data(hex::decode(hex.as_ref()).unwrap())
74    }
75
76    /// Return the data as a hexadecimal string.
77    pub fn hex(&self) -> String { hex::encode(self.as_bytes()) }
78}
79
80/// Allows accessing the underlying data as a byte slice reference.
81impl<'a> From<&'a JSON> for &'a [u8] {
82    fn from(value: &'a JSON) -> Self { value.as_bytes() }
83}
84
85/// Allows using a JSON as a reference to a byte slice.
86impl AsRef<[u8]> for JSON {
87    fn as_ref(&self) -> &[u8] { &self.0 }
88}
89
90/// Provides a self-reference, enabling API consistency with other types.
91impl AsRef<JSON> for JSON {
92    fn as_ref(&self) -> &JSON { self }
93}
94
95/// Identifies the CBOR tags used for JSON serialization.
96impl CBORTagged for JSON {
97    fn cbor_tags() -> Vec<Tag> { tags_for_values(&[tags::TAG_JSON]) }
98}
99
100/// Enables conversion of JSON into a tagged CBOR value.
101impl From<JSON> for CBOR {
102    fn from(value: JSON) -> Self { value.tagged_cbor() }
103}
104
105/// Defines how JSON is encoded as CBOR (as a byte string).
106impl CBORTaggedEncodable for JSON {
107    fn untagged_cbor(&self) -> CBOR { CBOR::to_byte_string(self.as_bytes()) }
108}
109
110/// Enables conversion from CBOR to JSON, with proper error handling.
111impl TryFrom<CBOR> for JSON {
112    type Error = dcbor::Error;
113
114    fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
115        Self::from_tagged_cbor(cbor)
116    }
117}
118
119/// Defines how JSON is decoded from CBOR.
120impl CBORTaggedDecodable for JSON {
121    fn from_untagged_cbor(untagged_cbor: CBOR) -> dcbor::Result<Self> {
122        let data = CBOR::try_into_byte_string(untagged_cbor)?;
123        let instance = Self::from_data(data);
124        Ok(instance)
125    }
126}
127
128/// Provides a debug representation showing the JSON data as a string.
129impl std::fmt::Debug for JSON {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        write!(f, "JSON({})", self.as_str())
132    }
133}
134
135/// Enables cloning a JSON from a reference using From trait.
136impl From<&JSON> for JSON {
137    fn from(json: &JSON) -> Self { json.clone() }
138}
139
140/// Converts JSON into a `Vec<u8>` containing the JSON bytes.
141impl From<JSON> for Vec<u8> {
142    fn from(json: JSON) -> Self { json.0.to_vec() }
143}
144
145/// Converts a JSON reference into a `Vec<u8>` containing the JSON bytes.
146impl From<&JSON> for Vec<u8> {
147    fn from(json: &JSON) -> Self { json.0.to_vec() }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn test_json_creation() {
156        let json = JSON::from_string(r#"{"key": "value"}"#);
157        assert_eq!(json.as_str(), r#"{"key": "value"}"#);
158        assert_eq!(json.len(), 16);
159        assert!(!json.is_empty());
160    }
161
162    #[test]
163    fn test_json_from_bytes() {
164        let data = b"[1, 2, 3]";
165        let json = JSON::from_data(data);
166        assert_eq!(json.as_bytes(), data);
167        assert_eq!(json.as_str(), "[1, 2, 3]");
168    }
169
170    #[test]
171    fn test_json_empty() {
172        let json = JSON::from_string("");
173        assert!(json.is_empty());
174        assert_eq!(json.len(), 0);
175    }
176
177    #[test]
178    fn test_json_cbor_roundtrip() {
179        let json = JSON::from_string(r#"{"name":"Alice","age":30}"#);
180        let cbor: CBOR = json.clone().into();
181        let json2: JSON = cbor.try_into().unwrap();
182        assert_eq!(json, json2);
183    }
184
185    #[test]
186    fn test_json_hex() {
187        let json = JSON::from_string("test");
188        let hex = json.hex();
189        let json2 = JSON::from_hex(hex);
190        assert_eq!(json, json2);
191    }
192
193    #[test]
194    fn test_json_debug() {
195        let json = JSON::from_string(r#"{"test":true}"#);
196        let debug = format!("{:?}", json);
197        assert_eq!(debug, r#"JSON({"test":true})"#);
198    }
199
200    #[test]
201    fn test_json_clone() {
202        let json = JSON::from_string("original");
203        let json2 = json.clone();
204        assert_eq!(json, json2);
205    }
206
207    #[test]
208    fn test_json_into_vec() {
209        let json = JSON::from_string("data");
210        let vec: Vec<u8> = json.into();
211        assert_eq!(vec, b"data");
212    }
213}