Skip to main content

braid_http/types/
response.rs

1//! HTTP response with Braid protocol information.
2
3use crate::protocol;
4use crate::types::{ContentRange, Version};
5use bytes::Bytes;
6use std::collections::BTreeMap;
7
8/// HTTP response with Braid protocol information.
9#[derive(Clone, Debug)]
10pub struct BraidResponse {
11    pub status: u16,
12    pub headers: BTreeMap<String, String>,
13    pub body: Bytes,
14    pub is_subscription: bool,
15}
16
17impl BraidResponse {
18    pub fn new(status: u16, body: impl Into<Bytes>) -> Self {
19        BraidResponse {
20            status,
21            headers: BTreeMap::new(),
22            body: body.into(),
23            is_subscription: status == 209,
24        }
25    }
26
27    pub fn subscription(body: impl Into<Bytes>) -> Self {
28        Self::new(209, body)
29    }
30
31    pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
32        self.headers.insert(name.into(), value.into());
33        self
34    }
35
36    pub fn header(&self, name: &str) -> Option<&str> {
37        self.headers
38            .iter()
39            .find(|(k, _)| k.eq_ignore_ascii_case(name))
40            .map(|(_, v)| v.as_str())
41    }
42
43    pub fn get_version(&self) -> Option<Vec<Version>> {
44        self.header("version")
45            .and_then(|v| protocol::parse_version_header(v).ok())
46    }
47
48    pub fn get_parents(&self) -> Option<Vec<Version>> {
49        self.header("parents")
50            .and_then(|v| protocol::parse_version_header(v).ok())
51    }
52
53    pub fn get_current_version(&self) -> Option<Vec<Version>> {
54        self.header("current-version")
55            .and_then(|v| protocol::parse_version_header(v).ok())
56    }
57
58    pub fn get_merge_type(&self) -> Option<String> {
59        self.header("merge-type").map(|s| s.to_string())
60    }
61
62    pub fn get_content_range(&self) -> Option<ContentRange> {
63        self.header("content-range")
64            .and_then(|v| ContentRange::from_header_value(v).ok())
65    }
66
67    pub fn body_str(&self) -> Option<&str> {
68        std::str::from_utf8(&self.body).ok()
69    }
70
71    #[inline]
72    pub fn is_success(&self) -> bool {
73        (200..300).contains(&self.status)
74    }
75    #[inline]
76    pub fn is_partial(&self) -> bool {
77        self.status == 206
78    }
79}
80
81impl Default for BraidResponse {
82    fn default() -> Self {
83        BraidResponse {
84            status: 200,
85            headers: BTreeMap::new(),
86            body: Bytes::new(),
87            is_subscription: false,
88        }
89    }
90}
91
92#[cfg(feature = "fuzzing")]
93impl<'a> arbitrary::Arbitrary<'a> for BraidResponse {
94    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
95        let status: u16 = u.arbitrary()?;
96        Ok(BraidResponse {
97            status,
98            headers: u.arbitrary()?,
99            body: bytes::Bytes::from(u.arbitrary::<Vec<u8>>()?),
100            is_subscription: status == 209,
101        })
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_braid_response_basic() {
111        let res = BraidResponse::new(200, "hello").with_header("Version", "\"v1\"");
112        assert_eq!(res.body_str(), Some("hello"));
113        assert_eq!(res.header("version"), Some("\"v1\""));
114    }
115}