Skip to main content

braid_http/types/
content_range.rs

1//! Content-Range specification for patches.
2
3use std::fmt;
4use std::str::FromStr;
5
6/// Content-Range specification for patches.
7#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
8pub struct ContentRange {
9    /// The addressing unit type (e.g., "json", "bytes").
10    pub unit: String,
11    /// The range specification within the resource.
12    pub range: String,
13}
14
15impl ContentRange {
16    #[inline]
17    #[must_use]
18    pub fn new(unit: impl Into<String>, range: impl Into<String>) -> Self {
19        ContentRange {
20            unit: unit.into(),
21            range: range.into(),
22        }
23    }
24
25    #[inline]
26    #[must_use]
27    pub fn json(range: impl Into<String>) -> Self {
28        Self::new("json", range)
29    }
30    #[inline]
31    #[must_use]
32    pub fn bytes(range: impl Into<String>) -> Self {
33        Self::new("bytes", range)
34    }
35    #[inline]
36    #[must_use]
37    pub fn text(range: impl Into<String>) -> Self {
38        Self::new("text", range)
39    }
40    #[inline]
41    #[must_use]
42    pub fn lines(range: impl Into<String>) -> Self {
43        Self::new("lines", range)
44    }
45
46    #[inline]
47    #[must_use]
48    pub fn is_json(&self) -> bool {
49        self.unit == "json"
50    }
51    #[inline]
52    #[must_use]
53    pub fn is_bytes(&self) -> bool {
54        self.unit == "bytes"
55    }
56
57    #[must_use]
58    pub fn to_header_value(&self) -> String {
59        format!("{} {}", self.unit, self.range)
60    }
61
62    pub fn from_header_value(value: &str) -> Result<Self, String> {
63        let parts: Vec<&str> = value.splitn(2, ' ').collect();
64        if parts.len() != 2 {
65            return Err(format!(
66                "Invalid Content-Range: expected 'unit range', got '{}'",
67                value
68            ));
69        }
70        Ok(ContentRange {
71            unit: parts[0].to_string(),
72            range: parts[1].to_string(),
73        })
74    }
75}
76
77impl Default for ContentRange {
78    fn default() -> Self {
79        ContentRange {
80            unit: "bytes".to_string(),
81            range: "0:0".to_string(),
82        }
83    }
84}
85
86impl fmt::Display for ContentRange {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        write!(f, "{} {}", self.unit, self.range)
89    }
90}
91
92impl FromStr for ContentRange {
93    type Err = String;
94    fn from_str(s: &str) -> Result<Self, Self::Err> {
95        Self::from_header_value(s)
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_content_range_basic() {
105        let range = ContentRange::new("json", ".field");
106        assert_eq!(range.to_header_value(), "json .field");
107        let parsed = ContentRange::from_header_value("json .field").unwrap();
108        assert_eq!(parsed, range);
109    }
110}