Skip to main content

braid_http/protocol/
formatter.rs

1//! Protocol message formatter.
2//!
3//! Converts `Update` objects into Braid protocol message bytes.
4//! supporting standard headers, body content, and multi-patch formats.
5
6use crate::error::Result;
7use crate::protocol::constants::headers;
8use crate::protocol;
9use crate::types::{Update, Patch};
10use bytes::{Bytes, BytesMut};
11
12/// Format an Update into Braid protocol message bytes.
13///
14/// Serializes the update including all headers and body/patches
15/// into a byte buffer ready for transmission.
16///
17/// # Arguments
18///
19/// * `update` - The update to format
20///
21/// # Returns
22///
23/// Bytes containing the formatted message.
24pub fn format_update(update: &Update) -> Result<Bytes> {
25    let mut buffer = BytesMut::new();
26
27    // Format headers
28    if !update.version.is_empty() {
29        write_header(&mut buffer, headers::VERSION.as_str(), &protocol::format_version_header(&update.version));
30    }
31
32    if !update.parents.is_empty() {
33        write_header(&mut buffer, headers::PARENTS.as_str(), &protocol::format_version_header(&update.parents));
34    }
35
36    if let Some(merge_type) = &update.merge_type {
37        write_header(&mut buffer, headers::MERGE_TYPE.as_str(), merge_type);
38    }
39
40    for (k, v) in &update.extra_headers {
41        write_header(&mut buffer, k, v);
42    }
43
44    // Body or Patches
45    if let Some(body) = &update.body {
46        write_header(&mut buffer, headers::CONTENT_LENGTH.as_str(), &body.len().to_string());
47        if let Some(ct) = &update.content_type {
48            write_header(&mut buffer, headers::CONTENT_TYPE.as_str(), ct);
49        }
50        buffer.extend_from_slice(b"\r\n"); // End of headers
51        buffer.extend_from_slice(body);
52    } else if let Some(patches) = &update.patches {
53        if !patches.is_empty() {
54             write_header(&mut buffer, headers::PATCHES.as_str(), &patches.len().to_string());
55             buffer.extend_from_slice(b"\r\n"); // End of message headers
56             
57             // Format each patch
58             for patch in patches {
59                 format_patch(&mut buffer, patch)?;
60             }
61        } else {
62             write_header(&mut buffer, headers::CONTENT_LENGTH.as_str(), "0");
63             buffer.extend_from_slice(b"\r\n");
64        }
65    } else {
66        // Empty body
67        write_header(&mut buffer, headers::CONTENT_LENGTH.as_str(), "0");
68        buffer.extend_from_slice(b"\r\n");
69    }
70
71    Ok(buffer.freeze())
72}
73
74fn write_header(buffer: &mut BytesMut, key: &str, value: &str) {
75    buffer.extend_from_slice(key.as_bytes());
76    buffer.extend_from_slice(b": ");
77    buffer.extend_from_slice(value.as_bytes());
78    buffer.extend_from_slice(b"\r\n");
79}
80
81fn format_patch(buffer: &mut BytesMut, patch: &Patch) -> Result<()> {
82    write_header(buffer, headers::CONTENT_LENGTH.as_str(), &patch.content.len().to_string());
83    
84    let content_range = format!("{} {}", patch.unit, patch.range);
85    write_header(buffer, headers::CONTENT_RANGE.as_str(), &content_range);
86    
87    buffer.extend_from_slice(b"\r\n"); // End of patch headers
88    buffer.extend_from_slice(&patch.content);
89    
90    Ok(())
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96    use crate::types::Version;
97
98    #[test]
99    fn test_format_snapshot() {
100        let update = Update::snapshot(Version::new("v1"), "data");
101        let bytes = format_update(&update).unwrap();
102        let s = std::str::from_utf8(&bytes).unwrap();
103        
104        assert!(s.contains("version: \"v1\""));
105        assert!(s.contains("content-length: 4"));
106        assert!(s.ends_with("\r\ndata"));
107    }
108}