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 { /* .. */ }

贡献者指南