-
Notifications
You must be signed in to change notification settings - Fork 599
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Improving Http
and surrounding APIs
#1905
Comments
I've come up with a potential solution to the 3rd point above. It involves changing the builder methods to be called on the result of the relevant method on the corresponding model type, rather than inside a closure passed as argument to that method. As an example: The following code: let msg = channel_id
.send_message(&ctx.http, |m| {
m.content("Hello, World!")
.embed(|e| {
e.title("This is a title")
.description("This is a description")
.image("attachment://ferris_eyes.png")
})
.add_file("./ferris_eyes.png")
})
.await?; would turn into this: let msg = channel_id
.send_message(&ctx.http)
.content("Hello, World!")
.embed(|e| {
e.title("This is a title")
.description("This is a description")
.image("attachment://ferris_eyes.png")
})
.add_file("./ferris_eyes.png")
.await?; or possibly even this: let e = Embed::builder()
.title("This is a title")
.description("This is a description")
.image("attachment://ferris_eyes.png");
let msg = channel_id
.send_message(&ctx.http)
.content("Hello, World!")
.add_file("./ferris_eyes.png")
.embed(e)
.await?; This would require changing the return types of all the current model methods that take builder closures as arguments from this: pub async fn send_message<'a, F>(self, http: impl AsRef<Http>, f: F) -> Result<Message>
where
for<'b> F: FnOnce(&'b mut CreateMessage<'a>) -> &'b mut CreateMessage<'a>, to this: pub fn send_message<'a>(self, http: impl AsRef<Http>) -> CreateMessage<'a> {
CreateMessage::new(http.as_ref())
} Note that impl<'a> Future for CreateMessage<'a> {
type Output = Result<Message>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
...
}
} Or simpler would be to add an impl<'a> CreateMessage<'a> {
async fn execute() -> Result<Message> {
...
}
} Besides allowing for less-nested code, this opens the door to allow encoding API-level invariants into the type system. For example, the Thoughts? |
I think semantically it would be a little confusing. For example, the methods on I did think some more about using a proc-macro to autogenerate the wrapper methods. You would define a method once on #[wrapped_by(Guild, PartialGuild, field=id)]
/// Gets all auto moderation [`Rule`]s of this guild via HTTP.
///
/// **Note**: Requires the [Manage Guild] permission.
///
/// # Errors
///
/// Returns an [`Error::Http`] if the guild is unavailable.
///
/// [Manage Guild]: Permissions::MANAGE_GUILD
#[inline]
pub async fn automod_rules(self, http: impl AsRef<Http>) -> Result<Vec<Rule>> {
http.as_ref().get_automod_rules(self.get()).await
} Then, the macro could duplicate the docs, the signature, and the return type, and the body would simply be |
This seems like quite a large issue tracking multiple concepts. Certain parts of it are also already fixed. Should this just be closed as it's not really useful? |
A lot of this has already been addressed. If you feel like some parts are worth tracking still, you can file another issue, but this one can be closed. |
There was some discussion about some areas of the API where there is currently room for improvement; things like taking more advantage of Rust's type system to ensure API compatibility, plus removing duplicated code or method signatures as well as in general decreasing API surface area to prevent future internal changes from being breaking. I've gone and listed some of them below, so that they don't become lost:
Http
might be considered the "canonical" wrappers around the Discord API, they also don't take advantage of Rust's type system at all, since they take a Json object, which is basically just a HashMap with Strings as keys. The user interacts with theHttp
struct quite often, mainly to facilitate requests to Discord endpoints on their behalf. So, they also have access to these methods.It would be nice to mark all such methods as
pub(crate)
instead ofpub
, as this would require users go through the various model types instead, which only allow specific fields to be set in the hashmap. Plus, if Discord changes how an endpoint works in some way, then the underlyingHttp
implementation could be changed without introducing breaking changes, while methods on model types would remain unaffected (methods would only be added, not changed). This would allow features to release quicker and reduce technical debt.Http
take an optionalaudit_log_reason
parameter, which currently all the wrapper methods on model types just set toNone
. Should we keep these methods exposed, or add methods to the model types to allow passing an optionalaudit_log_reason
? Better yet, is it worth considering adding builders for these specific function calls? As in, take as argument a closure that constructs a builder struct. This might not be worth doing for methods with very few arguments.GuildId
/Guild
/PartialGuild
types. Many methods inGuildId
are wrapped by methods on the other two guild types. When combined with (1), the worry is that this code duplication could introduce inconsistency. So, it would be ideal if there were only a single possible callsite. The logical choice is to just keep as many methods onGuildId
as possible. However, semantically, these methods operate on guilds, not on the id itself, so there's a discrepancy there.The alternative would be to auto-generate wrappers, perhaps using a proc-macro. Many of these wrappers are simple thin wrappers. Others add some functionality though, such as checking the cache to validate user permissions. These too could probably be auto-impl'd using a proc-macro, as long as the required permissions are specified in each case.
Any thoughts on how to tackle these issues would be greatly appreciated!
The text was updated successfully, but these errors were encountered: