diff --git a/codegen/src/decorators/derive_form.rs b/codegen/src/decorators/derive_form.rs index 69f6e9ec..f3ac0e88 100644 --- a/codegen/src/decorators/derive_form.rs +++ b/codegen/src/decorators/derive_form.rs @@ -191,6 +191,11 @@ fn from_form_substructure(cx: &mut ExtCtxt, trait_span: Span, substr: &Substruct for (k, v) in ::rocket::request::FormItems($arg) { match k { $arms + field if field == "_method" => { + /* This is a Rocket-specific field. If the user hasn't asked + * for it, just let it go by without error. This should stay + * in sync with Rocket::preprocess. */ + } _ => { println!(" => {}={} has no matching field in struct.", k, v); diff --git a/codegen/tests/run-pass/derive_form.rs b/codegen/tests/run-pass/derive_form.rs index 7fdf79b2..b7978863 100644 --- a/codegen/tests/run-pass/derive_form.rs +++ b/codegen/tests/run-pass/derive_form.rs @@ -47,6 +47,12 @@ struct DefaultInput<'r> { arg: Option<&'r str>, } +#[derive(Debug, PartialEq, FromForm)] +struct ManualMethod<'r> { + _method: Option<&'r str>, + done: bool +} + fn main() { // Same number of arguments: simple case. let task = TodoTask::from_form_string("description=Hello&completed=on"); @@ -59,6 +65,13 @@ fn main() { let task = TodoTask::from_form_string("other=a&description=Hello&completed=on"); assert!(task.is_err()); + // Ensure _method isn't required. + let task = TodoTask::from_form_string("_method=patch&description=Hello&completed=off"); + assert_eq!(task, Ok(TodoTask { + description: "Hello".to_string(), + completed: false + })); + let form_string = &[ "password=testing", "checkbox=off", "checkbox=on", "number=10", "checkbox=off", "textarea=", "select=a", "radio=c", @@ -79,4 +92,18 @@ fn main() { assert_eq!(default, Ok(DefaultInput { arg: None })); + + // Ensure _method can be captured if desired. + let manual = ManualMethod::from_form_string("_method=put&done=true"); + assert_eq!(manual, Ok(ManualMethod { + _method: Some("put"), + done: true + })); + + // And ignored when not present. + let manual = ManualMethod::from_form_string("done=true"); + assert_eq!(manual, Ok(ManualMethod { + _method: None, + done: true + })); } diff --git a/lib/src/request/form/from_form.rs b/lib/src/request/form/from_form.rs index 08848aa5..11185b90 100644 --- a/lib/src/request/form/from_form.rs +++ b/lib/src/request/form/from_form.rs @@ -38,9 +38,14 @@ use error::Error; /// ``` /// /// When deriving `FromForm`, every field in the structure must implement -/// [FromFormValue](trait.FromFormValue.html). If you implement `FormForm` -/// yourself, use the [FormItems](struct.FormItems.html) iterator to iterate -/// through the form key/value pairs. +/// [FromFormValue](trait.FromFormValue.html). +/// +/// # Implementing +/// +/// If you implement `FormForm` yourself, use the +/// [FormItems](struct.FormItems.html) iterator to iterate through the form +/// key/value pairs. Be aware that form fields that are typically hidden from +/// your application, such as `_method`, will be present while iterating. pub trait FromForm<'f>: Sized { /// The associated error to be returned when parsing fails. type Error; diff --git a/lib/src/rocket.rs b/lib/src/rocket.rs index 7dc40ce3..a3d76233 100644 --- a/lib/src/rocket.rs +++ b/lib/src/rocket.rs @@ -75,7 +75,7 @@ impl Rocket { #[inline] fn issue_response(&self, mut response: Response, hyp_res: hyper::FreshResponse) { // Add the 'rocket' server header, and write out the response. - // TODO: If removing Hyper, write out `Data` header too. + // TODO: If removing Hyper, write out `Date` header too. response.set_header(header::Server("rocket".to_string())); match self.write_response(response, hyp_res) { @@ -134,7 +134,8 @@ impl Rocket { } /// Preprocess the request for Rocket-specific things. At this time, we're - /// only checking for _method in forms. + /// only checking for _method in forms. Keep this in-sync with derive_form + /// when preprocessing form fields. fn preprocess_request(&self, req: &mut Request, data: &Data) { // Check if this is a form and if the form contains the special _method // field which we use to reinterpret the request's method. @@ -176,7 +177,7 @@ impl Rocket { // convince it to give us another mutable reference. // FIXME: Pay the cost to copy Request into UnsafeCell? Pay the // cost to use RefCell? Move the call to `issue_response` here - // to move Request and move directly into a RefCell? + // to move Request and move directly into an UnsafeCell? let request: &'r mut Request = unsafe { &mut *(request as *const Request as *mut Request) }; diff --git a/lib/tests/form_method-issue-45.rs b/lib/tests/form_method-issue-45.rs new file mode 100644 index 00000000..cccf5a71 --- /dev/null +++ b/lib/tests/form_method-issue-45.rs @@ -0,0 +1,35 @@ +#![feature(plugin, custom_derive)] +#![plugin(rocket_codegen)] + +extern crate rocket; + +use rocket::request::Form; + +#[derive(FromForm)] +struct FormData { + form_data: String, +} + +#[patch("/", data = "")] +fn bug(form_data: Form) -> &'static str { + assert_eq!("Form data", &form_data.get().form_data); + "OK" +} + + +use rocket::testing::MockRequest; +use rocket::http::Method::*; +use rocket::http::ContentType; + +#[test] +fn method_eval() { + let rocket = rocket::ignite().mount("/", routes![bug]); + + let mut req = MockRequest::new(Patch, "/") + .header(ContentType::Form) + .body("_method=patch&form_data=Form+data"); + + let mut response = req.dispatch_with(&rocket); + let body_str = response.body().and_then(|b| b.into_string()); + assert_eq!(body_str, Some("OK".to_string())); +}