synapse_proxy/transform/
mod.rs1pub 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
107pub struct ProxyResponse {
111 pub status: StatusCode,
112 pub headers: HeaderMap,
113 replacement: Option<Value>,
114}
115
116impl ProxyResponse {
117 pub fn new(status: StatusCode, headers: HeaderMap) -> Self {
118 Self {
119 status,
120 headers,
121 replacement: None,
122 }
123 }
124 pub fn replace_body(&mut self, value: Value) {
126 self.replacement = Some(value);
127 }
128 pub fn replacement(&self) -> Option<&Value> {
129 self.replacement.as_ref()
130 }
131}
132
133#[async_trait]
134pub trait RequestTransform: Send + Sync {
135 async fn apply(
136 &self,
137 ctx: &ResolvedContext,
138 req: &mut ProxyRequest,
139 ) -> Result<(), TransformError>;
140}
141
142#[async_trait]
143pub trait ResponseTransform: Send + Sync {
144 async fn apply(
145 &self,
146 ctx: &ResolvedContext,
147 resp: &mut ProxyResponse,
148 ) -> Result<(), TransformError>;
149}
150
151#[derive(Default, Clone)]
153pub struct TransformRegistry {
154 pub(crate) request: HashMap<String, Arc<dyn RequestTransform>>,
155 pub(crate) response: HashMap<String, Arc<dyn ResponseTransform>>,
156}
157
158impl TransformRegistry {
159 pub fn register_request(&mut self, name: impl Into<String>, t: Arc<dyn RequestTransform>) {
160 self.request.insert(name.into(), t);
161 }
162 pub fn register_response(&mut self, name: impl Into<String>, t: Arc<dyn ResponseTransform>) {
163 self.response.insert(name.into(), t);
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 fn req() -> ProxyRequest {
172 ProxyRequest::from_parts(
173 Method::POST,
174 "/x".into(),
175 None,
176 HeaderMap::new(),
177 b"{\"a\":1}".to_vec(),
178 )
179 }
180
181 #[test]
182 fn set_header_overwrites() {
183 let mut r = req();
184 r.set_header("x-test", "v1");
185 r.set_header("x-test", "v2");
186 assert_eq!(r.headers.get("x-test").unwrap(), "v2");
187 }
188
189 #[test]
190 fn body_json_mut_parses_and_serializes() {
191 let mut r = req();
192 r.body_json_mut().unwrap()["b"] = serde_json::json!(2);
193 let bytes = r.into_body_bytes();
194 let v: Value = serde_json::from_slice(&bytes).unwrap();
195 assert_eq!(v["a"], 1);
196 assert_eq!(v["b"], 2);
197 }
198
199 #[test]
200 fn body_json_mut_rejects_non_json() {
201 let mut r = ProxyRequest::from_parts(
202 Method::POST,
203 "/x".into(),
204 None,
205 HeaderMap::new(),
206 b"not json".to_vec(),
207 );
208 assert!(matches!(
209 r.body_json_mut(),
210 Err(TransformError::Reject { .. })
211 ));
212 }
213}