Skip to main content

typesafe_rs/
headers.rs

1use http::{HeaderMap, HeaderName, HeaderValue};
2
3use crate::VERSION;
4
5const PROTECTED: &[&str] = &[
6    "authorization",
7    "accept",
8    "user-agent",
9    "x-typesafe-sdk",
10    "x-typesafe-runtime",
11    "x-typesafe-retry-count",
12];
13
14pub(crate) fn is_protected(name: &HeaderName) -> bool {
15    PROTECTED.contains(&name.as_str())
16}
17
18/// Copy user headers, ignoring names the SDK owns.
19pub(crate) fn merge_user_headers(dst: &mut HeaderMap, src: &HeaderMap) {
20    for (name, value) in src {
21        if !is_protected(name) {
22            dst.insert(name.clone(), value.clone());
23        }
24    }
25}
26
27pub(crate) fn user_agent() -> HeaderValue {
28    static_or_owned(&format!("typesafe-rs/{VERSION}"))
29}
30
31pub(crate) fn sdk_header() -> HeaderValue {
32    static_or_owned(&format!("typesafe-rs/{VERSION}"))
33}
34
35pub(crate) fn runtime_header() -> HeaderValue {
36    static_or_owned(&format!(
37        "rust/{}; {}-{}",
38        env!("TYPESAFE_RUSTC_VERSION"),
39        env!("TYPESAFE_TARGET_OS"),
40        env!("TYPESAFE_TARGET_ARCH"),
41    ))
42}
43
44fn static_or_owned(value: &str) -> HeaderValue {
45    HeaderValue::from_str(value).unwrap_or_else(|_| HeaderValue::from_static("typesafe-rs"))
46}
47
48pub(crate) fn request_id(headers: &HeaderMap) -> Option<String> {
49    headers
50        .get("x-typesafe-request-id")
51        .and_then(|v| v.to_str().ok())
52        .map(str::to_owned)
53}
54
55pub(crate) fn retry_count_value(attempt: u32) -> HeaderValue {
56    HeaderValue::from_str(&attempt.to_string()).unwrap_or_else(|_| HeaderValue::from_static("1"))
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use http::header::{ACCEPT, AUTHORIZATION, USER_AGENT};
63
64    #[test]
65    fn protected_headers_are_skipped() {
66        let mut dst = HeaderMap::new();
67        let mut src = HeaderMap::new();
68        src.insert(AUTHORIZATION, HeaderValue::from_static("Bearer stolen"));
69        src.insert(ACCEPT, HeaderValue::from_static("text/plain"));
70        src.insert(USER_AGENT, HeaderValue::from_static("other"));
71        src.insert("x-typesafe-sdk", HeaderValue::from_static("nope"));
72        src.insert("x-custom", HeaderValue::from_static("ok"));
73        merge_user_headers(&mut dst, &src);
74        assert!(dst.get(AUTHORIZATION).is_none());
75        assert!(dst.get(ACCEPT).is_none());
76        assert_eq!(dst.get("x-custom").unwrap(), "ok");
77    }
78}