Option: none. Failed to deserialize form body. Are there any options for skipping fields..?

There is an html form..

<form class="card" method="POST">
	<div class="card-body">
	<sup>title</sup>
	<input required type="text" name="title" class="form-control">
	<sup>description</sup>
	<input type="text" name="description" class="form-control">
	</div>
    <div class="card-body row">
	    <div class="col-sm">
	        <sup class="float-start">start</sup>
	        <input
	            class="form-control my-2"
	            type="date"
	            name="st_date"
	        />
	    </div>
	    <div class="col-sm">
	        <sup class="float-start">end</sup>
	        <input
	            class="form-control my-2"
	            type="date"
	            name="en_date"
	        />
	    </div>
	</div>
	<div class="card-footer">
	<input type="submit" value="submit" class="btn btn-sm btn-outline-primary mt-2">
	</div>
</form>

There is a model..

#[derive(Debug, Clone, Deserialize, Serialize, Queryable, Selectable, Insertable)]
#[diesel(table_name = crate::schema::provision_d)]
pub struct NewPrD {
    pub user_id:     i32,
    pub title:       String,
    pub description: Option<String>,
    pub st_date:     Option<NaiveDate>,
    pub en_date:     Option<NaiveDate>,
    pub created_at:  DateTime<Utc>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct FormPrD {
    pub title:       String,
    pub description: Option<String>,
    pub st_date:     Option<NaiveDate>,
    pub en_date:     Option<NaiveDate>,
}

skip (pub description: Option) - works.
skipping st_, en_ - does not work.
I solved this problem like this..

pub async fn post_creat_days(
    State(pool): State<Pool>,
    TypedHeader(cookie): TypedHeader<Cookie>,
    Form(form): Form<FormPrD>,
) -> impl IntoResponse {

    let token = auth::views::request_token(
        TypedHeader(cookie)
    ).await;

    let s: Option<String> = Some(form.st_date.clone().expect("REASON"));
    let s_value = s.as_deref().unwrap_or("default string");
    let e: Option<String> = Some(form.en_date.clone().expect("REASON"));
    let e_value = e.as_deref().unwrap_or("default string");

    let start = NaiveDate::parse_from_str(&s_value, "%Y-%m-%d");
    let end = NaiveDate::parse_from_str(&e_value, "%Y-%m-%d");

    let mut conn = pool.get().await.unwrap();
    use schema::provision_d::dsl::*;

    if s_value == "" && e_value == "" {
        let no_prv = NoneNewPrD {user_id: token.clone().unwrap().claims.id, title: form.title.clone(), description: form.description.clone(), created_at: Utc::now()};
        let _ = diesel::insert_into(provision_d)
            .values(no_prv)
            .returning(NoneNewPrD::as_returning())
            .get_result(&mut conn)
            .await;
    } else if s_value != "" && e_value == "" {
        let st_prv = StNewPrD {user_id: token.clone().unwrap().claims.id, title: form.title.clone(), description: form.description.clone(), st_date: Some(start.expect("REASON")), created_at: Utc::now()};
        let _ = diesel::insert_into(provision_d)
            .values(st_prv)
            .returning(StNewPrD::as_returning())
            .get_result(&mut conn)
            .await;
    } else if s_value == "" && e_value != "" {
        let en_prv = EnNewPrD {user_id: token.clone().unwrap().claims.id, title: form.title.clone(), description: form.description.clone(), en_date: Some(end.expect("REASON")), created_at: Utc::now()};
        let _ = diesel::insert_into(provision_d)
            .values(en_prv)
            .returning(EnNewPrD::as_returning())
            .get_result(&mut conn)
            .await;
    } else {
        let prv = NewPrD {user_id: token.clone().unwrap().claims.id, title: form.title.clone(), description: form.description.clone(), st_date: Some(start.expect("REASON")), en_date: Some(end.expect("REASON")), created_at: Utc::now()};
        let _ = diesel::insert_into(provision_d)
            .values(prv)
            .returning(NewPrD::as_returning())
            .get_result(&mut conn)
            .await;
    }
    return Redirect::to("/").into_response()
}

replaced the model

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct FormPrD {
    pub title:             String,
    pub description: Option<String>,
    pub st_date:      Option<String>,
    pub en_date:     Option<String>,
}

Is there any other way to skip fields..?

It's hard for me to understand what your exact problem is. The html file seems completely irrelevant to me. I interpret your title as: you are trying to parse some form typed as FormPrD and that fails somehow. Is this a correct assessment?


There is the #[serde(skip)] attribute. But without understanding your problem, I'm not sure whether this is what you are looking for.

What do you mean by skipping? Can you explain in simpler words what are you trying to achieve?

There is only one ("required") field in the form - "title".. Output; method="POST" -> type="submit" should save only the actual number of fields.. The missing field name="description" is saved as (nil, none).. Missing fields (name="st_date", name="en_date") err: Failed to deserialize form body.. A null Option causes a failure - serde::{Deserialize, Serialize}..

Some(form.st_date.clone().expect("REASON"))

This is a bad idea. You'll panic if form.st_date is None, and then wrap it in an Option again?

Only to then Immediately unwrap the option??:
s.as_deref().unwrap_or("default string")

I think you've gotten confused by some previous compiler errors and found something that compiles, but it's going to be worth taking a few steps back and thinking things through some more.

Running clippy might help:

cargo clippy

But for that specific situation it makes more sense to do something like (apologies if this doesn't compile!):

form.st_date
    .as_ref()
    .unwrap_or("default string");

But a related question, is st_date a NaiveDate or a String? I expect the HTML form gives you a String and you want to parse it into a NaiveDate.

Which then raises the issues of timezones, you should probably include timezone information in the form so you can convert to UTC before storing/using the date. But that's a whole other issue.

I rewrote it

    let s_value = form.st_date.as_deref().unwrap_or("default string");
    let e_value = form.en_date.as_deref().unwrap_or("default string");

What does the urlencoded data look like when you receive it on the server? Fields that are None should be omitted from the form data you pass to your endpoint. I.e. something like st_date=null won't work with the default deserializer if you use serde_urlencoded (which I assume you do as I believe you are using axum).

1 Like

In the documents..

    /// Hint that the `Deserialize` type is expecting an optional value.
    ///
    /// This allows deserializers that encode an optional value as a nullable
    /// value to convert the null value into `None` and a regular value into
    /// `Some(value)`.
    fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>;

the methodology of application is unclear to me..

    // An absent optional is represented as the JSON `null`.
    fn serialize_none(self) -> Result<()> {
        self.serialize_unit()
    }

You are trying to deserialize your type FormPrD from x-www-form-urlencoded data. This data format—unlike JSON, for example—does not have a notion of null. There is no such thing in x-www-form-urlencoded world. If you pass st_date=null as part of your encoded data, it will be interpreted as Some("null"). Because the string "null" is not a valid date, serde_urlencoded will throw an error. I've made you a playground that shows how to parse FormPrD from x-www-form-urlencoded data. If one of your fields is supposed to be None, don't add it into the form data you pass to your server (see how there is no en_date field in the encoded string in the playground).

4 Likes

Similar in python, there is an analog in rust

qsl = dict(urllib.parse.parse_qsl(request.body))
qs = dict(urllib.parse.parse_qs(request.body))

It sounds like this solved your problem. If so, please don't forget to mark @jofas 's reply as the solution using the checkbox at the bottom of that reply.

2 Likes

The problem has not been solved, the size of this problem has been partially reduced..
Let me express my sincere gratitude to all the participants of the discussion for their detailed answers.

Decision.. I brought the matter to an end..

pub async fn post_creat_days(
    State(pool): State<Pool>,
    TypedHeader(cookie): TypedHeader<Cookie>,
    Form(form): Form<FormPrD>,
) -> impl IntoResponse {

    let token = auth::views::request_token(TypedHeader(cookie)).await;

    let s_value = form.st_date.as_deref().unwrap_or("default string");
    let e_value = form.en_date.as_deref().unwrap_or("default string");

    let start: Option<NaiveDate>;
    let end: Option<NaiveDate>;

    if !s_value.is_empty() {
        start = Some(NaiveDate::parse_from_str(s_value, "%Y-%m-%d").expect("REASON"));
    } else {
        start = None
    }
    if !e_value.is_empty() {
        end = Some(NaiveDate::parse_from_str(e_value, "%Y-%m-%d").expect("REASON"));
    } else {
        end = None
    }

    let mut conn = pool.get().await.unwrap();
    use schema::provision_d::dsl::*;

    let prv = NewPrD {
        user_id: token.clone().unwrap().claims.id,
        title: form.title.clone(),
        description: form.description.clone(),
        st_date: start,
        en_date: end,
        created_at: Utc::now(),
    };
    let _ = diesel::insert_into(provision_d)
        .values(prv)
        .returning(NewPrD::as_returning())
        .get_result(&mut conn)
        .await
        .unwrap();
    Redirect::to("/").into_response()
}

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.