Warp: Path filter api inflexibility

Created on 19 Jan 2020  Â·  7Comments  Â·  Source: seanmonstar/warp

I try to reimplement some existing rest API using warp. This API has routes like /something/:some_uuid.json. It seems not possible to express that using the existing api in warp::filter::path, because both the path! and the function based API seems to assume that segments (static parts, parameters, …) are seperated by /.
I currently workaround this by implementing a custom filter based on warp::filter::path::Tail, but this is involves quite ugly string parsing there (and does not validate the extension)

fn id_with_extension() -> impl Filter<Extract = (Uuid,), Error = warp::reject::Rejection> + Clone {
    warp::path::tail().and_then(|tail: warp::path::Tail| {
        let uuid = tail
            .as_str()
            .split(".")
            .next()
            .and_then(|uuid| Uuid::from_str(uuid).ok());
        async move {
            if let Some(uuid) = uuid {
                Ok(uuid)
            } else {
                Err(warp::reject::not_found())
            }
        }
    })
}

It would be much nicer if it where possible to just write path!(Uuid ".json") instead.

Relevant version information:

  • Warp version: 0.2

All 7 comments

Hi, have you taken a look at https://github.com/seanmonstar/warp/issues/19#issuecomment-410392693 ?
since Uuid impl's FromStr couldn't you do something like:

route = warp::path!("something" / Uuid ).map(|uuid| {
        ...
    });

It's not about parsing a Uuid is not possible, that works well.
This issue is about that the path filter api is not able to express filters for urls like something/4757d312-04c8-4910-b3ea-166c13622d66.json or even something/4757d312-04c8-4910-b3ea-166c13622d66.json/some_other_thing.
I've tried your example and it does not match any of the provided urls.
Let me know if there are more information required to resolve this.

If the types used in path!() need only implement FromStr, I think maybe @jxs is suggesting something like the following. You could add a wrapping type around Uuid which accounts for the .json portion of the string representation.

use std::str::FromStr;
use uuid::Uuid;

#[derive(Debug)]
struct UuidDotJson(Uuid);

impl FromStr for UuidDotJson {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let (tail, head) = {
            let mut splits = s.rsplitn(2, '.');
            (splits.next(), splits.next())
        };

        if tail == Some("json") {
            Uuid::parse_str(head.unwrap())
                .map(UuidDotJson)
                .map_err(|_| ())
        } else {
            Err(())
        }
    }
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6a18bfcbbad12f9631edb347d59d3648

The example by @onelson is exactly how I would have responded as well.

It's true that it could be nice to allow matching only parts of a segment. I think the tricky part is in defining what the API looks like. We can open an issue to add such an API if someone has a though-out suggestion.

I'd just add that I don't particularly see this suggestion as a workaround for a missing warp feature. These building blocks in the language offer a bunch of flexibility for us as API consumers.

Consider, with a little more work, you could turn this into ParsableDotJson<T> by constraining T to any type that it itself implements FromStr. Deeper still, if your application supports more than json, you could make this type able to represent the parsable and the extension (whatever it might be).

I've had a lot of success with a similar pattern for getting vecs of generic values from query string values that are comma-separated. We can use this in a variety of situations for breaking up the likes of names=alice,bob into a vec of strings, or product_ids=1,2,3 into a vec of i32s.

The sky is the limit.

Thanks for the suggestions. As mentioned in the opening post I've already a working solution, so that was not my main reason why I've opened the issue. I just wanted to suggest to improve the API there to be more flexible. Being able to just write warp::path!("something" / Uuid . "json") would be great.

After looking a bit a the related code, I think one way to achieve that is to decouple path from the used path delimiter. As far as I see this could be done by providing a new wrapping type like DelemitedBy<T>{del: char, inner: T}. This type then is used to extract the next segment till the provided delimiter (That what is now done by the internal segment function). path and param could then just return their existing type wrapped into this new type using / as default delimiter. Additionally matching functions are added to construct path/param segments with explicit delimiter. Probably even something like warp::path("something").delimiter("/") is meaningful. This should make it possible to change the macro in such a way that more delimiter's are allowed, by adding some more rules to match on allowed delimiters. This leads to the question: Which delimiters should be allowed?

When attempting to use Warp for an RESTful service that conforms to Google's API design guide, I ran into a similar issue for custom methods, where the separator for the method is specified as : instead of /.

I understand that there are work-arounds if I want to implement the API in this way, but these don't feel as ergonomic as the rest of Warp (which has been really amazing!). I also wonder if it makes more sense to include a custom separator argument to warp::path(), where if it is omitted the default is /?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

richardanaya picture richardanaya  Â·  6Comments

silvioprog picture silvioprog  Â·  3Comments

kamalmarhubi picture kamalmarhubi  Â·  5Comments

droundy picture droundy  Â·  8Comments

Apromixately picture Apromixately  Â·  3Comments