Skip to main content

a3s_boot/http/
response_passthrough.rs

1use super::{BootResponse, CookieOptions};
2use crate::{BootError, Result};
3use std::collections::BTreeMap;
4use std::fmt;
5use std::sync::{Arc, RwLock};
6
7/// Request-bound response metadata handle for Nest-style passthrough response decorators.
8#[derive(Clone, Default)]
9pub struct ResponsePassthrough {
10    state: Arc<RwLock<ResponsePassthroughState>>,
11}
12
13#[derive(Debug, Clone, Default)]
14struct ResponsePassthroughState {
15    status: Option<u16>,
16    headers: BTreeMap<String, String>,
17    appended_headers: Vec<(String, String)>,
18}
19
20impl fmt::Debug for ResponsePassthrough {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        match self.snapshot() {
23            Ok(state) => f
24                .debug_struct("ResponsePassthrough")
25                .field("status", &state.status)
26                .field("headers", &state.headers)
27                .field("appended_headers", &state.appended_headers)
28                .finish(),
29            Err(_) => f
30                .debug_struct("ResponsePassthrough")
31                .field("state", &"<poisoned>")
32                .finish(),
33        }
34    }
35}
36
37impl ResponsePassthrough {
38    pub fn new() -> Self {
39        Self::default()
40    }
41
42    pub fn set_status(&self, status: u16) -> Result<()> {
43        BootResponse::empty(status).validate_status()?;
44        self.write_state()?.status = Some(status);
45        Ok(())
46    }
47
48    pub fn status(&self, status: u16) -> Result<Self> {
49        self.set_status(status)?;
50        Ok(self.clone())
51    }
52
53    pub fn set_header(&self, name: impl Into<String>, value: impl Into<String>) -> Result<()> {
54        let (name, value) = normalized_header(name, value)?;
55        self.write_state()?.headers.insert(name, value);
56        Ok(())
57    }
58
59    pub fn header(&self, name: impl Into<String>, value: impl Into<String>) -> Result<Self> {
60        self.set_header(name, value)?;
61        Ok(self.clone())
62    }
63
64    pub fn append_header(&self, name: impl Into<String>, value: impl Into<String>) -> Result<()> {
65        let (name, value) = normalized_header(name, value)?;
66        self.write_state()?.appended_headers.push((name, value));
67        Ok(())
68    }
69
70    pub fn set_cookie(
71        &self,
72        name: impl AsRef<str>,
73        value: impl AsRef<str>,
74        options: CookieOptions,
75    ) -> Result<()> {
76        let header = options.set_cookie_header(name.as_ref(), value.as_ref())?;
77        self.append_header("set-cookie", header)
78    }
79
80    pub fn cookie(
81        &self,
82        name: impl AsRef<str>,
83        value: impl AsRef<str>,
84        options: CookieOptions,
85    ) -> Result<Self> {
86        self.set_cookie(name, value, options)?;
87        Ok(self.clone())
88    }
89
90    pub fn delete_cookie(&self, name: impl AsRef<str>, options: CookieOptions) -> Result<()> {
91        let header = options.delete_cookie_header(name.as_ref())?;
92        self.append_header("set-cookie", header)
93    }
94
95    pub fn has_changes(&self) -> Result<bool> {
96        let state = self.snapshot()?;
97        Ok(state.status.is_some()
98            || !state.headers.is_empty()
99            || !state.appended_headers.is_empty())
100    }
101
102    pub fn apply(&self, mut response: BootResponse) -> Result<BootResponse> {
103        let state = self.snapshot()?;
104        if let Some(status) = state.status {
105            response = response.with_status(status);
106        }
107        for (name, value) in state.headers {
108            response = response.with_header(name, value);
109        }
110        for (name, value) in state.appended_headers {
111            response = response.append_header(name, value);
112        }
113        Ok(response)
114    }
115
116    fn snapshot(&self) -> Result<ResponsePassthroughState> {
117        self.state
118            .read()
119            .map_err(|_| BootError::Internal("response passthrough lock is poisoned".to_string()))
120            .map(|state| state.clone())
121    }
122
123    fn write_state(&self) -> Result<std::sync::RwLockWriteGuard<'_, ResponsePassthroughState>> {
124        self.state
125            .write()
126            .map_err(|_| BootError::Internal("response passthrough lock is poisoned".to_string()))
127    }
128}
129
130fn normalized_header(
131    name: impl Into<String>,
132    value: impl Into<String>,
133) -> Result<(String, String)> {
134    let response = BootResponse::empty(200).with_header(name, value);
135    response.validate_headers()?;
136    response
137        .headers
138        .into_iter()
139        .next()
140        .ok_or_else(|| BootError::Internal("missing normalized response header".to_string()))
141}