-
-
Notifications
You must be signed in to change notification settings - Fork 713
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implemented Filter::then
#878
Conversation
hi @gtsiam I was called from the issue you linked, I'm really interested in understanding your PR and how in practice could be used. Please fix my understanding. By reading again Sean's comment my hunch is that your PR is trying to draw a clearer line between a Can you please show here an example of how to use your Regardless, thank you from me for working on this. |
Pretty much, yes.
async fn login(state: State, req: LoginRequest) -> Result<LoginResponse, LoginError> { /* ... */ }
fn json_response<T: Serialize, E: Serialize>(res: Result<T, E>) -> reply::Json { /* ... */ }
// In my filter function:
path!("login")
.and(post())
.and(with_state)
.and(body::json())
.map_async(login)
.map(json_response); Notice that Warp's api today looks like this:
The idea is to fill in the second gap with This would make the transition from sync to async as simple as changing your function to return Then there's the #[derive(Debug, From, Serialize)]
enum LoginError {
// Other variants here. InternalError is a catch-all for, well, internal errors.
Internal(#[from(forward)] api::InternalError),
}
// No need for impl Reject for LoginError. Warp never deconstructs the returned Result.
async fn login(state: State, req: LoginRequest) -> Result<LoginResponse, LoginError> {
let conn = state.pool.get()?; // No need to map_err(|err| reject::custom(err)) !
// [...] Stuff that doesn't really matter for this example
} You are already able to do just this with There's also infallible async endpoints where you have to do stuff like this because of type inference: .and_then(|data| async move {
let response = data.get().await;
Ok::<_, Rejection>(warp::reply::json(&response))
}); But with .map_async(|data| async move {
let response = data.get().await;
warp::reply::json(&response)
}); Also a fundamental issue with using I'd wager there's also some overhead to using recover to check for a lot of errors, though I could be wrong. (Also while writing, I just realized this probably closes #594 too - I'll edit the description) |
Fantastic explaination, thank you so much. You clarified what I hoped for (i.e. remove all the custom Rejections which are really confusing).
Also, I suspect, this could potentially solve another annoying side-effect: if a request goes through the chain of filters and reaches the end without being "picked up" and handled I could avoid the EDIT:
well, basically what you just said :-) |
This is a great addition. I have just followed the same trap - using |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't say I understand all the details of implementation and pinning of the future, but it looks good to me, and seems to be similar to and different from the and_then
implementation in the right ways. I like the doc changes to Filter::and_then
.
The changes is src/filter/and_then.rs
is not really relevant to implementing map_async, but nice cleanup.
Good to hear :) I ended up with this after copy-pasting the As for |
I was hoping the convinient "Update" branch button would just rebase it... guess not. Let me do it manually then. |
Actually it just occurred to me github could probably merge that anyway and rebasing messes with reviews... so uh.... oops? |
My review still stands, but I'm no maintainer ... |
@seanmonstar @jxs gently bumping for a review of this PR thanks! |
Hello all, |
@option-greek this is a branch from a fork waiting for a review from the maintainers. As such, it is not published anywhere. If you want to test it, you need to checkout this fork and use it in your project with something like:
(I didn't test the above import) |
Got it. I'll try it out and post here the results. Btw, didn't realise
import directly from github and that too from a commit was possible!
…On Mon, 6 Sep 2021, 21:00 apiraino, ***@***.***> wrote:
@option-greek <https://github.com/option-greek> this is a branch from a
fork waiting for a review from the maintainers. As such, it is not
published anywhere. If you want to test it, you need to checkout this fork
and use it in your project with something like:
warp = { git="https://github.com/gtsiam/warp.git", rev="b16c88" }
(I didn't test the above import)
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#878 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAK5GI2M2PF6PLKDPLZAKN3UATM7ZANCNFSM4723CFJA>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
|
Any ETA for this PR? |
🎉 @rubycut ETA: -3m |
I have come across this PR after researching my error handling problem in warp. After trying to get Previously I've fallen in the same trap of converting my DB errors into let login = warp::post()
.and(warp::path("login"))
.and(warp::body::json())
.and(with_db.clone())
.and_then(user::login); pub async fn login(user: UserCredentials, db: PgPool) -> Result<impl warp::Reply, warp::Rejection> {
if !user_exists(&user.name, &db).await? {
return Ok(StatusCode::UNAUTHORIZED.into_response());
}
...
} The Is there any way I could make the use of Could someone point out to me what I'm missing? It's getting quite frustrating spending the majority of my time with error handling and not business logic :) |
Off the top of my head, I'd say to try something like this: let login = warp::post()
.and(warp::path("login"))
.and(warp::body::json())
.and(with_db.clone())
.then(user::login) // Note that then() doesn't care about Reply / Rejection
.map(util::result_reply); // What's important is that whatever comes out here, implements Reply pub async fn login(user: UserCredentials, db: PgPool) -> Result<impl warp::Reply, ApiError> {
if !user_exists(&user.name, &db).await? {
return Ok(StatusCode::UNAUTHORIZED.into_response());
}
...
} So, assuming you implement pub async fn result_reply<T: Reply, E: Reply>(res: Result<T, E>) -> impl Reply {
match res {
Ok(r) => r.into_response(),
Err(e) => e.into_response(),
}
} This last part ( |
Now it makes sense, thanks a lot :). This should make my code quite a bit cleaner. Looking forward to have a |
This is great to have warp return an error from a handler and be done with it, instead of continuing to try a bunch of other filters. However, how should one handle various "extractor" filters that are fallible? For example, if you have an In this case, "fatal rejections" would still be needed to avoid unnecessary processing, no? |
@gtsiam bringing the above to your attention, now that there’s renewed “activity” :) any ideas? |
Let me experiment a bit and I'll draft a few proposals in the coming days. In the meantime, please spin up a new issue for this. |
Added: #1053 |
Tipping point for me to jump into
Future
type hell to implement this was this comment - but I'd say it's worth it. I've used it in my personal project for a while, and the code I've been able to write is much cleaner, I think.Changes:
Filter::then()
and complementary typeThen
. Exactly likemap
, but async. The name is meant to reflect future'sthen()
function.Filter::and_then()
documentation now points toFilter::then()
as an alternative, since my understanding is that:Fix #712
Fix #594
Providing Reply implementations for Result would also be very useful, so this is loosely related to #458
Post-edit note: This function was renamed from
map_async
tothen
to more closely matchfuture
's api. However it is worth noting that just asFilter::map
isn't exactly likefuture
'smap
on account of rejections,Filter::then
isn't exactly likefuture
'sthen
. HoweverFilter::then
is the same, conceptually, asfuture
'sthen
, so this seems like the best choice.