-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Stricter optional extractors #1883
Comments
You can already use |
Yes that is the alternative, but I think it would be useful to push that information into the type system since we have the capability to do so with axum's excellent handlers. Saves having to dive into the implementation of the extractor itself to understand how it works, and, of course, repeat the same boilerplate checks across a number of handlers. In the case that the extractor can't have missing values (is that possible in practice?) or can't fail parsing, then adding Option or Result to the type signature can simply always hit the Ok or Some case. Also it would be cool if axum exported a result type similar to: type Result<T: FromRequestParts> = Result<T, T::Rejection>; But that is a separate idea :) |
I like the spirit of the idea but I'm not sure it's feasible. Supporting something like Sorta feels to me like |
No boilerplate needed. You can customize rejections like shown in this example |
Thank you for directing me towards WithRejection, I believe that will be enough to cover my usecase. I quite like the additive nature of option and result in my proposal, however I realise we are quite far down the line and changing these two traits is probably not reasonable. |
Consider pub enum JsonRejection {
JsonDataError(JsonDataError),
JsonSyntaxError(JsonSyntaxError),
MissingJsonContentType(MissingJsonContentType),
BytesRejection(BytesRejection),
} when should that return
I worry that we'd hit similar questions with other extractors as well. By just using an enum we avoid all that.
Sounds good! I think I'll close this for now but feel free to ask follow up questions. |
So I had some more time today, and I don't think that any of the three mechanisms for customizing rejections will work for me on its own so I am back to a match statement. Essentially what I am looking for is a way to opt out of certain rejections, rather than customizing the rejection object. I want to be able to take a async fn handler(result: Result<Jwt, JwtRejection>) -> String {
match result {
Ok(jwt) => Ok(Some(jwt)),
Err(JwtRejection::Missing) => Ok(None),
rest => rest
}
} Json I agree is more complicated (though an error variant for 'no data at all' would be nice), an opt-in trait to express optional pieces of data (such as query strings, cookies) would be useful. trait FromRequestPartsOptional<S>: FromRequestParts<S> {
fn option_reject(result: Result<Self, Self::Rejection>) -> Result<Option<Self>, Self::Rejection>;
}
impl<T, S> FromRequestPartsOptional<S> for JwtClaims<T>
where
T: DeserializeOwned + Send + Sync + Clone + 'static,
S: Send + Sync,
{
fn option_reject(result: Result<Self, Self::Rejection>) -> Result<Option<Self>, Self::Rejection> {
match result {
Ok(claims) => Ok(Some(claims)),
Err(AuthError::MissingToken) => Ok(None),
Err(e) => Err(e),
}
}
} For cases where it is ambiguous, then the trait is not implemented, and |
For something like what you're describing I think a wrapper is the best approach. It gives you full control. |
I have opted to write a crate for this (tentatively called 'axum-option') which solves the N + M problem (N implementors, M extractors) and provides an alternative extractor to #[axum::async_trait]
#[cfg_attr(
nightly_error_messages,
rustc_on_unimplemented(
note = "Function argument is not a valid optional extractor. \nSee `https://docs.rs/axum-option/latest/axum-option` for details",
)
)]
pub trait FromRequestPartsOptional<S>: FromRequestParts<S> {
async fn option_reject(
result: Result<Self, Self::Rejection>,
) -> Result<Option<Self>, Self::Rejection>;
}
#[axum::async_trait]
#[cfg_attr(
nightly_error_messages,
rustc_on_unimplemented(
note = "Function argument is not a valid optional extractor. \nSee `https://docs.rs/axum-option/latest/axum-option` for details",
)
)]
pub trait FromRequestOptional<S, B>: FromRequest<S, B> {
async fn option_reject(
result: Result<Self, Self::Rejection>,
) -> Result<Option<Self>, Self::Rejection>;
} It will need some PRs to properly expose missing data in axum before it is ready for publishing. I am currently using it with a fork. Would you be open to a PR that improves the detail in some extractors (such as a totally missing query string, for example)? I will note that this would also support #1884 under the same machinery, which is why I was interested in having this. Under axum-option, the Path could just implement the trait, and |
Hm, I'm not convinced |
Rather than have the end user remember a bunch of additional types (one for each extractor they use), and have the implementer of those extractors write / maintain a newtype that contains the logic or worse have each end user have to write their own version, everyone in the ecosystem can use just use / implement the Less code to maintain, fewer types to remember, and better ergonomics for both library authors and consumers. If you want to create a custom type, you can of course as mentioned just re-expose a type alias. |
I think I kind of get what you mean, but I don't really see a need for all of these extractors. I can kind of see use cases for a |
Sure, as long as we don't have to rewrite the two extractor traits and just make the rejection enums more informative. This generally seems like a better approach to me than writing new |
Actually, I think this is a bad example. If you intend to change |
I've come around to this. In a significant number of cases, using I opened issue #2298 with a more detailed explanation of where I see the problem(s) and PR #2475 for an (as of yet incomplete) implementation. |
Feature Request
Right now, an optional extractor in axum will maps to the following:
Valid X -> Some(X)
Invalid X -> None
Missing -> None
A result type maps to the following:
Valid X -> Ok(X)
Invalid X -> Err(X::Error)
Missing -> Implementation Dependent, but often some 'missing' error variant
Whereas, I would like the complement / dual of this, which is
Valid X -> Some(X)
Invalid X -> Rejected by extractor
Missing -> None
Motivation
It seems to be a common operation (ex: when handling cookies or login details) to want to get either valid data, or no data, but to reject invalid data, and it seems that axum doesn't really have the nuance to describe that relationship.
Proposal
This is a significant breaking change, but I think it is important to have a distinction at the extractor level between completely missing data, invalid data, and valid data. I think that in the case of the Option type, by default, the extractor's validation should be respected rather than returning
None
. Additionally, we could support Option<Result<E, E::Error>> as a middleground for those who need to be able to distinguish between valid, invalid, and missing data in their handlers.Alternatives
I could wrap all libraries that are programmed to axum's APIs to give limited information, and try to intercept to infer that data myself on a case-by-case basis. I would rather not do that.
The text was updated successfully, but these errors were encountered: