postrust_response/
headers.rs1use http::{HeaderMap, HeaderValue};
4use postrust_core::ApiRequest;
5use std::fmt;
6
7#[derive(Clone, Debug)]
9pub struct ContentRange {
10 pub start: i64,
12 pub end: i64,
14 pub total: Option<i64>,
16 pub unit: String,
18}
19
20impl ContentRange {
21 pub fn new(start: i64, end: i64, total: Option<i64>) -> Self {
23 Self {
24 start,
25 end,
26 total,
27 unit: "items".to_string(),
28 }
29 }
30
31 pub fn from_pagination(
33 offset: i64,
34 limit: Option<i64>,
35 count: i64,
36 total: Option<i64>,
37 ) -> Self {
38 let end = match limit {
39 Some(l) => (offset + l - 1).min(offset + count - 1).max(offset),
40 None => offset + count - 1,
41 };
42
43 Self::new(offset, end, total)
44 }
45}
46
47impl fmt::Display for ContentRange {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 match self.total {
50 Some(total) => write!(f, "{} {}-{}/{}", self.unit, self.start, self.end, total),
51 None => write!(f, "{} {}-{}/*", self.unit, self.start, self.end),
52 }
53 }
54}
55
56pub fn build_response_headers(
58 request: &ApiRequest,
59 content_type: &str,
60 content_range: Option<&ContentRange>,
61 location: Option<&str>,
62) -> HeaderMap {
63 let mut headers = HeaderMap::new();
64
65 if let Ok(v) = HeaderValue::from_str(content_type) {
67 headers.insert(http::header::CONTENT_TYPE, v);
68 }
69
70 if let Some(range) = content_range {
72 if let Ok(v) = HeaderValue::from_str(&range.to_string()) {
73 headers.insert(http::header::CONTENT_RANGE, v);
74 }
75 }
76
77 if let Some(loc) = location {
79 if let Ok(v) = HeaderValue::from_str(loc) {
80 headers.insert(http::header::LOCATION, v);
81 }
82 }
83
84 if request.negotiated_by_profile {
86 if let Ok(v) = HeaderValue::from_str(&request.schema) {
87 headers.insert(http::header::HeaderName::from_static("content-profile"), v);
88 }
89 }
90
91 if let Some(applied) =
93 postrust_core::api_request::preferences::preference_applied(&request.preferences)
94 {
95 if let Ok(v) = HeaderValue::from_str(&applied) {
96 headers.insert(
97 http::header::HeaderName::from_static("preference-applied"),
98 v,
99 );
100 }
101 }
102
103 headers
104}
105
106#[allow(dead_code)] pub fn parse_guc_headers(guc_headers: &str) -> Vec<(String, String)> {
109 guc_headers
111 .lines()
112 .filter_map(|line| {
113 let mut parts = line.splitn(2, ':');
114 let key = parts.next()?.trim().to_string();
115 let value = parts.next()?.trim().to_string();
116 Some((key, value))
117 })
118 .collect()
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn test_content_range_display() {
127 let range = ContentRange::new(0, 9, Some(100));
128 assert_eq!(range.to_string(), "items 0-9/100");
129
130 let range = ContentRange::new(10, 19, None);
131 assert_eq!(range.to_string(), "items 10-19/*");
132 }
133
134 #[test]
135 fn test_content_range_from_pagination() {
136 let range = ContentRange::from_pagination(0, Some(10), 10, Some(100));
138 assert_eq!(range.start, 0);
139 assert_eq!(range.end, 9);
140
141 let range = ContentRange::from_pagination(90, Some(10), 5, Some(95));
143 assert_eq!(range.start, 90);
144 assert_eq!(range.end, 94);
145 }
146
147 #[test]
148 fn test_parse_guc_headers() {
149 let guc = "X-Custom-Header: value1\nX-Another: value2";
150 let headers = parse_guc_headers(guc);
151
152 assert_eq!(headers.len(), 2);
153 assert_eq!(headers[0], ("X-Custom-Header".into(), "value1".into()));
154 assert_eq!(headers[1], ("X-Another".into(), "value2".into()));
155 }
156}