apisix_admin_client/models/plugins/
proxy_rewrite.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use strum_macros::{Display, EnumString};
4use crate::{Result};
5use crate::models::Plugin;
6
7/// Builder pattern to create a ProxyRewrite
8#[serde_with::skip_serializing_none]
9#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub struct ProxyRewriteBuilder {
11    pub uri: Option<String>,
12    pub method: Option<ProxyRewriteMethod>,
13    pub regex_uri: Option<Vec<String>>,
14    pub host: Option<String>,
15    pub use_real_request_uri_unsafe: Option<bool>,
16    pub headers: Option<ProxyRewriteHeaders>,
17}
18
19impl ProxyRewriteBuilder {
20    pub fn new() -> Self {
21        ProxyRewrite::default().into()
22    }
23
24    /// New Upstream forwarding address. Value supports Nginx variables.
25    ///
26    /// Example, $arg_name
27    pub fn with_uri(mut self, uri: impl Into<String>) -> Self {
28        self.uri = Some(uri.into());
29        self
30    }
31
32    /// Rewrites the HTTP method
33    pub fn with_method(mut self, method: ProxyRewriteMethod) -> Self {
34        self.method = Some(method);
35        self
36    }
37
38    /// Regular expressions can be used to match the URL from client.
39    /// If it matches, the URL template is forwarded to the upstream.
40    /// Otherwise, the URL from the client is forwarded.
41    /// When both uri and regex_uri are configured, uri has a higher priority.
42    /// Multiple regular expressions are currently supported for pattern matching,
43    /// and the plugin will try to match them one by one until they succeed or all fail.
44    ///
45    /// For example:
46    /// ["^/iresty/(. *)/(. *)/(. *)", "/$1-$2-$3", ^/theothers/(. *)/(. *)", "/theothers/$1-$2"],
47    /// the element with the odd index represents the uri regular expression that matches
48    /// the request from the client, and the element with the even index represents
49    /// the uri template that is forwarded upstream upon a successful match.
50    /// Please note that the length of this value must be an even number.
51    pub fn with_regex_uri(mut self, regex_uri: Vec<String>) -> Self {
52        self.regex_uri = Some(regex_uri);
53        self
54    }
55
56    /// New Upstream host address
57    pub fn with_host(mut self, host: impl Into<String>) -> Self {
58        self.host = Some(host.into());
59        self
60    }
61
62    /// Use real_request_uri (original $request_uri in nginx) to bypass URI normalization.
63    /// Enabling this is considered unsafe as it bypasses all URI normalization steps.
64    pub fn with_use_real_request_uri_unsafe(mut self, use_real_request_uri_unsafe: bool) -> Self {
65        self.use_real_request_uri_unsafe = Some(use_real_request_uri_unsafe);
66        self
67    }
68
69    pub fn with_headers(mut self, headers: ProxyRewriteHeaders) -> Self {
70        self.headers = Some(headers);
71        self
72    }
73
74    /// Header manipulator
75    pub fn build(self) -> Result<ProxyRewrite> {
76        Ok(ProxyRewrite {
77            uri: self.uri,
78            method: self.method,
79            regex_uri: self.regex_uri,
80            host: self.host,
81            use_real_request_uri_unsafe: self.use_real_request_uri_unsafe,
82            headers: self.headers,
83        })
84    }
85}
86
87/// The proxy-rewrite Plugin rewrites Upstream proxy information such as scheme, uri and host.
88/// [Documentation](https://apisix.apache.org/docs/apisix/plugins/proxy-rewrite/)
89#[serde_with::skip_serializing_none]
90#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
91pub struct ProxyRewrite {
92    pub uri: Option<String>,
93    pub method: Option<ProxyRewriteMethod>,
94    pub regex_uri: Option<Vec<String>>,
95    pub host: Option<String>,
96    pub use_real_request_uri_unsafe: Option<bool>,
97    pub headers: Option<ProxyRewriteHeaders>,
98}
99
100impl From<ProxyRewrite> for ProxyRewriteBuilder {
101    fn from(item: ProxyRewrite) -> Self {
102        ProxyRewriteBuilder {
103            uri: item.uri,
104            method: item.method,
105            regex_uri: item.regex_uri,
106            host: item.host,
107            use_real_request_uri_unsafe: item.use_real_request_uri_unsafe,
108            headers: item.headers,
109        }
110    }
111}
112impl Plugin for ProxyRewrite {}
113
114/// [add]: Append the new headers.
115/// The format is {"name": "value",...}.
116/// The values in the header can contain Nginx variables like $remote_addr and $balancer_ip.
117/// It also supports referencing the match result of regex_uri as a variable like $1-$2-$3
118///
119/// [set]: Overwrite the headers. If the header does not exist, it will be added.
120/// The format is {"name": "value", ...}.
121/// The values in the header can contain Nginx variables like $remote_addr and $balancer_ip.
122/// It also supports referencing the match result of regex_uri as a variable like $1-$2-$3.
123/// Note that if you would like to set the Host header, use the host attribute instead
124///
125/// [remove]: Remove the headers. The format is ["name", ...]
126#[serde_with::skip_serializing_none]
127#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
128pub struct ProxyRewriteHeaders {
129    pub set: Option<Value>,
130    pub add: Option<Value>,
131    pub remove: Option<Value>,
132}
133
134/// Rewrites the HTTP method
135#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Display, EnumString)]
136#[allow(non_camel_case_types)]
137#[strum(ascii_case_insensitive)]
138#[non_exhaustive]
139pub enum ProxyRewriteMethod {
140    GET,
141    POST,
142    PUT,
143    HEAD,
144    DELETE,
145    OPTIONS,
146    MKCOL,
147    COPY,
148    MOVE,
149    PROPFIND,
150    LOCK,
151    UNLOCK,
152    PATH,
153    TRACE,
154}
155
156
157// region: tests
158#[cfg(test)]
159mod tests {
160    use serde_json::{to_string, to_string_pretty};
161    use super::*;
162    use tracing::{error, info};
163    use tracing_test::traced_test;
164    use crate::models::admin_upstream_requests::UpstreamType;
165    use crate::models::common::TypedItem;
166
167    #[traced_test]
168    #[tokio::test]
169    async fn test_parse_proxy_rewrite_empty_response() {
170        let nodes = r#"{}"#;
171        let nodes: ProxyRewrite = serde_json::from_str(nodes).unwrap();
172        assert_eq!(nodes.host, None);
173        assert_eq!(nodes.method, None);
174        assert_eq!(nodes.uri, None);
175        assert_eq!(nodes.regex_uri, None);
176        assert_eq!(nodes.use_real_request_uri_unsafe, None);
177    }
178
179    #[traced_test]
180    #[tokio::test]
181    async fn test_parse_proxy_rewrite_response() {
182        let nodes = r#"
183        {
184            "regex_uri": [
185                "^/auth/v1/(. *)",
186                "/v1/$1"
187            ],
188            "use_real_request_uri_unsafe": false
189        }"#;
190        let nodes: ProxyRewrite = serde_json::from_str(nodes).unwrap();
191        assert_eq!(nodes.regex_uri.unwrap(), vec!["^/auth/v1/(. *)", "/v1/$1"]);
192        assert_eq!(nodes.use_real_request_uri_unsafe.unwrap(), false);
193    }
194
195    #[traced_test]
196    #[tokio::test]
197    async fn test_parse_proxy_rewrite_headers_response() {
198        let nodes = r#"
199        {
200            "uri": "/test/home.html",
201            "host": "iresty.com",
202            "headers": {
203               "set": {
204                    "X-Api-Version": "v1",
205                    "X-Api-Engine": "apisix",
206                    "X-Api-useless": ""
207                },
208                "add": {
209                    "X-Request-ID": "112233"
210                },
211                "remove":[
212                    "X-test"
213                ]
214            }
215        }"#;
216        let nodes: ProxyRewrite = serde_json::from_str(nodes).unwrap();
217        assert_eq!(nodes.uri.unwrap(), "/test/home.html");
218        assert_eq!(nodes.host.unwrap(), "iresty.com");
219        assert_eq!(nodes.headers.clone().unwrap().set.unwrap()["X-Api-Version"], "v1");
220        assert_eq!(nodes.headers.clone().unwrap().set.unwrap()["X-Api-Engine"], "apisix");
221        assert_eq!(nodes.headers.clone().unwrap().set.unwrap()["X-Api-useless"], "");
222        assert_eq!(nodes.headers.clone().unwrap().add.unwrap()["X-Request-ID"], "112233");
223        assert_eq!(nodes.headers.clone().unwrap().remove.unwrap()[0], "X-test");
224    }
225}
226// endregion: tests