How to deserialize actix web form data and serialize it into csv file?

Hello! I'm trying to save form data x-www-form-urlencoded into csv file in my first Rust program, how can I deserialize actix_web form data and serialize it into csv file? Is it possible to use one struct for it? How to cover csv error scenario?

extern crate csv;
#[macro_use]
extern crate serde_derive;
use actix_web::{middleware, web, App, HttpResponse, HttpServer, Result};
use serde::{Deserialize, Serialize};
use std::io;

#[derive(Serialize, Deserialize)]
struct FormData {
    email: String,
    fullname: String,
    message: String,
}

async fn contact(form: web::Form<FormData>) -> Result<String> {
    let mut wtr = csv::Writer::from_writer(io::stdout());
    wtr.serialize(form)?;
    wtr.flush()?;

    Ok(format!("Hello {}!", form.fullname))
}

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(web::resource("/contact").route(web::post().to(contact)))
    })
    .bind("127.0.0.1:8000")?
    .run()
    .await
}

I've got following errors:

error[E0277]: the trait bound `actix_web::types::form::Form<FormData>: _IMPL_DESERIALIZE_FOR_FormData::_serde::Serialize` is not satisfied
  --> src/main.rs:46:19
   |
46 |     wtr.serialize(form)?;
   |                   ^^^^ the trait `_IMPL_DESERIALIZE_FOR_FormData::_serde::Serialize` is not implemented for `actix_web::types::form::Form<FormData>`

error[E0277]: the trait bound `csv::Error: actix_http::error::ResponseError` is not satisfied
  --> src/main.rs:46:24
   |
46 |     wtr.serialize(form)?;
   |                        ^ the trait `actix_http::error::ResponseError` is not implemented for `csv::Error`
   |
   = note: required because of the requirements on the impl of `std::convert::From<csv::Error>` for `actix_http::error::Error`
   = note: required by `std::convert::From::from`

error: aborting due to 2 previous errors

Used libs:

[dependencies]
actix-web = "2.0.0"
actix-rt = "1.0.0"
serde = "1.0"
serde_derive = "1.0.110"
csv = "1.1"

If you use web::Form your request should have a content-type of 'application/x-www-form-urlencoded'.

If you send a file, you might have a 'multipart/form-data', instead and that does not work.

Try to use this crate: https://github.com/actix/actix-web/tree/master/actix-multipart

I didn't use it, as my case was just with plain html form, but I think it does what you need.

I think this part is fine, I was able to get ie. form.fullname deserialized properly, difficult part is that I can't use this deserialized FormData to store them in wtr.serialize(form)?;, actually I don't know how it should be in the "proper way" in Rust

I see now, maybe is just because the form variable is of type web::Form and not FormData, that is the struct you marked as serializable.

Maybe you just need to use form.into_inner() to have the plain struct, and that should be serializable.

1 Like

Thank you! You have solved one issue with struct.
Now I've got ^ the trait actix_http::error::ResponseError is not implemented for csv::Error
I removed Elvis operator and got new error:

   |                  ---- move occurs because `form` has type `actix_web::types::form::Form<FormData>`, which does not implement the `Copy` trait
45 |     let mut wtr = csv::Writer::from_writer(io::stdout());
46 |     wtr.serialize(form.into_inner());
   |                   ---- value moved here
...
51 |         form.fullname
   |         ^^^^ value borrowed here after move

but maybe should I keep Elvis operator and handle error explicitly?

You can use map_err to convert the error type.

The second problem is because you consume the value by using into_inner(), so you cannot access to form variable.

let form = form.into_inner();
wtr.serialize(form);
Ok(format!("Hello {}!", form.fullname))

this should work.

I've added borrowing operator to move forward:

async fn contact(form: web::Form<FormData>) -> Result<String> {
    let mut wtr = csv::Writer::from_writer(io::stdout());
    let form = form.into_inner();
    wtr.serialize(&form);
    wtr.flush()?;

    Ok(format!("Hello {}!", form.fullname))
}

but got :frowning:

warning: unused `std::result::Result` that must be used
  --> src/main.rs:47:5
   |
47 |     wtr.serialize(&form);
   |     ^^^^^^^^^^^^^^^^^^^^^
   |
   = note: `#[warn(unused_must_use)]` on by default
   = note: this `Result` may be an `Err` variant, which should be handled

I tried also to use it directly

46 |     let form = form.into_inner();
   |         ---- move occurs because `form` has type `FormData`, which does not implement the `Copy` trait
47 |     wtr.serialize(form);
   |                   ---- value moved here
...
50 |     Ok(format!("Hello {}!", form.fullname))
   |                             ^^^^^^^^^^^^^ value borrowed here after move

It seems that I can't copy FormData struct :thinking:

That is just a warning, but you can just assign the result:

let _ = wtr.serialize(&form);

And that is, if you don't need the error, but a better way is to handle the error

match wtr.serialize(&form) {
    Ok(_) => Ok(format!("done!")),
    Err(e) => {
           log.error("{:?}", e);
          Ok("Some error here...".into())
    }
}

Maybe the wtr.serialize take ownership of the struct, so you have to get the data you need:

let filename = form.filename.to_string();
match wtr.serialize(form) {
    Ok(_) => Ok(format!("done! {}", filename)),
    Err(e) => {
           log.error("{:?}", e);
          Ok("Some error here...".into())
    }
}
1 Like

You have unblocked me to do other amazing things in Rust, thank you! :slightly_smiling_face:

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.