Skip to content
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

Add a separate trait for optional extractors #2475

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

jplatte
Copy link
Member

@jplatte jplatte commented Dec 30, 2023

Motivation

Closes #2298.

Solution

  • Add OptionalFromRequest, OptionalFromRequestParts
    • Q: Should the trait items have different names from the ones in the non-optional traits?
  • Implement for relevant types
  • Deprecate OptionalFoo extractors
  • Documentation
  • Changelog

@jplatte jplatte force-pushed the jplatte/optional-from-request branch 3 times, most recently from 91fabd2 to b8f499e Compare December 30, 2023 22:38
@jplatte jplatte added this to the 0.8 milestone Dec 30, 2023
@jplatte jplatte added C-cleanup Category: PRs that clean code up or issues documenting cleanup. breaking change A PR that makes a breaking change. A-axum-core labels Dec 30, 2023
@jplatte
Copy link
Member Author

jplatte commented Jan 18, 2024

@davidpdrsn Have you had time to take a look? The main changes are done, and I don't want to do the remaining docs / changelog writing and cleanup if it's not yet clear whether this will be merged.

@jplatte
Copy link
Member Author

jplatte commented Mar 14, 2024

@mladedav @yanns both of you have been active with contributions recently. Would you be up for reviewing this? For context, see the linked issue.

Copy link
Collaborator

@mladedav mladedav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing that bugs me is that the optional variants use the same rejections so for example if someone uses the optional typed header there still is valid representation in the rejection. for the missing case.

That said the rejections are non exhaustive so they cannot handle explicitly everything so they may omit this one as well. It might just be confusing.

I will read it more thoroughly tomorrow or over the weekend, but it makes sense as a whole.

I want to think on other ways to make the option work but on a first sight this may be the best solution.

axum-extra/src/extract/query.rs Outdated Show resolved Hide resolved
Copy link
Collaborator

@mladedav mladedav left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks good to me.

The drawback I see here is having two distinct traits so it might be harder for users to discover how to properly use Options. That said in hindsight, it might be better to force users to use Result instead of Option if they really mean to just discard any errors.

I am still unsure about whether it is better to use the same rejections for optional and normal extractors if the missing case is already handled in the normal rejection. I think we could use some kind of enum (probably very similar to Option) for extractors that have optional variants. But each extractor would have to define IntoResponse for its missing case.

I also wonder if we couldn't get away with just one FromRequestParts trait where the extracting method would return an enum with three cases like MyResult<T, T::Missing, T::Rejection>. Then we could maybe create the extractors with correct semantics without the need for multiple traits.

But if that's not possible or having the two traits is a better tradeoff than whatever issues that potential solution has, the implementation as is looks ok. I think we should just go over the implementations of FromRequest and FromRequstParts and add a few implementations for the optional conterparts.

#[derive(Debug, Clone, Copy, Default)]
pub struct OptionalQuery<T>(pub Option<T>);

#[allow(deprecated)]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OptionalPath became this:

        parts
            .extract::<Option<Path<T>>>()
            .await
            .map(|opt| Self(opt.map(|Path(x)| x)))

I think that a similar thing can be done here.

axum-macros/tests/typed_path/pass/option_result.rs Outdated Show resolved Hide resolved
Comment on lines -11 to -12
async fn option_handler(_: Option<UsersShow>) {}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should also be derived and usable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? When would it be None?

where
S: Send + Sync,
{
type Rejection = MatchedPathRejection;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be Infallible as missing is the only failure case.

And just to be sure, it seems to me like the only way MatchedPath would be missing is if it is accessed inside a fallback handler? If that's so, I think it would be better not to add this impl and have users just use Result if they want to guard against using it there.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could also be missing if you don't use Router at all, for example calling .into_service() on a handler and using the resulting handler as a standalone service.

Why would it be better to require users to use Result here in your opinion? I think both the Option and Result route are very rarely useful, but if you want to distinguish, or just be defensive against logic bugs affecting other parts of the system, the Option route seems more convenient.

examples/todos/src/main.rs Outdated Show resolved Hide resolved
@jplatte
Copy link
Member Author

jplatte commented Nov 17, 2024

Damn, I didn't realize I failed to respond to most of the feedback here since asking for it. I definitely think having more specific rejection types would be better. I think it's something I skipped initially because it was just easier that way for the first prototype.

I'll try to make some time to back to this soon-ish.

@jplatte jplatte force-pushed the jplatte/optional-from-request branch from 226a27e to d7446e3 Compare November 26, 2024 17:29
@jplatte jplatte removed this from the 0.8 milestone Nov 26, 2024
@jplatte jplatte force-pushed the jplatte/optional-from-request branch from d7446e3 to 6460d88 Compare November 26, 2024 20:35
@jplatte
Copy link
Member Author

jplatte commented Nov 26, 2024

Suggestions for the docs would be very much welcome 😅

@jplatte jplatte force-pushed the jplatte/optional-from-request branch from 6460d88 to 1f2c649 Compare November 30, 2024 16:48
@jplatte jplatte marked this pull request as ready for review November 30, 2024 16:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-axum-core breaking change A PR that makes a breaking change. C-cleanup Category: PRs that clean code up or issues documenting cleanup.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Remove or change impl FromRequest* for Option<impl FromRequest*>?
2 participants