mocra_core/common/model/
headers.rs1use crate::cacheable::CacheAble;
2use reqwest::header::{HeaderMap, HeaderValue};
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6#[derive(Clone, Serialize, Deserialize)]
7pub struct HeaderItem {
8 pub key: String,
9 pub value: String,
10}
11
12impl fmt::Debug for HeaderItem {
13 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14 let key_lower = self.key.to_lowercase();
15 let value = if key_lower.contains("auth")
16 || key_lower.contains("cookie")
17 || key_lower.contains("secret")
18 || key_lower.contains("token")
19 {
20 "***REDACTED***"
21 } else {
22 &self.value
23 };
24
25 f.debug_struct("HeaderItem")
26 .field("key", &self.key)
27 .field("value", &value)
28 .finish()
29 }
30}
31
32#[derive(Clone, Serialize, Deserialize)]
33pub struct Headers {
34 pub headers: Vec<HeaderItem>,
35}
36
37impl fmt::Debug for Headers {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 f.debug_struct("Headers")
40 .field("headers", &self.headers)
41 .finish()
42 }
43}
44
45impl From<Headers> for Vec<(String, String)> {
46 fn from(value: Headers) -> Self {
47 value
48 .headers
49 .into_iter()
50 .map(|h| (h.key, h.value))
51 .collect()
52 }
53}
54
55impl Default for Headers {
56 fn default() -> Self {
57 Self::new()
58 }
59}
60
61impl Headers {
62 pub fn new() -> Self {
63 Headers {
64 headers: Vec::new(),
65 }
66 }
67
68 pub fn add(mut self, key: impl AsRef<str>, value: impl AsRef<str>) -> Self {
69 if let Some(v) = self
70 .headers
71 .iter_mut()
72 .find(|h| h.key.eq_ignore_ascii_case(key.as_ref()))
73 {
74 v.value = value.as_ref().into();
75 } else {
76 self.headers.push(HeaderItem {
77 key: key.as_ref().into(),
78 value: value.as_ref().into(),
79 })
80 }
81 self
82 }
83 pub fn merge(&mut self, other: &Headers) {
84 for header_item in &other.headers {
85 self.headers.push(header_item.clone());
86 }
87 }
88 pub fn merge_if_absent(&mut self, other: &Headers) {
89 for header_item in &other.headers {
90 if !self
91 .headers
92 .iter()
93 .any(|h| h.key.eq_ignore_ascii_case(&header_item.key))
94 {
95 self.headers.push(header_item.clone());
96 }
97 }
98 }
99 pub fn merge_map(&mut self, other: &HeaderMap) {
100 for (key, value) in other.iter() {
101 if let Ok(value_str) = value.to_str() {
102 self.headers.push(HeaderItem {
103 key: key.as_str().to_string(),
104 value: value_str.to_string(),
105 });
106 }
107 }
108 }
109 pub fn is_empty(&self) -> bool {
110 self.headers.is_empty()
111 }
112 pub fn contains(&self, key: impl AsRef<str>) -> bool {
113 self.headers
114 .iter()
115 .any(|header_item| header_item.key.eq_ignore_ascii_case(key.as_ref()))
116 }
117 pub fn get(&self, key: impl AsRef<str>) -> Option<String> {
118 for header_item in &self.headers {
119 if header_item.key.eq_ignore_ascii_case(key.as_ref()) {
120 return Some(header_item.value.clone());
121 }
122 }
123 None
124 }
125}
126
127impl From<&Headers> for HeaderMap {
128 fn from(value: &Headers) -> Self {
129 let mut header_map = HeaderMap::new();
130
131 for header_item in &value.headers {
132 match (
133 header_item.key.parse::<reqwest::header::HeaderName>(),
134 HeaderValue::from_str(&header_item.value),
135 ) {
136 (Ok(name), Ok(value)) => {
137 let name_str = name.as_str().to_lowercase();
139 if matches!(
140 name_str.as_str(),
141 "set-cookie" | "cookie" |
143 "accept" | "accept-encoding" | "accept-language" | "accept-charset" |
145 "cache-control" | "pragma" |
147 "link" | "forwarded" | "x-forwarded-for" | "x-forwarded-proto" |
149 "content-security-policy" | "x-content-security-policy" |
151 "x-webkit-csp" | "feature-policy" | "permissions-policy" |
152 "access-control-allow-origin" | "access-control-allow-methods" |
154 "access-control-allow-headers" | "access-control-expose-headers" |
155 "www-authenticate" | "proxy-authenticate" |
157 "vary" | "via" | "warning" |
159 "x-forwarded-host" | "x-real-ip" | "x-original-forwarded-for"
161 ) {
162 header_map.append(name, value);
163 } else {
164 header_map.insert(name, value);
166 }
167 }
168 _ => continue,
169 }
170 }
171
172 header_map
173 }
174}
175impl From<HeaderMap> for Headers {
176 fn from(value: HeaderMap) -> Self {
177 let headers = value
178 .iter()
179 .map(|(key, value)| HeaderItem {
180 key: key.as_str().to_string(),
181 value: value.to_str().unwrap_or("").to_string(),
182 })
183 .collect();
184
185 Headers { headers }
186 }
187}
188
189impl CacheAble for Headers {
190 fn field() -> impl AsRef<str> {
191 "headers"
192 }
193}
194
195#[test]
196fn test() {
197 let headers = Headers::new()
198 .add("Content-Type", "application/json")
199 .add("User-Agent", "MyApp/1.0");
200
201 let header_map: HeaderMap = HeaderMap::from(&headers);
202
203 assert_eq!(header_map.get("Content-Type").unwrap(), "application/json");
204 assert_eq!(header_map.get("User-Agent").unwrap(), "MyApp/1.0");
205}