Open m-doughty opened 4 years ago
Maybe try something similar to:
step1
.and(step2)
.and(step3a.or(step3b).unify())
.and(step4)
.and(step5a.or(step5b).unify())
.and(step6)
.and_then(step7)
I don't know whether that actually works, but at least the types should match.
Would be nice to turn this thread into a general collection of patterns. Here's one that has me stumped: 'if some header exists (say 'Authorization'), try to parse it into a Some(Token)
. if parsing fails, throw an error. if the header doesn't exist, return None
. Here's my attempt to achieve that:
let user_extractor = warp::header::<String>("authorization")
.and_then(|token: String| async {
decode_jwt(token).map_err(|e| {
warp::reject::custom(TokenError {
message: e.to_string(),
})
})
})
.or(warp::any().map(|| None))
.unify();
The problem is that or() currently swallows the error essentially representing 'try and parse auth header: if it fails or the header doesn't exist, return None, else Some(Token)
Would be nice to turn this thread into a general collection of patterns. Here's one that has me stumped: 'if some header exists (say 'Authorization'), try to parse it into a
Some(Token)
. if parsing fails, throw an error. if the header doesn't exist, returnNone
. Here's my attempt to achieve that:let user_extractor = warp::header::<String>("authorization") .and_then(|token: String| async { decode_jwt(token).map_err(|e| { warp::reject::custom(TokenError { message: e.to_string(), }) }) }) .or(warp::any().map(|| None)) .unify();
The problem is that or() currently swallows the error essentially representing 'try and parse auth header: if it fails or the header doesn't exist, return None, else Some(Token)
This is very similar to an issue we're having -- we want to check if the user requested XML with Accept: application/xml
or Accept: text/xml
, if not, we want to default to JSON, but if so, we want the XML path to error as usual and not continue to try the JSON path.
@arlyon
The problem is that or() currently swallows the error
or()
is designed to swallow the error. If you need access to the error, use or_else()
Also, the way you model your domain is not fitting in the type signature.
You want to model your data with a Option<Result<String>>
, while header()
gives you Filter<Extract = String, Error = Rejection>
.
A much better way is to model your data as Filter<Extract = Token, Error = Rejection>
directly, and write:
impl FromStr for Token {
type Err = TokenError;
fn from_str(token: &str) -> Result<Self, Self::Err> {
decode_jwt(token)
}
}
let user_extractor = warp::header::<Token>("authorization").recover(|r: Rejection| async {
let rep = if let Some(TokenError { message }) = r.find() {
reply::html(format!("{}", message))
} else if let Some(MissingHeader) = r.find() {
reply::html(format!("Missing authorization header"))
} else {
reply::html("Unknown error".into())
};
Ok(rep)
});
@louy2 Thanks for your insight. The final version looks like this:
let user_extractor = warp::header("authorization")
.map(|t: Token<Claims>| Some(t.claims().user.clone()))
.or_else(|e: Rejection| async {
e.find::<MissingHeader>().map(|_| (None,)).ok_or_else(|| e)
});
One of the things I'm finding confusing is how to do branching filters. Let's say I have:
I would expect this to:
It doesn't seem to do that, instead it seems to try to run step5b if anything up to and including step5a produces a rejection, and I can't work out how to achieve the desired behavior from the documentation.