Skip to main content

bcp_types/
structured_data.rs

1use crate::enums::DataFormat;
2use crate::error::TypeError;
3use crate::fields::{
4    decode_bytes_value, decode_field_header, decode_varint_value, encode_bytes_field,
5    encode_varint_field, skip_field,
6};
7
8/// STRUCTURED_DATA block — represents tables, JSON, configs, etc.
9///
10/// Used for any structured content that isn't source code: API responses,
11/// configuration files, CSV datasets, YAML manifests. The `format` field
12/// tells the renderer which parser/highlighter to apply.
13///
14/// Field layout within body:
15///
16/// ```text
17/// ┌──────────┬───────────┬─────────┬──────────────────────────────┐
18/// │ Field ID │ Wire Type │ Name    │ Description                  │
19/// ├──────────┼───────────┼─────────┼──────────────────────────────┤
20/// │ 1        │ Varint    │ format  │ DataFormat enum byte         │
21/// │ 2        │ Bytes     │ schema  │ Optional schema descriptor   │
22/// │ 3        │ Bytes     │ content │ Raw data bytes               │
23/// └──────────┴───────────┴─────────┴──────────────────────────────┘
24/// ```
25#[derive(Clone, Debug, PartialEq, Eq)]
26pub struct StructuredDataBlock {
27    pub format: DataFormat,
28    /// Optional schema descriptor (e.g. a JSON Schema URI or inline schema).
29    pub schema: Option<String>,
30    pub content: Vec<u8>,
31}
32
33impl StructuredDataBlock {
34    /// Serialize this block's fields into a TLV-encoded body.
35    pub fn encode_body(&self) -> Vec<u8> {
36        let mut buf = Vec::new();
37        encode_varint_field(&mut buf, 1, u64::from(self.format.to_wire_byte()));
38        if let Some(ref schema) = self.schema {
39            encode_bytes_field(&mut buf, 2, schema.as_bytes());
40        }
41        encode_bytes_field(&mut buf, 3, &self.content);
42        buf
43    }
44
45    /// Deserialize a STRUCTURED_DATA block from a TLV-encoded body.
46    pub fn decode_body(mut buf: &[u8]) -> Result<Self, TypeError> {
47        let mut format: Option<DataFormat> = None;
48        let mut schema: Option<String> = None;
49        let mut content: Option<Vec<u8>> = None;
50
51        while !buf.is_empty() {
52            let (header, n) = decode_field_header(buf)?;
53            buf = &buf[n..];
54
55            match header.field_id {
56                1 => {
57                    let (v, n) = decode_varint_value(buf)?;
58                    buf = &buf[n..];
59                    format = Some(DataFormat::from_wire_byte(v as u8)?);
60                }
61                2 => {
62                    let (data, n) = decode_bytes_value(buf)?;
63                    buf = &buf[n..];
64                    schema = Some(String::from_utf8_lossy(data).into_owned());
65                }
66                3 => {
67                    let (data, n) = decode_bytes_value(buf)?;
68                    buf = &buf[n..];
69                    content = Some(data.to_vec());
70                }
71                _ => {
72                    let n = skip_field(buf, header.wire_type)?;
73                    buf = &buf[n..];
74                }
75            }
76        }
77
78        Ok(Self {
79            format: format.ok_or(TypeError::MissingRequiredField { field: "format" })?,
80            schema,
81            content: content.ok_or(TypeError::MissingRequiredField { field: "content" })?,
82        })
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn roundtrip_json_no_schema() {
92        let block = StructuredDataBlock {
93            format: DataFormat::Json,
94            schema: None,
95            content: b"{\"key\": \"value\"}".to_vec(),
96        };
97        let body = block.encode_body();
98        let decoded = StructuredDataBlock::decode_body(&body).unwrap();
99        assert_eq!(decoded, block);
100    }
101
102    #[test]
103    fn roundtrip_csv_with_schema() {
104        let block = StructuredDataBlock {
105            format: DataFormat::Csv,
106            schema: Some("name,age,city".to_string()),
107            content: b"Alice,30,NYC\nBob,25,LA".to_vec(),
108        };
109        let body = block.encode_body();
110        let decoded = StructuredDataBlock::decode_body(&body).unwrap();
111        assert_eq!(decoded, block);
112    }
113}