Search code examples
rustserdeactix-web

How do I turn invalid enum variants into None when used as query parameters in actix-web


With the example provided in the documentation for actix_web::web::Query, how can I make the response_type resort to None when providing an unknown variant?

If I have the following:

use actix_web::{web, App, HttpServer};
use serde::Deserialize;

#[derive(Debug, Deserialize)]
pub enum ResponseType {
    Token,
    Code,
}

#[derive(Deserialize)]
pub struct AuthRequest {
    id: u64,
    response_type: Option<ResponseType>,
}

async fn index(web::Query(info): web::Query<AuthRequest>) -> String {
    format!(
        "Authorization request for client with id={} and type={:?}!",
        info.id, info.response_type
    )
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().route("/", web::get().to(index)))
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}

And I visit http://localhost:8080/?id=1&response_type=foo, I get this 400 response:

Query deserialize error: unknown variant foo, expected Token or Code

When I instead would like it to only accept the values of the Enum as valid values, and if no value or an invalid value is provided I want it to be set to None.


Solution

  • This can be dealt with deserialize_with.

    use actix_web::{web, App, HttpServer};
    use serde::Deserialize;
    use serde::de::{Deserializer};
    
    #[derive(Debug, Deserialize)]
    pub enum ResponseType {
        Token,
        Code,
    }
    
    fn from_response_type<'de, D>(deserializer: D) -> Result<Option<ResponseType>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let res: Option<ResponseType> = Deserialize::deserialize(deserializer).unwrap_or(None);
        Ok(res)
    }
    
    #[derive(Debug, Deserialize)]
    pub struct AuthRequest {
        id: u64,
        #[serde(deserialize_with = "from_response_type")]
        response_type: Option<ResponseType>,
    }
    
    async fn index(web::Query(info): web::Query<AuthRequest>) -> String {
        format!(
            "Authorization request for client with id={} and type={:?}!",
            info.id, info.response_type
        )
    }
    
    #[actix_web::main]
    async fn main() -> std::io::Result<()> {
        HttpServer::new(|| App::new().route("/", web::get().to(index)))
            .bind(("127.0.0.1", 8080))?
            .run()
            .await
    }
    

    Any invalid value is considered a None. The key line being

    let res: Option<ResponseType> = Deserialize::deserialize(deserializer).unwrap_or(None);