rwf2/Rocket

Ease Testing Forms w/`LocalForm`

開放

#1,591 建立於 2021年3月25日

 (7 則留言) (6 個反應) (2 位負責人)Rust (1,645 個分叉)batch import
enhancementhelp wanted

倉庫指標

星標
 (25,738 顆星)
PR 合併指標
 (30 天內沒有已合併 PR)

描述

Testing forms in Rocket is currently an entirely manual process:

let client = Client::tracked(rocket).unwrap();

let response = client.post("/")
    .header(ContentType::Form)
    .body("field=value&is+it=a+cat%3F")
    .dispatch();

With the addition of multipart form support, form testing verbosity is even further exacerbated:

let client = Client::tracked(rocket).unwrap();

let ct = "multipart/form-data; boundary=X-BOUNDARY"
    .parse::<ContentType>()
    .unwrap();

let body = &[
    "--X-BOUNDARY",
    r#"Content-Disposition: form-data; name="names[]""#,
    "",
    "abcd",
    "--X-BOUNDARY",
    r#"Content-Disposition: form-data; name="names[]""#,
    "",
    "123",
    "--X-BOUNDARY",
    r#"Content-Disposition: form-data; name="file"; filename="foo.txt""#,
    "Content-Type: text/plain",
    "",
    "hi there",
    "--X-BOUNDARY--",
    "",
].join("\r\n");

let response = client.post("/")
    .header(ct)
    .body(body)
    .dispatch();

Rocket should make testing applications with forms a much simpler task.

Here's how we might hope testing the above forms would look:

let client = Client::tracked(rocket).unwrap();
let response = client.post("/")
    .form(&[("field", "value"), ("is it", "a cat?")])
    .dispatch();

let client = Client::tracked(rocket).unwrap();
let response = client.post("/")
    .form(LocalForm::new()
        .field("names[]", "abcd")
        .field("names[]", "123")
        .file("foo.txt", ContentType::Plain, "hi there"))
    .dispatch();

An API enabling this might resemble:

impl Client {
    pub fn form<F: Into<LocalForm>>(&mut self, form: F) -> &mut Self {
        let form = form.into();
        self.set_header(form.content_type());
        self.set_body(form.body_data());
        self
    }
}

struct LocalForm { /* .. */ }

impl LocalForm {
    pub fn new() -> Self;

    /// A percent-decoded `name` and `value`.
    pub fn field<'v, N, V>(mut self, name: N, value: V) -> Self
        where N: Into<NameBuf<'v>>, V: AsRef<str>;

    /// Adds all of the `fields` to `self`.
    pub fn fields<'v, I, F>(mut self, fields: I) -> Self
        where I: Iterator<Item = F>, F: Into<ValueField<'v>>;

    /// A percent-encoded `name` and `value`.
    pub fn raw_field(mut self, name: &'v RawStr, value: &'v RawStr) -> Self;

    /// Adds a field for the file with name `file_name`, content-type `ct`, and
    /// file contents `data`.
    pub fn file<'v, N, V>(mut self, file_name: N, ct: ContentType, data: V) -> Self
        where N: Into<Option<&'v str>>, V: AsRef<[u8]>;

    /// Add a data field with content-type `ct` and binary `data`.
    pub fn data<'v, V>(mut self, ct: ContentType, data: V) -> Self
        where V: AsRef<[u8]>;

    /// The full content-type for this form.
    pub fn content_type(&self) -> ContentType;

    /// The full body data for this form.
    pub fn body_data(&self) -> Vec<u8>;
}

impl<'v, F: Into<ValueField<'v>>, I: Iterator<Item = F>> From<I> for LocalForm { /* .. */ }

貢獻者指南