actix-web icon indicating copy to clipboard operation
actix-web copied to clipboard

Path extractor not working for enums

Open sean-bennett112 opened this issue 6 years ago • 7 comments

It looks like the Path extractor doesn't currently work with enums. To illustrate, I’m getting

 WARN 2018-06-14T23:46:13Z: actix_web::pipeline: Error occured during request handling: unsupported type: 'any'
 INFO 2018-06-14T23:46:13Z: actix_web::middleware::logger: 127.0.0.1:63742 [14/Jun/2018:16:46:13 -0700] "GET /my_resource/52635231 HTTP/1.1" 404 0 "-" "curl/7.54.0” 0.000458

whenever I try to do the following:

.resource(“/my_resource/{thing}", |r| {
    r.method(Method::GET).with(handle_mypath)
})
#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum MyThing {
    String(String),
    Int(u32),
}

pub fn handle_mypath<A>((state, path): (State<Arc<A>>, Path<MyThing>)) -> HttpResponse {
    …
}

It appears to never reach the handler, despite compiling fine. As soon as I convert it to a bare type or a struct it seems to work fine. Interestingly, Query extractors seem to work fine with this.

sean-bennett112 avatar Jun 15 '18 14:06 sean-bennett112

@DoumanAsh @Dowwie i think this should be fixed with latest serde_urlencoded?

fafhrd91 avatar Aug 23 '18 17:08 fafhrd91

@fafhrd91 No, Path extract uses own deserializer I don't see it using seder url encoded, we only can use it for query component of path. In case of path, we just cannot handle enums at all

DoumanAsh avatar Aug 23 '18 19:08 DoumanAsh

Oh, right

fafhrd91 avatar Aug 23 '18 19:08 fafhrd91

Any update here?

rokob avatar May 14 '19 20:05 rokob

No updates. I won’t have any time in near future

fafhrd91 avatar May 14 '19 21:05 fafhrd91

Work around:

pub enum ArticleUid {
    String(String),
    Int(u64),
}

impl FromStr for ArticleUid {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(match s.parse::<u64>() {
            Ok(uid) => ArticleUid::Int(uid),
            Err(_) => ArticleUid::String(s.to_string())
        })
    }
}

#[get("/site_news/{uid}")]
pub async fn get_site_article(
    req: HttpRequest
) -> HttpResponse {
    let uid: Option<ArticleUid> = req.match_info().get("uid").and_then(|v| v.parse().ok());
    // ...   
}

DokkanWiki avatar Jul 02 '22 21:07 DokkanWiki

Another workaround so one can actually keep the current structure and still use Path:

I'm new to rust so this may not be the ultimate top code or so... feel free to judge. :heart:

use serde::{Serialize};

/// Make sure you are not using Deserialize here.
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum ProjectNameOrId {
    Name(String),
    Id(u64),
}
use serde::{de::Deserializer, Deserialize};
use std::str::FromStr;

// This can be left out if impl Deserialize is adjusted...
// but honestly I had already added it, so I just stuck to it.
impl FromStr for ProjectNameOrId {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if let Ok(id) = s.parse::<u64>() {
            return Ok(ProjectNameOrId::Id(id));
        }
        Ok(ProjectNameOrId::Name(String::from(s)))
    }
}

impl<'de> Deserialize<'de> for ProjectNameOrId {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        ProjectNameOrId::from_str(&s).map_err(|_| serde::de::Error::custom("invalid ProjectNameOrId value"))
    }
}
use crate::AppState;
use actix_web::{get, web, HttpResponse, Responder};

#[get("/schema")]
async fn get_schema(
    data: web::Data<AppState>,
    path: web::Path<ProjectNameOrId>
) -> impl Responder {
    /* Your Code */
    println!("{:#?}", &path);
}

aliyss avatar Mar 21 '23 19:03 aliyss