rainier_middleware/
method_override.rs1use rainier_http::{Method, Request, Response};
36
37use crate::pipeline::{Middleware, Next};
38
39const FIELD: &str = "_method";
42
43const HEADER: &str = "x-http-method-override";
45
46#[derive(Debug, Clone)]
48pub struct MethodOverride {
49 trust_header: bool,
50}
51
52impl Default for MethodOverride {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl MethodOverride {
59 pub fn new() -> Self {
61 Self { trust_header: false }
62 }
63
64 #[must_use = "this returns a configured middleware rather than configuring in place"]
72 pub fn trusting_the_header(mut self) -> Self {
73 self.trust_header = true;
74 self
75 }
76}
77
78#[async_trait::async_trait]
79impl Middleware for MethodOverride {
80 async fn handle(&self, mut request: Request, next: Next) -> Response {
81 if request.method() == Method::POST {
84 if let Some(method) = self.requested(&request) {
85 request.set_method(method);
86 }
87 }
88
89 next.run(request).await
90 }
91
92 fn name(&self) -> &'static str {
93 "MethodOverride"
94 }
95}
96
97impl MethodOverride {
98 fn requested(&self, request: &Request) -> Option<Method> {
101 let asked = request
102 .input(FIELD)
103 .or_else(|| {
104 self.trust_header.then(|| request.header(HEADER).map(str::to_owned)).flatten()
105 })?
106 .trim()
107 .to_ascii_uppercase();
108
109 match asked.as_str() {
110 "PUT" => Some(Method::PUT),
115 "PATCH" => Some(Method::PATCH),
116 "DELETE" => Some(Method::DELETE),
117 _ => None,
118 }
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use crate::pipeline::Pipeline;
126 use serde_json::json;
127
128 async fn method_seen(request: Request, middleware: MethodOverride) -> String {
130 Pipeline::new()
131 .through(middleware)
132 .then(|request: Request| async move { Response::ok(request.method().to_string()) })
133 .run(request)
134 .await
135 .into_string()
136 .await
137 .unwrap()
138 }
139
140 fn form(method: Method, body: serde_json::Value) -> Request {
141 Request::builder().method(method).uri("/posts/7").json(&body).build()
142 }
143
144 #[tokio::test]
145 async fn a_post_with_the_field_becomes_that_method() {
146 for (asked, expected) in [("PUT", "PUT"), ("PATCH", "PATCH"), ("DELETE", "DELETE")] {
147 let request = form(Method::POST, json!({ FIELD: asked }));
148 assert_eq!(method_seen(request, MethodOverride::new()).await, expected);
149 }
150 }
151
152 #[tokio::test]
153 async fn the_spelling_does_not_have_to_be_shouted() {
154 let request = form(Method::POST, json!({ FIELD: "delete" }));
155 assert_eq!(method_seen(request, MethodOverride::new()).await, "DELETE");
156 }
157
158 #[tokio::test]
159 async fn a_get_is_never_rewritten() {
160 let request = Request::builder().method(Method::GET).uri("/posts/7?_method=DELETE").build();
163
164 assert_eq!(method_seen(request, MethodOverride::new()).await, "GET");
165 }
166
167 #[tokio::test]
168 async fn a_post_cannot_become_a_get() {
169 let request = form(Method::POST, json!({ FIELD: "GET" }));
170 assert_eq!(method_seen(request, MethodOverride::new()).await, "POST");
171 }
172
173 #[tokio::test]
174 async fn nonsense_is_ignored_rather_than_refused() {
175 let request = form(Method::POST, json!({ FIELD: "TEAPOT" }));
178 assert_eq!(method_seen(request, MethodOverride::new()).await, "POST");
179 }
180
181 #[tokio::test]
182 async fn the_header_is_ignored_until_it_is_trusted() {
183 let request = || {
184 Request::builder().method(Method::POST).uri("/posts/7").header(HEADER, "DELETE").build()
185 };
186
187 assert_eq!(method_seen(request(), MethodOverride::new()).await, "POST");
188 assert_eq!(
189 method_seen(request(), MethodOverride::new().trusting_the_header()).await,
190 "DELETE"
191 );
192 }
193
194 #[tokio::test]
195 async fn a_request_with_nothing_to_say_is_left_alone() {
196 let request = form(Method::POST, json!({ "title": "Hello" }));
197 assert_eq!(method_seen(request, MethodOverride::new()).await, "POST");
198 }
199}