braid_http/protocol/
headers.rs1use crate::error::{BraidError, Result};
4use crate::types::Version;
5
6pub fn parse_version_header(value: &str) -> Result<Vec<Version>> {
8 tracing::info!("[BraidHTTP] Parsing version header: '{}'", value);
9 use sfv::{BareItem, List, ListEntry, Parser};
11 match Parser::new(value).parse::<List>() {
12 Ok(list) => {
13 let mut versions = Vec::new();
14 for member in list {
15 match member {
16 ListEntry::Item(item) => match item.bare_item {
17 BareItem::String(s) => versions.push(Version::String(s.into())),
18 BareItem::Integer(i) => versions.push(Version::Integer(i.into())),
19 BareItem::Token(t) => versions.push(Version::String(t.into())),
20 _ => {}
21 },
22 _ => {}
23 }
24 }
25 if !versions.is_empty() {
26 return Ok(versions);
27 }
28 }
29 Err(_) => {}
30 }
31
32 if let Ok(json_arr) = serde_json::from_str::<Vec<String>>(value) {
34 return Ok(json_arr.into_iter().map(Version::String).collect());
35 }
36
37 if let Ok(json_str) = serde_json::from_str::<String>(value) {
39 return Ok(vec![Version::String(json_str)]);
40 }
41
42 let trimmed = value.trim();
44 if !trimmed.is_empty() {
45 let mut clean = trimmed.to_string();
47 loop {
48 let next = clean
49 .trim_matches(|c| c == '"' || c == '\'' || c == '\\')
50 .to_string();
51 if next == clean || next.is_empty() {
52 break;
53 }
54 clean = next;
55 }
56
57 if !clean.is_empty() {
58 return Ok(vec![Version::String(clean)]);
59 }
60 }
61
62 Ok(Vec::new())
63}
64
65pub fn format_version_header(versions: &[Version]) -> String {
67 versions
68 .iter()
69 .map(|v| match v {
70 Version::String(s) => format!("\"{}\"", s.replace("\"", "\\\"")),
71 Version::Integer(i) => format!("\"{}\"", i), })
73 .collect::<Vec<_>>()
74 .join(", ")
75}
76
77pub fn format_version_header_json(versions: &[Version]) -> String {
78 let strings: Vec<String> = versions.iter().map(|v| v.to_string()).collect();
81 let json = serde_json::to_string(&strings).unwrap_or_else(|_| "[]".to_string());
82 tracing::info!("[Protocol] Formatted headers as JSON: {}", json);
83 json
84}
85
86pub fn parse_current_version_header(value: &str) -> Result<Vec<Version>> {
87 parse_version_header(value)
88}
89
90pub fn parse_content_range(value: &str) -> Result<(String, String)> {
91 let parts: Vec<&str> = value.splitn(2, ' ').collect();
92 if parts.len() != 2 {
93 return Err(BraidError::HeaderParse(format!(
94 "Invalid Content-Range: expected 'unit range', got '{}'",
95 value
96 )));
97 }
98 Ok((parts[0].to_string(), parts[1].to_string()))
99}
100
101#[inline]
102pub fn format_content_range(unit: &str, range: &str) -> String {
103 format!("{} {}", unit, range)
104}
105
106pub fn parse_heartbeat(value: &str) -> Result<u64> {
107 let trimmed = value.trim();
108 if let Some(ms_str) = trimmed.strip_suffix("ms") {
109 return ms_str
110 .parse::<u64>()
111 .map(|n| n / 1000)
112 .map_err(|_| BraidError::HeaderParse(format!("Invalid heartbeat: {}", value)));
113 }
114 if let Some(s_str) = trimmed.strip_suffix('s') {
115 return s_str
116 .parse()
117 .map_err(|_| BraidError::HeaderParse(format!("Invalid heartbeat: {}", value)));
118 }
119 trimmed
120 .parse()
121 .map_err(|_| BraidError::HeaderParse(format!("Invalid heartbeat: {}", value)))
122}
123
124pub fn parse_merge_type(value: &str) -> Result<String> {
125 let trimmed = value.trim();
126 match trimmed {
127 crate::protocol::constants::merge_types::DIAMOND => Ok(trimmed.to_string()),
128 _ => Err(BraidError::HeaderParse(format!(
129 "Unsupported merge-type: {}",
130 value
131 ))),
132 }
133}
134
135pub fn parse_tunneled_response(
136 bytes: &[u8],
137) -> Result<(u16, std::collections::BTreeMap<String, String>, usize)> {
138 let s = String::from_utf8_lossy(bytes);
139 if let Some(end_idx) = s.find("\r\n\r\n") {
140 let headers_part = &s[..end_idx];
141 let mut status = 200;
142 let mut headers = std::collections::BTreeMap::new();
143 for line in headers_part.lines() {
144 let line = line.trim();
145 if line.is_empty() {
146 continue;
147 }
148 if let Some(val) = line.strip_prefix(":status:") {
149 status = val.trim().parse().unwrap_or(200);
150 continue;
151 }
152 if let Some((name, value)) = line.split_once(':') {
153 headers.insert(name.trim().to_lowercase(), value.trim().to_string());
154 }
155 }
156 Ok((status, headers, end_idx + 4))
157 } else {
158 Err(BraidError::HeaderParse("Incomplete headers".to_string()))
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165 #[test]
166 fn test_headers() {
167 assert_eq!(parse_heartbeat("5s").unwrap(), 5);
168 assert_eq!(format_content_range("json", ".f"), "json .f");
169 }
170}