admin管理员组

文章数量:1289362

When handling JSON in Actix Web using serde, the default deserialization errors (missing field ...) do not provide a list of all missing fields. Instead, serde stops at the first missing field and returns an error like:

missing field `firstname` at line 4 column 1

I want to modify this behavior to return all missing fields in a structured JSON format like this:

{
    "status": "error",
    "errors": {
        "validate": {
            "missing_value": ["lastname", "password"]
        }
    }
}

I found that serde provides an error macro:

fn missing_field(field: &'static str) -> Self {
    Error::custom(format_args!("missing field `{}`", field))
}

However, I couldn't figure out how to override it so that it collects all missing fields into a Vec<String> instead of stopping at the first one. Ideally, I want to be able to process the collected errors and return them in different formats (JSON, XML, etc.).

Here is my current Actix Web handler:

use actix_web::{web, HttpResponse, Responder};
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize)]
pub struct RegisterRequest {
    login: String,
    password: String,
    firstname: String,
    lastname: String,
}

#[derive(Debug, Serialize)]
struct RegisterResponse {
    message: String,
}

pub async fn register(payload: web::Json<RegisterRequest>) -> impl Responder {
    let response = RegisterResponse {
        message: format!("User {} registered successfully", payload.login),
    };
    HttpResponse::Ok().json(response)
}

When handling JSON in Actix Web using serde, the default deserialization errors (missing field ...) do not provide a list of all missing fields. Instead, serde stops at the first missing field and returns an error like:

missing field `firstname` at line 4 column 1

I want to modify this behavior to return all missing fields in a structured JSON format like this:

{
    "status": "error",
    "errors": {
        "validate": {
            "missing_value": ["lastname", "password"]
        }
    }
}

I found that serde provides an error macro:

fn missing_field(field: &'static str) -> Self {
    Error::custom(format_args!("missing field `{}`", field))
}

However, I couldn't figure out how to override it so that it collects all missing fields into a Vec<String> instead of stopping at the first one. Ideally, I want to be able to process the collected errors and return them in different formats (JSON, XML, etc.).

Here is my current Actix Web handler:

use actix_web::{web, HttpResponse, Responder};
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize)]
pub struct RegisterRequest {
    login: String,
    password: String,
    firstname: String,
    lastname: String,
}

#[derive(Debug, Serialize)]
struct RegisterResponse {
    message: String,
}

pub async fn register(payload: web::Json<RegisterRequest>) -> impl Responder {
    let response = RegisterResponse {
        message: format!("User {} registered successfully", payload.login),
    };
    HttpResponse::Ok().json(response)
}
Share Improve this question asked Feb 20 at 13:49 ShvargonShvargon 113 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

You can achieve this by defining a custom error type which will collect all the missing attributes. Sample implementation below.

use actix_web::{web, HttpResponse, Responder};
use serde::{Deserialize, Deserializer};
use serde::de::{self, Visitor, MapAccess};
use std::fmt;

#[derive(Debug)]
struct MissingFieldsError {
    missing_fields: Vec<String>,
}

impl MissingFieldsError {
    fn new() -> Self {
        MissingFieldsError {
            missing_fields: Vec::new(),
        }
    }

    fn add_field(&mut self, field: &str) {
        self.missing_fields.push(field.to_string());
    }
}

impl fmt::Display for MissingFieldsError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "missing fields: {:?}", self.missing_fields)
    }
}

impl std::error::Error for MissingFieldsError {}

#[derive(Debug)]
struct RegisterRequest {
    login: String,
    password: String,
    firstname: String,
    lastname: String,
}

impl<'de> Deserialize<'de> for RegisterRequest {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        enum Field { Login, Password, Firstname, Lastname }

        struct RegisterRequestVisitor {
            missing_fields: MissingFieldsError,
        }

        impl<'de> Visitor<'de> for RegisterRequestVisitor {
            type Value = RegisterRequest;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("struct RegisterRequest")
            }

            fn visit_map<V>(mut self, mut map: V) -> Result<RegisterRequest, V::Error>
            where
                V: MapAccess<'de>,
            {
                let mut login = None;
                let mut password = None;
                let mut firstname = None;
                let mut lastname = None;

                while let Some(key) = map.next_key()? {
                    match key {
                        Field::Login => {
                            if login.is_some() {
                                return Err(de::Error::duplicate_field("login"));
                            }
                            login = Some(map.next_value()?);
                        }
                        Field::Password => {
                            if password.is_some() {
                                return Err(de::Error::duplicate_field("password"));
                            }
                            password = Some(map.next_value()?);
                        }
                        Field::Firstname => {
                            if firstname.is_some() {
                                return Err(de::Error::duplicate_field("firstname"));
                            }
                            firstname = Some(map.next_value()?);
                        }
                        Field::Lastname => {
                            if lastname.is_some() {
                                return Err(de::Error::duplicate_field("lastname"));
                            }
                            lastname = Some(map.next_value()?);
                        }
                    }
                }

                if login.is_none() {
                    self.missing_fields.add_field("login");
                }
                if password.is_none() {
                    self.missing_fields.add_field("password");
                }
                if firstname.is_none() {
                    self.missing_fields.add_field("firstname");
                }
                if lastname.is_none() {
                    self.missing_fields.add_field("lastname");
                }

                if !self.missing_fields.missing_fields.is_empty() {
                    return Err(de::Error::custom(self.missing_fields));
                }

                Ok(RegisterRequest {
                    login: login.unwrap(),
                    password: password.unwrap(),
                    firstname: firstname.unwrap(),
                    lastname: lastname.unwrap(),
                })
            }
        }

        const FIELDS: &'static [&'static str] = &["login", "password", "firstname", "lastname"];
        deserializer.deserialize_struct("RegisterRequest", FIELDS, RegisterRequestVisitor { missing_fields: MissingFieldsError::new() })
    }
}

#[derive(Debug, Serialize)]
struct RegisterResponse {
    message: String,
}

pub async fn register(payload: web::Json<RegisterRequest>) -> impl Responder {
    let response = RegisterResponse {
        message: format!("User {} registered successfully", payload.login),
    };
    HttpResponse::Ok().json(response)
}

本文标签: validationHow to get a list of all missing fields when deserializing a JSON with serdeStack Overflow