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