-
Notifications
You must be signed in to change notification settings - Fork 76
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
make it a compile error to accept more than one body extractor #556
Conversation
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.
looks good
dropshot/src/extractor/common.rs
Outdated
@@ -0,0 +1,198 @@ | |||
// Copyright 2022 Oxide Computer Company |
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.
2023 here and elsewhere?
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.
Done in 8ee3aa9.
pub trait ExclusiveExtractor: Send + Sync + Sized { | ||
/// Construct an instance of this type from a `RequestContext`. | ||
async fn from_request<Context: ServerContext>( | ||
rqctx: &RequestContext<Context>, |
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 assumed this would be either the actual RequestContext<Context>
or a &mut
to it; perhaps that's in a subsequent PR...
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.
Counter-intuitively, this works with just &RequestContext<Context>
. That's because what exclusive extractors need mutable access to is the hyper::Request
, which is in the RequestContext
but wrapped with Arc<Mutex<...>>
. So you can take the lock to access a &mut hyper::Request
.
After #558, the hyper::Request
won't even be in the RequestContext
any more.
So given that it's not necessary to have a &mut RequestContext
and that the caller is going to pass the same RequestContext
to the request handler function after calling this one, I prefer giving the extractor just a &RequestContext
. Hopefully that all makes sense.
--> tests/fail/bad_endpoint3.rs:17:12 | ||
| | ||
17 | param: String, | ||
| ^^^^^^ the trait `Extractor` is not implemented for `String` |
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.
this seems helpful and I'm not sure the parts that remain are as helpful
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.
Totally. Since you're on board with this general idea, the next step is to figure out how to ensure good error messages when (1) an argument is not an extractor at all, or (2) you attempt to use two exclusive extractors. I think (1) is pretty easy because I made SharedExtractor
impl ExclusiveExtractor
, so we can change the existing check to check ExclusiveExtractor
. I have not yet figured out a good way to do (2) but Rain suggested an approach I'm going to look at.
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.
Done. The basic approach is to verify that:
- the first argument is a
RequestContext
(like we did before) - the last argument is an
ExclusiveExtractor
(slight variation on what we did before) - all other arguments are
SharedExtractor
This has the side effect of verifying that you don't have two exclusive extractors because they don't impl SharedExtractor.
The naming of the tests is a little weird. I kept the existing dense integer naming but it's a little confusing that test 19 is a slight variant of test 3. (That is, the behavior tested by the old bad_endpoint3.rs is now tested by a combination of bad_endpoint3.rs and bad_endpoint19.rs.) The alternatives seemed like separating into 3a and 3b or renaming everything to be more semantic. 🤷
dropshot_endpoint/src/lib.rs
Outdated
sig.output = | ||
syn::parse2(quote!(-> dropshot::WebsocketEndpointResult))?; |
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.
huh.. had not seen this construction.
const _: fn() = || { | ||
fn need_extractor<T>() | ||
where | ||
T: ?Sized + dropshot::Extractor, |
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.
could this change to RequestExtractor
?
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.
... or maybe SharedExtractor
?
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 think it could change to ExclusiveExtractor
(shared extractors can be treated as exclusive, but not the other way around). (see my other comment -- I do intend to restore a helpful message for this case, one way or another)
dropshot_endpoint/src/lib.rs
Outdated
@@ -173,7 +173,8 @@ fn do_channel( | |||
ChannelProtocol::WEBSOCKETS => { | |||
// here we construct a wrapper function and mutate the arguments a bit |
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.
// here we construct a wrapper function and mutate the arguments a bit | |
// Here we construct a wrapper function and mutate the arguments a bit |
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.
Done in fb31831.
dropshot_endpoint/src/lib.rs
Outdated
// Historically, we required that the `WebsocketConnection` argument | ||
// be first after the `RequestContext`. However, we also require | ||
// that any exclusive extractor (which includes the | ||
// `WebsocketUpgrade` argument that we put in its place) appears | ||
// last. We replaced the type above, but now we need to put it in | ||
// the right spot. | ||
sig.inputs = { | ||
let mut input_pairs = | ||
sig.inputs.clone().into_pairs().collect::<Vec<_>>(); | ||
let second_pair = input_pairs.remove(1); | ||
input_pairs.push(second_pair); | ||
input_pairs.into_iter().collect() | ||
}; |
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 curious what people think about this. The comment basically summarizes it: we previously said that a WebsocketConnection
has to be the first extractor. As part of this PR, I decided that exclusive extractors (which would include this one) have to be last. I did that because I think it's natural for the extractors to appear in the order they look at parts of the HTTP request, and exclusive extractors generally look at the request body (the last thing).
There's not actually a conflict here because this macro already translates the user's argument list into what the endpoint
macro expects, so we can shuffle the arguments around like this. But it's kind of annoying that we have to do this, and a user might notice the weirdness that their WebsocketConnection
has to appear first, but other exclusive extractors have to appear last.
We could:
- do nothing (what we're doing here). Downside: this ugliness, and maybe slightly confusing for people?
- make
WebsocketConnection
appear last. Downside: breaking change, though trivial to fix. - make exclusive extractors appear first instead. Downside: less intuitive for the much more common case
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 like making WebsocketConnection
appear last -- it's a breaking change but I think it's justified by the overall direction of the PR.
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.
likewise, i would by far prefer consistency over just leaving things as-is. and being that the only code using it now is this one thing, now would be the time to make such a change
I've sync'd this change up with "main", cleaned up the tests and other XXXs, and written migration instructions in the changelog. I think it's ready to go except maybe for this one question. @ahl Unfortunately I think the way I sync'd this up will make it annoying to do an incremental review. Sorry. Let me know if I can help. |
See #555 for motivation. We currently verify at runtime that you don't have two extractors that both try to read the body. We could make this a compile-time error instead.
This currently depends on #554 so I'm going to leave this as "draft". Once #554 lands, I will update the base branch of this PR. We may also want to wait for #557 (which implements #555), and then also wait for a prototype of that in Omicron, before landing this. Until we have all that, this could all be a bust.
Status:
hyper::Request
fromRequestContext
#557 at least, and maybe remove Arc around RequestContext #558)