1pub mod error_remap;
6pub mod inject;
7pub mod wrap;
8
9use std::collections::HashMap;
10use std::sync::Arc;
11
12use async_trait::async_trait;
13use axum::http::{HeaderMap, HeaderName, HeaderValue, Method, StatusCode};
14use serde_json::Value;
15
16use crate::context::ResolvedContext;
17
18#[derive(Debug, Clone)]
20pub enum TransformError {
21 Reject {
22 status: StatusCode,
23 error: String,
24 detail: String,
25 },
26 Internal(String),
27}
28
29pub struct ProxyRequest {
33 pub method: Method,
34 pub path: String,
35 pub query: Option<String>,
36 pub headers: HeaderMap,
37 body: Body,
38}
39
40enum Body {
41 Bytes(Vec<u8>),
42 Json(Value),
43}
44
45impl ProxyRequest {
46 pub fn from_parts(
47 method: Method,
48 path: String,
49 query: Option<String>,
50 headers: HeaderMap,
51 bytes: Vec<u8>,
52 ) -> Self {
53 Self {
54 method,
55 path,
56 query,
57 headers,
58 body: Body::Bytes(bytes),
59 }
60 }
61
62 pub fn set_header(&mut self, name: &str, value: &str) {
64 if let (Ok(n), Ok(v)) = (HeaderName::try_from(name), HeaderValue::try_from(value)) {
65 self.headers.insert(n, v);
66 }
67 }
68
69 pub fn remove_header(&mut self, name: &str) {
72 if let Ok(n) = HeaderName::try_from(name) {
73 self.headers.remove(n);
74 }
75 }
76
77 pub fn body_json_mut(&mut self) -> Result<&mut Value, TransformError> {
80 if let Body::Bytes(b) = &self.body {
81 let parsed = if b.is_empty() {
82 Value::Object(Default::default())
83 } else {
84 serde_json::from_slice(b).map_err(|e| TransformError::Reject {
85 status: StatusCode::BAD_REQUEST,
86 error: "invalid_body".into(),
87 detail: format!("expected JSON body: {e}"),
88 })?
89 };
90 self.body = Body::Json(parsed);
91 }
92 match &mut self.body {
93 Body::Json(v) => Ok(v),
94 Body::Bytes(_) => unreachable!(),
95 }
96 }
97
98 pub fn into_body_bytes(self) -> Vec<u8> {
100 match self.body {
101 Body::Bytes(b) => b,
102 Body::Json(v) => serde_json::to_vec(&v).unwrap_or_default(),
103 }
104 }
105
106 pub fn into_forward_parts(self) -> (HeaderMap, Vec<u8>) {
113 let reencoded = matches!(self.body, Body::Json(_));
114 let bytes = match self.body {
115 Body::Bytes(b) => b,
116 Body::Json(v) => serde_json::to_vec(&v).unwrap_or_default(),
117 };
118 let mut headers = self.headers;
119 if reencoded {
120 headers.remove("content-length");
121 headers.remove("transfer-encoding");
122 }
123 (headers, bytes)
124 }
125}
126
127pub struct ProxyResponse {
131 pub status: StatusCode,
132 pub headers: HeaderMap,
133 replacement: Option<Value>,
134}
135
136impl ProxyResponse {
137 pub fn new(status: StatusCode, headers: HeaderMap) -> Self {
138 Self {
139 status,
140 headers,
141 replacement: None,
142 }
143 }
144 pub fn replace_body(&mut self, value: Value) {
146 self.replacement = Some(value);
147 }
148 pub fn replacement(&self) -> Option<&Value> {
149 self.replacement.as_ref()
150 }
151}
152
153#[async_trait]
154pub trait RequestTransform: Send + Sync {
155 async fn apply(
156 &self,
157 ctx: &ResolvedContext,
158 req: &mut ProxyRequest,
159 ) -> Result<(), TransformError>;
160}
161
162#[async_trait]
163pub trait ResponseTransform: Send + Sync {
164 async fn apply(
165 &self,
166 ctx: &ResolvedContext,
167 resp: &mut ProxyResponse,
168 ) -> Result<(), TransformError>;
169}
170
171#[derive(Default, Clone)]
173pub struct TransformRegistry {
174 pub(crate) request: HashMap<String, Arc<dyn RequestTransform>>,
175 pub(crate) response: HashMap<String, Arc<dyn ResponseTransform>>,
176}
177
178impl TransformRegistry {
179 pub fn register_request(&mut self, name: impl Into<String>, t: Arc<dyn RequestTransform>) {
180 self.request.insert(name.into(), t);
181 }
182 pub fn register_response(&mut self, name: impl Into<String>, t: Arc<dyn ResponseTransform>) {
183 self.response.insert(name.into(), t);
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 fn req() -> ProxyRequest {
192 ProxyRequest::from_parts(
193 Method::POST,
194 "/x".into(),
195 None,
196 HeaderMap::new(),
197 b"{\"a\":1}".to_vec(),
198 )
199 }
200
201 #[test]
202 fn set_header_overwrites() {
203 let mut r = req();
204 r.set_header("x-test", "v1");
205 r.set_header("x-test", "v2");
206 assert_eq!(r.headers.get("x-test").unwrap(), "v2");
207 }
208
209 #[test]
210 fn body_json_mut_parses_and_serializes() {
211 let mut r = req();
212 r.body_json_mut().unwrap()["b"] = serde_json::json!(2);
213 let bytes = r.into_body_bytes();
214 let v: Value = serde_json::from_slice(&bytes).unwrap();
215 assert_eq!(v["a"], 1);
216 assert_eq!(v["b"], 2);
217 }
218
219 #[test]
220 fn into_forward_parts_strips_stale_content_length_after_json_inject() {
221 let mut headers = HeaderMap::new();
222 headers.insert("content-length", "10".parse().unwrap());
223 headers.insert("transfer-encoding", "chunked".parse().unwrap());
224 let mut r = ProxyRequest::from_parts(
225 Method::POST,
226 "/x".into(),
227 None,
228 headers,
229 br#"{"a":1}"#.to_vec(),
230 );
231 r.body_json_mut().unwrap()["orgId"] = serde_json::json!("acme");
232 let (headers, bytes) = r.into_forward_parts();
233 assert!(headers.get("content-length").is_none());
234 assert!(headers.get("transfer-encoding").is_none());
235 let v: Value = serde_json::from_slice(&bytes).unwrap();
236 assert_eq!(v["orgId"], "acme");
237 assert!(bytes.len() > 10);
238 }
239
240 #[test]
241 fn into_forward_parts_keeps_content_length_when_body_unchanged() {
242 let mut headers = HeaderMap::new();
243 headers.insert("content-length", "7".parse().unwrap());
244 let r = ProxyRequest::from_parts(
245 Method::POST,
246 "/x".into(),
247 None,
248 headers,
249 br#"{"a":1}"#.to_vec(),
250 );
251 let (headers, bytes) = r.into_forward_parts();
252 assert_eq!(headers.get("content-length").unwrap(), "7");
253 assert_eq!(bytes, br#"{"a":1}"#);
254 }
255}