actix / actix-web

Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust.
https://actix.rs
Apache License 2.0
21.6k stars 1.67k forks source link

Path extractor not working for enums #318

Open sean-bennett112 opened 6 years ago

sean-bennett112 commented 6 years ago

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.

fafhrd91 commented 6 years ago

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

DoumanAsh commented 6 years ago

@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

fafhrd91 commented 6 years ago

Oh, right

rokob commented 5 years ago

Any update here?

fafhrd91 commented 5 years ago

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

DokkanWiki commented 2 years ago

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());
    // ...   
}
aliyss commented 1 year ago

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);
}