Hah, encoding type-level strings by creating a type for each possible character and using tuples of them to form identifiers – yikes! While I agree it could be made to work, that approach would definitely cut against the goals around sticking to “plain Rust”.
Here’s another idea, one that’s a midpoint between Actix and const generics. We could introduce a FromUrlSegment trait:
trait FromUrlSegment {
const MATCH_NAME: &'static str;
type Error;
fn parse(segment: &str) -> Result<Self, Self::Error>;
}
and have Path<T> require that T: FromUrlSegment, using the MATCH_NAME to determine which match to extract.
Then you could define:
struct UserId(u64);
impl FromUrlSegment for UserId {
const MATCH_NAME: &'static str = "user_id";
type Error = ();
fn parse(segment: &str) -> Result<UserId, ()> { /* ... */ }
}
and for some route "/users/{user_id}" you could have an endpoint:
async fn get_user(id: Path<UserId>) -> Json<User>;
Interestingly, this approach promotes best practices anyway, in the sense that it’s good practice to avoid working directly with e.g. raw u64 values precisely because of the potential for confusion; wrapping with UserId makes the intent more clear.
It doesn’t seem too far-fetched to have a custom derive for FromUrlSegment too…