-
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
Add IntoResponseParts
#797
Conversation
Small overall comment after reading through (only) the description: What I meant in #770 was a trait for just the headers and extensions, i.e. the parts that can added onto. The impls would still work the same way as the ones for tuples containing Maybe this more general approach is the better solution though. I'll look at the code. |
Also gonna add some tests. |
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.
Code looks good, but I'm still not entirely sure whether the arbitrary order of tuple elements is worth it considering the potential confusion / pitfalls around Result
or opaque impl IntoResponse
types in tuples¹. We'd also lose the static verification of only a single status code and body being set.
¹ in particular, I wouldn't be surprised if some people are currently writing handlers that call other handlers (where the fn signatures are likely -> impl IntoResponse
) and override the status code or add headers
I thought about as well. We kinda had the problem previously since one could do You're right that it wasn't possible to accidentally override the body previously. I'm gonna experiment and see if I can find a way around these issues while still being able to put headers and extensions anywhere in the tuples without having to wrap the headers in
Changing those functions to |
I think I've cracked it. Now Additionally I was able to tweak the impls such that:
Thus a status code must be the first element in the tuple, and there can only be one status code. The body must be the last element. Anything in the middle must implement How that doesn't give conflicting impls is magical to me, but it works! I think this addresses the footguns. See for a bunch of examples https://github.com/tokio-rs/axum/blob/extension-into-response/axum/src/response/mod.rs#L89 |
Still have to update the docs but I'll do that if you think the code makes sense. |
impl IntoResponse for Version { | ||
fn into_response(self) -> Response { | ||
let mut res = ().into_response(); | ||
*res.version_mut() = self; | ||
res | ||
} | ||
} |
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.
Kinda surprised no one has asked why Version
didn't implement IntoResponse
earlier 🤔
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.
Why would you ever want to override that? I feel like the http version should always be set by hyper.
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.
Not sure that you need to actually. But tonic does set it, although I think hyper would handle that as well.
res.insert_header(key, value); | ||
} | ||
} | ||
} |
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.
The rule with these impls is that if things can be appended to a response, then they should implement IntoResponseParts
and not IntoResponse
. That only applies to headers and extensions.
Extension
implements IntoResponseParts
in another file.
fn into_response_parts(self, res: &mut ResponseParts) { | ||
res.insert_extension(self.0); | ||
} | ||
} |
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'm thinking if we should even implement tower::Layer
for Extension
? Then we wouldn't need AddExtension
layer and users would only deal with Extension
. But maybe thats odd? 🤔
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.
Oohh, nice idea! I don't really see why not. I'm personally going to continue using ServiceBuilderExt::add_extension
either way, but as this would reduce axum's API surface a little bit, I'd be in favor.
Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com>
@@ -4,7 +4,7 @@ use std::{convert::TryInto, fmt}; | |||
|
|||
/// Trait for adding headers and extensions to a response. | |||
/// | |||
/// You generally shouldn't need to implement this trait manually. Its recommended instead to rely | |||
/// You generally don't need to implement this trait manually. It's recommended instead to rely |
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.
To be clear, I think this isn't the only instance where it was worded this way.
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.
That's all my nitpicks voiced.
Should we open a separate issue about whether or not to keep the |
Yeah lets do that 👍 |
Note: Design has changed quite a bit from the description here. See comments for details.
This is a proposal that changes how responses are made by introducing a new trait:
Where
ResponseParts
is:And these impls
This means you can return tuples of response parts like
(status, headers, body)
just like before, but the order no longer matters. So(body, headers, status)
also works.I've also implemented
IntoResponseParts
forExtension
and[(HeaderNameIsh, HeaderValueIsh); N]
so(Extension(1), Extension(2), [("content-type", "text/plain")], "Hello, World!")
works!Overall I'm quite happy with this approach. I think its more consistent since
Extension
works and I like that the order of things in tuples no longer matters. I also like that there is only trait to implement vs the previousIntoResponseHeaders
andIntoResponse
.The only downside I see is that users might be confused by having to implement
IntoResponseParts
but useIntoResponse
. We should help by clearly documenting the blanket impl and how that works.Things to note
IntoResponse
is now sealed, so users will have to implementIntoResponseParts
instead.http::Extensions
doesn't implementIntoResponseParts
since we cannot merge extensions.Response
andhttp::response::Parts
implementsIntoResponse
and notIntoResponseParts
, again because we cannot merge extensions.Result<T, E>
implementsIntoResponse
and notIntoResponseParts
. This makesResult<impl IntoResponse, E>
work. Means you cannot have results in tuples of parts but thats probably fine.Headers
has been removed.[(HeaderNameIsh, HeaderValueIsh); N]
works instead and doesn't require a wrapper type.Fixes #770
TODO
Future improvements
Extension
into the root because it, likeJson
, is both an extractor and response part. I'll do that in a follow up PR. Will be re-exported fromaxum::response
andaxum::extract
likeJson
. Same forTypedHeader
.