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