synapse_proxy/transform/
error_remap.rs1use async_trait::async_trait;
5use axum::http::StatusCode;
6use serde_json::json;
7
8use crate::config::ErrorRemapSpec;
9use crate::context::ResolvedContext;
10
11use super::{ProxyResponse, ResponseTransform, TransformError};
12
13pub struct ErrorRemap {
14 when: StatusCode,
15 error: String,
16 detail: Option<String>,
17}
18
19impl ErrorRemap {
20 pub fn from_spec(spec: &ErrorRemapSpec) -> anyhow::Result<Self> {
21 let when = StatusCode::from_u16(spec.when_status).map_err(|_| {
22 anyhow::anyhow!("error_remap: invalid when_status {}", spec.when_status)
23 })?;
24 Ok(Self {
25 when,
26 error: spec.error.clone(),
27 detail: spec.detail.clone(),
28 })
29 }
30}
31
32#[async_trait]
33impl ResponseTransform for ErrorRemap {
34 async fn apply(
35 &self,
36 _ctx: &ResolvedContext,
37 resp: &mut ProxyResponse,
38 ) -> Result<(), TransformError> {
39 if resp.status == self.when {
40 let body = match &self.detail {
41 Some(d) => json!({ "error": self.error, "detail": d }),
42 None => json!({ "error": self.error }),
43 };
44 resp.replace_body(body);
45 }
46 Ok(())
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53 use crate::transform::ProxyResponse;
54 use axum::http::HeaderMap;
55 use std::collections::HashMap;
56
57 fn ctx() -> ResolvedContext {
58 crate::context::ContextStore::new(HashMap::new()).resolve()
59 }
60
61 #[tokio::test]
62 async fn remaps_matching_status() {
63 let er = ErrorRemap::from_spec(&ErrorRemapSpec {
64 when_status: 401,
65 error: "auth_expired".into(),
66 detail: None,
67 })
68 .unwrap();
69 let mut resp = ProxyResponse::new(StatusCode::UNAUTHORIZED, HeaderMap::new());
70 er.apply(&ctx(), &mut resp).await.unwrap();
71 assert_eq!(resp.status, StatusCode::UNAUTHORIZED); assert_eq!(resp.replacement().unwrap()["error"], "auth_expired");
73 }
74
75 #[tokio::test]
76 async fn leaves_non_matching_status() {
77 let er = ErrorRemap::from_spec(&ErrorRemapSpec {
78 when_status: 401,
79 error: "auth_expired".into(),
80 detail: None,
81 })
82 .unwrap();
83 let mut resp = ProxyResponse::new(StatusCode::OK, HeaderMap::new());
84 er.apply(&ctx(), &mut resp).await.unwrap();
85 assert!(resp.replacement().is_none());
86 }
87
88 #[test]
89 fn from_spec_rejects_invalid_status() {
90 assert!(ErrorRemap::from_spec(&ErrorRemapSpec {
91 when_status: 9999,
92 error: "x".into(),
93 detail: None
94 })
95 .is_err());
96 }
97}