-
-
Notifications
You must be signed in to change notification settings - Fork 131
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 an HTTP ResponseFuture
struct
#1008
Merged
zeylahellyer
merged 2 commits into
twilight-rs:next
from
zeylahellyer:feature/http/response-future
Jul 10, 2021
Merged
Add an HTTP ResponseFuture
struct
#1008
zeylahellyer
merged 2 commits into
twilight-rs:next
from
zeylahellyer:feature/http/response-future
Jul 10, 2021
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
zeylahellyer
added
t-feature
Addition of a new feature
c-http
Affects the http crate
m-breaking change
Breaks the public API.
labels
Jul 3, 2021
We currently take owned heap allocated types, such as Vecs and Strings. This is due to the lifetimes around requests, their inner polled futures, and the caller are difficult to work around. The way requests currently work is that they implement `std::future::Future` and "start" a future when initially polled for the first time. They then store this future internally in a field like so: ```rust pub struct SomeRequest { fut: Option<Pending<'a, Message>>, // more fields... } ``` This requires a lot of boilerplate on our part: we have a macro implementing `Future` on every request with more or less unique implementations and need fields for the inner future. For users, this design comes at a cost of performance and clarity. Users need to give us owned arguments. It is also impossible for users to determine whether a request is in flight using the type system due to that information being hidden. We need to clone arguments if we want to avoid panics from users accidentally double polling a request after completion, when it may not be clear that a request has been used due to the request not being consumed. Instead, we can turn the inner request functions that "start" a future into ones that consume and execute the request and then return a future resolving to a response directly to the user. The new `response::ResponseFuture` implements `Poll` and will begin the execution of a request - if a failure did not occur during construction - while polling it to completion, providing the resulting `response::Response`. Internally the implementation of the `CreateMessage::exec` function looks like this: ```rust pub fn exec(self) -> ResponseFuture<Message> { let request = Request::from_route(Route::GetMessage { channel_id: self.channel_id.0, message_id: self.message_id.0, }); self.http.request(request) } ``` The usage of executing the request and printing the message contents looks like this: ```rust let msg = client.message(channel_id, message_id).exec().await?; let message = response.model().await?; println!("content: {}", message.content); ``` This work does not include the optimization work that turns all of our owned parameters to borrowed ones and will be done by a separate PR. Signed-off-by: Zeyla Hellyer <zeyla@hellyer.dev>
Rebased with the merging of #923. |
7596ff
previously requested changes
Jul 9, 2021
Signed-off-by: Zeyla Hellyer <zeyla@hellyer.dev>
Addressed comments. |
zeylahellyer
dismissed
7596ff’s stale review
July 9, 2021 01:35
Concerns have been resolved or answered.
7596ff
approved these changes
Jul 9, 2021
BlackHoleFox
approved these changes
Jul 10, 2021
7596ff
pushed a commit
to 7596ff/twilight
that referenced
this pull request
Jul 25, 2021
We currently take owned heap allocated types, such as Vecs and Strings. This is due to the lifetimes around requests, their inner polled futures, and the caller are difficult to work around. The way requests currently work is that they implement `std::future::Future` and "start" a future when initially polled for the first time. They then store this future internally in a field like so: ```rust pub struct SomeRequest { fut: Option<Pending<'a, Message>>, // more fields... } ``` This requires a lot of boilerplate on our part: we have a macro implementing `Future` on every request with more or less unique implementations and need fields for the inner future. For users, this design comes at a cost of performance and clarity. Users need to give us owned arguments. It is also impossible for users to determine whether a request is in flight using the type system due to that information being hidden. We need to clone arguments if we want to avoid panics from users accidentally double polling a request after completion, when it may not be clear that a request has been used due to the request not being consumed. Instead, we can turn the inner request functions that "start" a future into ones that consume and execute the request and then return a future resolving to a response directly to the user. The new `response::ResponseFuture` implements `Poll` and will begin the execution of a request - if a failure did not occur during construction - while polling it to completion, providing the resulting `response::Response`. Internally the implementation of the `CreateMessage::exec` function looks like this: ```rust pub fn exec(self) -> ResponseFuture<Message> { let request = Request::from_route(Route::GetMessage { channel_id: self.channel_id.0, message_id: self.message_id.0, }); self.http.request(request) } ``` The usage of executing the request and printing the message contents looks like this: ```rust let msg = client.message(channel_id, message_id).exec().await?; let message = response.model().await?; println!("content: {}", message.content); ``` This work does not include the optimization work that turns all of our owned parameters to borrowed ones and will be done by a separate PR. Signed-off-by: Zeyla Hellyer <zeyla@hellyer.dev>
7596ff
pushed a commit
to 7596ff/twilight
that referenced
this pull request
Jul 31, 2021
We currently take owned heap allocated types, such as Vecs and Strings. This is due to the lifetimes around requests, their inner polled futures, and the caller are difficult to work around. The way requests currently work is that they implement `std::future::Future` and "start" a future when initially polled for the first time. They then store this future internally in a field like so: ```rust pub struct SomeRequest { fut: Option<Pending<'a, Message>>, // more fields... } ``` This requires a lot of boilerplate on our part: we have a macro implementing `Future` on every request with more or less unique implementations and need fields for the inner future. For users, this design comes at a cost of performance and clarity. Users need to give us owned arguments. It is also impossible for users to determine whether a request is in flight using the type system due to that information being hidden. We need to clone arguments if we want to avoid panics from users accidentally double polling a request after completion, when it may not be clear that a request has been used due to the request not being consumed. Instead, we can turn the inner request functions that "start" a future into ones that consume and execute the request and then return a future resolving to a response directly to the user. The new `response::ResponseFuture` implements `Poll` and will begin the execution of a request - if a failure did not occur during construction - while polling it to completion, providing the resulting `response::Response`. Internally the implementation of the `CreateMessage::exec` function looks like this: ```rust pub fn exec(self) -> ResponseFuture<Message> { let request = Request::from_route(Route::GetMessage { channel_id: self.channel_id.0, message_id: self.message_id.0, }); self.http.request(request) } ``` The usage of executing the request and printing the message contents looks like this: ```rust let msg = client.message(channel_id, message_id).exec().await?; let message = response.model().await?; println!("content: {}", message.content); ``` This work does not include the optimization work that turns all of our owned parameters to borrowed ones and will be done by a separate PR. Signed-off-by: Zeyla Hellyer <zeyla@hellyer.dev>
7596ff
added a commit
that referenced
this pull request
Jul 31, 2021
Enhancements Many functions have been made constant ([#1010] - [@zeylahellyer]). Changes There are significant changes to how users make HTTP requests. When users make a request, they must pass borrowed types instead of owned types. To execute the request, users must call `exec` on the request builder. Once the request has completed execution, users may use the `ResponseFuture` struct methods to access the status code of the request. To access a returned model, if there is one, users must call `model` on the response. A call to `Client::create_message` like this: ```rust client.create_message(ChannelId(1)) .content("some content")? .embed(Embed {})? .await?; ``` is now written like this: ```rust client.create_message(ChannelId(1)) .content(&"some conntent")? .embeds(&[&Embed {}])? .exec() .await? .model() .await?; ``` For more information on the motivation behind these changes, see the PR descriptions of [#923], [#1008], and [#1009]. These changes were authored by [@zeylahellyer]. Rename `ErrorCode::UnallowedWordsForPublicStage` variant to `UnallowedWords` ([#956] - [@7596ff]) `CreateGlobalCommand`, `CreateGuildCommand`, `SetGlobalCommands`, and `SetGuildCommands` now return command(s) ([#1037] - [@vilgotf]). A few spelling errors have been fixed by adding the `codespell` Action ([#1041] - [@Gelbpunkt]. [#923]: #923 [#956]: #956 [#1008]: #1008 [#1009]: #1009 [#1010]: #1010 [#1037]: #1037 [#1041]: #1041 [@7596ff]: https://github.com/7596ff [@Gelbpunkt]: https://github.com/Gelbpunkt [@vilgotf]: https://github.com/vilgotf [@zeylahellyer]: https://github.com/zeylahellyer
Erk-
pushed a commit
to Erk-/twilight
that referenced
this pull request
Aug 9, 2021
We currently take owned heap allocated types, such as Vecs and Strings. This is due to the lifetimes around requests, their inner polled futures, and the caller are difficult to work around. The way requests currently work is that they implement `std::future::Future` and "start" a future when initially polled for the first time. They then store this future internally in a field like so: ```rust pub struct SomeRequest { fut: Option<Pending<'a, Message>>, // more fields... } ``` This requires a lot of boilerplate on our part: we have a macro implementing `Future` on every request with more or less unique implementations and need fields for the inner future. For users, this design comes at a cost of performance and clarity. Users need to give us owned arguments. It is also impossible for users to determine whether a request is in flight using the type system due to that information being hidden. We need to clone arguments if we want to avoid panics from users accidentally double polling a request after completion, when it may not be clear that a request has been used due to the request not being consumed. Instead, we can turn the inner request functions that "start" a future into ones that consume and execute the request and then return a future resolving to a response directly to the user. The new `response::ResponseFuture` implements `Poll` and will begin the execution of a request - if a failure did not occur during construction - while polling it to completion, providing the resulting `response::Response`. Internally the implementation of the `CreateMessage::exec` function looks like this: ```rust pub fn exec(self) -> ResponseFuture<Message> { let request = Request::from_route(Route::GetMessage { channel_id: self.channel_id.0, message_id: self.message_id.0, }); self.http.request(request) } ``` The usage of executing the request and printing the message contents looks like this: ```rust let msg = client.message(channel_id, message_id).exec().await?; let message = response.model().await?; println!("content: {}", message.content); ``` This work does not include the optimization work that turns all of our owned parameters to borrowed ones and will be done by a separate PR. Signed-off-by: Zeyla Hellyer <zeyla@hellyer.dev>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Labels
c-http
Affects the http crate
m-breaking change
Breaks the public API.
t-feature
Addition of a new feature
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
We currently take owned types, such as Vecs and Strings. This is due to the lifetimes around requests, their inner polled futures, and the caller are difficult to work around.
The way requests currently work is that they implement
std::future::Future
and "start" a future when initially polled for the first time. They then store this future internally in a field like so:This requires a lot of boilerplate on our part: we have a macro implementing
Future
on every request with more or less unique implementations and need fields for the inner future.For users, this design comes at a cost of performance and clarity. Users need to give us owned arguments. It is also impossible for users to determine whether a request is in flight using the type system due to that information being hidden. We need to clone arguments if we want to avoid panics from users accidentally double polling a request after completion, when it may not be clear that a request has been used due to the request not being consumed.
Instead, we can turn the inner request functions that "start" a future into ones that consume and execute the request and then return a future resolving to a response directly to the user. The new
response::ResponseFuture
implementsPoll
and will begin the execution of a request - if a failure did not occur during construction - while polling it to completion, providing the resultingresponse::Response
.Internally the implementation of the
CreateMessage::exec
function looks like this:The usage of executing the request and printing the message contents looks like this:
This work does not include the optimization work that turns all of our owned parameters to borrowed ones and will be done by a separate PR.
Blocks on #923.Merged.