Skip to content
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

feat(twilight-model)!: Implement additional select menu types #2219

Merged
merged 7 commits into from
Jul 1, 2023

Conversation

archer-321
Copy link
Contributor

@archer-321 archer-321 commented Jun 12, 2023

This merge request implements Discord's new select menu types (user, role, mentionable, and channel) using the fully-flattened approach outlined in this RFC.

General idea

The general idea is to implement all variant-specific fields as Options inside the SelectMenu struct. While this might require runtime validation, it's more in line with the rest of the Twilight ecosystem. Moreover, it doesn't duplicate fields that an approach with separate structs would duplicate.

Notably, this differs from the RFC mentioned above. This RFC was indirectly denied with this review.

Breaking changes

This PR introduces multiple breaking changes:

  • ComponentType's SelectMenu variant was renamed, and the enum was extended (breaking as it's not #[non_exhaustive]).
  • SelectMenu's options field was wrapped in an Option.
  • New channel_types and kind fields were introduced in SelectMenu.
  • twilight_model::application::interaction::application_command::{CommandInteractionDataResolved, InteractionChannel, InteractionMember} were moved into twilight_model::application::interaction.
  • CommandInteractionDataResolved was renamed to InteractionDataResolved.
  • MessageComponentInteractionData no longer implements Eq + Hash.
  • A new resolved field was introduced in MessageComponent.
  • InteractionData::MessageComponent's single field was Box-ed.

If I missed any breaking changes, please point them out.

Related work

Tests

This PR only updates the existing tests. If you'd like me to add other tests to verify the behaviour introduced by this patch, please feel free to request changes.

Signed commits

I'm unsure whether this is possible with Twilight's PR flow, but please ask me to squash any commits or edit commit messages if necessary. I prefer all commits attributed to me to be signed with my GPG key 🤓

Labels

Based on the current state, I believe you should assign the d-api and d-breaking labels.

This patch implements the additional select menu types `user`, `role`,
`mentionable`, and `channel`. Moreover, it moves the type-specific data
for the existing text select menu implementation into a separate enum
variant.

The new types are implemented by "semi-flattening" the `SelectMenu`
struct: fields common to all select menu types are implemented in the
struct itself, and type-specific fields like the available options or
channel types are moved into a `SelectMenuData` enum. This enum is also
used to select a select menu's type.

This approach de-duplicates the common fields while preventing users
from accessing fields irrelevant to the current select menu type.

Finally, this commit updates the documentation and existing tests to
reflect these changes. The tests, however, might require additional
attention in case new behaviour introduced by this commit should be
tested as well.
@github-actions github-actions bot added c-model Affects the model crate c-validate Affects the validate crate m-breaking change Breaks the public API. t-feature Addition of a new feature labels Jun 12, 2023
@Gelbpunkt Gelbpunkt added d-api Change related to Discord's API. w-unapproved Proposal for change has *not* been approved by @twilight-rs/core. d-breaking Code breaks the API because of Discord labels Jun 12, 2023
@baptiste0928 baptiste0928 self-requested a review June 12, 2023 20:09
Copy link
Member

@baptiste0928 baptiste0928 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this PR, it looks good to me overall!

It seems that you should also add a resolved field to MessageComponentInteractionData (Discord documentation). This has been implemented in #2051 if you need a reference.

Moreover, those instances are Box-ed inside the enum to avoid unnecessarily increasing the enum's memory footprint if Discord adds new fields in the future.

This need to be discussed, but I personally think it just adds unnecessary complexity. Discord is unlikely to add fields that make the enum very large - and we can't anticipate everything anyway.

twilight-model/src/channel/message/component/mod.rs Outdated Show resolved Hide resolved
This patch makes `ComponentVisitor::visit_map` use `unreachable!`
instead of `panic!` in case a select menu type was not implemented while
being listed in a previous match statement.

Moreover, this commit sneaks in a minor formatting fix inside
`Components` doc-comment example.
This commit implements the `resolved` field for
`MessageComponentInteraction`. This field holds resolved users, roles,
channels, or attachments for select menu interactions.

Unfortunately, the new field makes it impossible to implement `Eq` and
`Hash` for `MessageComponentInteractionData`.
archer-321 added a commit to archer-321/twilight-select-menus that referenced this pull request Jun 13, 2023
This commit adds a simple Discord bot that creates and responds to
select menus using Twilight.

The main purpose of this bot is to demonstrate the API introduced by
GH-PR twilight-rs/twilight#2219. Specifically, the code in this
repository is a "quick and dirty" test, and its code is not scalable,
idiomatic, or in any way recommendable outside testing the API.
@github-actions github-actions bot added c-cache Affects the cache crate c-standby Affects the standby crate labels Jun 13, 2023
@archer-321
Copy link
Contributor Author

It seems that you should also add a resolved field to MessageComponentInteractionData (Discord documentation). This has been implemented in #2051 if you need a reference.

Indeed! I'm not sure how I missed this. I took the opportunity to create a small throwaway bot here: https://github.com/archer-321/twilight-select-menus.

It responds to simple legacy text commands and sends a message with all select menu types.

Screenshot

A screenshot showing the bot's message with five select menus

This need to be discussed, but I personally think it just adds unnecessary complexity. Discord is unlikely to add fields that make the enum very large - and we can't anticipate everything anyway.

I agree that it adds extra complexity and might be unnecessary. Still, I'm unsure whether the decreased complexity of removing it outweighs the benefit of having each variant be the size of a pointer. Let's wait for other reviews c:

This commit fixes the tests testing the (de-)serialization of
`InteractionDataResolved`. Previously, the tests were failing, as the
type was renamed from `CommandInteractionDataResolved`.

Moreover, this commit fixes the message component interaction tests by
adding the missing `resolved` field to the expected fields in
`message_component_interaction_data`.
@baptiste0928
Copy link
Member

Indeed! I'm not sure how I missed this. I took the opportunity to create a small throwaway bot here: https://github.com/archer-321/twilight-select-menus.

It's not documented in Message Component Data Structure, I only saw it because the previous PR had implemented it 😅

Thanks for the throwaway bot, it's really useful for reviewing :)

the benefit of having each variant be the size of a pointer.

The enum is 32 bytes without Box, 16 bytes with. There is no benefit, it's even worse because you have to make a heap allocation.

This commit unboxes `SelectMenuData`'s variants. They were boxed in a
previous patch to prevent future fields from unnecessarily increasing
the enum's memory footprint, but considering that, with the current
structures, new fields are a breaking change already, it's OK to leave
the variants unboxed for now and re-box as necessary.

For the current code, this saves a heap allocation while only marginally
increasing the enum's memory footprint.

This change is only breaking in the context of PR twilight-rs#2219. When added to
the commits in this PR, it doesn't add any breaking changes to the PR's
list of breaking changes.
@archer-321 archer-321 marked this pull request as ready for review June 14, 2023 14:15
@archer-321
Copy link
Contributor Author

The enum is 32 bytes without Box, 16 bytes with. There is no benefit, it's even worse because you have to make a heap allocation.

I see. Considering that additional fields would be a breaking change anyway, I agree that boxing the enum variants is unnecessary.

As discussed on Discord, I marked the PR as ready. After all, approving the implementation implicitly approves the general design (and thus the RFC).

@Gelbpunkt Gelbpunkt added the w-do-not-merge PR is blocked or deferred label Jun 14, 2023
@Gelbpunkt Gelbpunkt self-requested a review June 14, 2023 14:26
@HTGAzureX1212 HTGAzureX1212 self-requested a review June 14, 2023 14:38
Copy link
Contributor

@HTGAzureX1212 HTGAzureX1212 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

Copy link
Member

@Gelbpunkt Gelbpunkt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fundamentally disagree with the design choices in this PR, for several reasons. By "the design choices", I mean not flattening the struct.

  1. If we were to make the change from flattened structs back to enums, that should be done for all model structs and not for a single struct. That is inconsistent and, in my view, a code smell.
  2. We have in the past switched from enums to flattened structs for things like channel due to "[flattening] will prevent breaking changes and API oddities in the future" (a0ec109). Flip-flopping back and forth is not really desirable for maintainers or users.
  3. Field access gets really awkward with inner enums for fields present conditionally. some_select_menu.options as Option<Vec<SelectMenuOption>> makes accessing the field much easier than if let matching on parts of a struct which are contained in an enum.
  4. Flattened structs model the Discord API closer and will be less likely to cause breaking changes (this kind of repeats the argument from 2.). If options is ever going to be present for other types than 3, it won't require breaking public API changes. This also models the Discord documentation a bit closer (link).

TLDR: I don't see the added value at all. I'd be happy to accept this if we would just include the struct fields flattened as options, or if we revisit the entire model crate and make serious future commitments instead of changing model philosophy every few releases.

@HTGAzureX1212
Copy link
Contributor

HTGAzureX1212 commented Jun 14, 2023

I fundamentally disagree with the design choices in this PR, for several reasons. By "the design choices", I mean not flattening the struct.

  1. If we were to make the change from flattened structs back to enums, that should be done for all model structs and not for a single struct. That is inconsistent and, in my view, a code smell.
  2. We have in the past switched from enums to flattened structs for things like channel due to "[flattening] will prevent breaking changes and API oddities in the future" (a0ec109). Flip-flopping back and forth is not really desirable for maintainers or users.
  3. Field access gets really awkward with inner enums for fields present conditionally. some_select_menu.options as Option<Vec<SelectMenuOption>> makes accessing the field much easier than if let matching on parts of a struct which are contained in an enum.
  4. Flattened structs model the Discord API closer and will be less likely to cause breaking changes (this kind of repeats the argument from 2.). If options is ever going to be present for other types than 3, it won't require breaking public API changes. This also models the Discord documentation a bit closer (link).

TLDR: I don't see the added value at all. I'd be happy to accept this if we would just include the struct fields flattened as options, or if we revisit the entire model crate and make serious future commitments instead of changing model philosophy every few releases.

To be fair. I would agree if the models involved in the enum have more fields in common - but this does not seem to be the case. I believe that if there are many common fields for different types in the enum, then sure flattening would be better (reduce duplication and also memory footprint). But in this case I do not see much point flattening albeit conflicting with the current design choices - as far as I know there are not much fields that are in common between different select menu types (perhaps only 1 field?).

Not really sure to be honest, but this indeed can spark further discussion.

Nevermind, I read the documentation for one more time and it seems like flattening is better.

@Gelbpunkt
Copy link
Member

There are 6 common fields and 2 conditional ones, you can check it here.

@HTGAzureX1212 HTGAzureX1212 self-requested a review June 14, 2023 23:40
@ImDarkDiamond
Copy link

I'm also for flattening. After using the flattened channel struct for a while now I find it much nicer than matching on an enum

Copy link
Contributor

@HTGAzureX1212 HTGAzureX1212 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per comments made by @Gelbpunkt, flattening the structures will align better with the Discord API model as well as our design philosophy for model structures.

@archer-321
Copy link
Contributor Author

TLDR: I don't see the added value at all

I'm a huge fan of static enforcement for types, and I aim to implement it through my libraries wherever possible - even if it comes at the cost of increasing the required boilerplate. E.g. it's less convenient to match an Option than being able to dereference a pointer, but Options prevent null pointer dereferencing at compile time.

That said, it's way less dramatic with the entirely flattened approach. Instead of crashing the program, the struct would allow users to specify invalid select menus (e.g. a channel select menu that tries to set the options field).
Considering that Discord would return an error if a developer tries to do this, I can see how the increased convenience can outweigh the added value of static enforcement.

Of course, I can flatten the struct in this PR, but I have a couple of questions regarding the desired implementation:

  • How would users specify which select menu type they want to implement? User, role, and mentionable select menus don't add any type-specific fields, so there needs to be an additional field (e.g. kind) to select the type. However, having another kind type would duplicate the kind already present in the component (de-)serialization. Considering that Discord assigns different type values to the different select menu types, this approach doesn't follow the upstream API either.
  • As outlined in feat(model, validate, cache)!: Add new select components #2051 (review), "if we leave options unchanged, then if any other select menu type supports options, the type of option (string select menu options) won't fit". Should I follow the approach explained in this comment to move the implementation of the current options into a separate enum?

@laralove143
Copy link
Member

i think this discussion should be moved to #2217 and that discussion should be renamed to reflect an overall consistent change in the model crate and then we should merge or request changes in the pr before merging it based on the result

we should first decide how we handle rfcs though

@HTGAzureX1212
Copy link
Contributor

i think this discussion should be moved to #2217 and that discussion should be renamed to reflect an overall consistent change in the model crate and then we should merge or request changes in the pr before merging it based on the result

we should first decide how we handle rfcs though

Do we want a similar procedure with that of Rust’s?

@laralove143
Copy link
Member

i think this discussion should be moved to #2217 and that discussion should be renamed to reflect an overall consistent change in the model crate and then we should merge or request changes in the pr before merging it based on the result

we should first decide how we handle rfcs though

Do we want a similar procedure with that of Rust’s?

partially yeah i agree but i think we can keep it simpler since i scope is smaller i think the flow should be like

rfc -> contributing.md (if accepted) -> adapting twilight for rfc changes

@archer-321
Copy link
Contributor Author

Could we progress with the RFC or this PR? If the static enforcement introduced by this change isn't an option because it would differ from the rest of the library, I'd be OK with implementing the entirely flattened approach. However, I'm still unsure about the two questions listed in #2219 (comment) 😟

In my opinion, an RFC wouldn't be needed when going for the flattened approach that aligns with other types in Twilight. Instead, a more general RFC should discuss the benefits and drawbacks of changing structs to a partially flattened implementation throughout the library, in this case.

@HTGAzureX1212
Copy link
Contributor

Could we progress with the RFC or this PR? If the static enforcement introduced by this change isn't an option because it would differ from the rest of the library, I'd be OK with implementing the entirely flattened approach. However, I'm still unsure about the two questions listed in #2219 (comment) 😟

In my opinion, an RFC wouldn't be needed when going for the flattened approach that aligns with other types in Twilight. Instead, a more general RFC should discuss the benefits and drawbacks of changing structs to a partially flattened implementation throughout the library, in this case.

Yeah, it would be nice if you could progress with implementing the flattened approach.

@archer-321
Copy link
Contributor Author

I'm sorry if my last message sounded impatient or demanding; that wasn't my intention.

I agree; for now, an approach that's more in line with the rest of the library is probably best. However, I still have the two questions I linked to in my last message.

Specifically, I still have the two questions quoted below ⬇️

  • How would users specify which select menu type they want to implement? User, role, and mentionable select menus don't add any type-specific fields, so there needs to be an additional field (e.g. kind) to select the type. However, having another kind type would duplicate the kind already present in the component (de-)serialization. Considering that Discord assigns different type values to the different select menu types, this approach doesn't follow the upstream API either.
  • As outlined in
    feat(model, validate, cache)!: Add new select components #2051 (review), "if we leave options unchanged, then if any other select menu type supports options, the type of option (string select menu options) won't fit". Should I follow the approach explained in this comment to move the implementation of the current options into a separate enum?

I can't think of a satisfying implementation that addresses both problems (preventing a duplicate kind field and future-proofing the API if Discord adds more select menu options).

Before I spend more hours creating another patch that fundamentally doesn't align with the crate's vision, I'd like some input before I start changing the API again. I hope you understand 🙂

@baptiste0928
Copy link
Member

How would users specify which select menu type they want to implement?

A kind field seems like a good solution to me. There is no duplication in the public API (except for the kind() method), only in the implementation, so it's not confusing imo.

Should I follow the approach explained in this comment to move the implementation of the current options into a separate enum?

Personally, I think it's best to focus on a simple implementation. We can't anticipate all the changes Discord will make, so it's not worth making our API more complicated for changes we don't know will happen. I'd be curious to know what others think about this.

This commit removes the SelectMenuData type and embeds all
variant-specific fields in the base struct. Moreover, it adds
documentation indicating which fields are required variant-specific
fields.

This commit also updates the component validation module in
`twilight-validate` by introducing a new error type for text select
menus that don't have the necessary `options` field.
archer-321 added a commit to archer-321/twilight-select-menus that referenced this pull request Jun 24, 2023
This commit updates the bot to reflect the most recent changes to
twilight-rs/twilight#2219 introduced in c774adc0. Specifically, the
`SelectMenu` struct was flattened to include all variant-specific
fields. Moreover, a new `kind` field is used instead of `data` to
determine the select menu variant.
@archer-321
Copy link
Contributor Author

archer-321 commented Jun 24, 2023

I implemented the entirely flattened approach requested above and updated the test bot. If I missed something, please let me know.

A kind field seems like a good solution to me.

Done ✔️

Personally, I think it's best to focus on a simple implementation. We can't anticipate all the changes Discord will make, so it's not worth making our API more complicated for changes we don't know will happen. I'd be curious to know what others think about this.

Considering that the API isn't stable across releases at this point in Twilight's development, this would likely be overkill. For now, I implemented the fields directly.

Copy link
Member

@Gelbpunkt Gelbpunkt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall code LGTM, I have some nits on documentation but I'm not a native speaker and there are other people who might disagree so I'd be fine with merging this as-is, consider my comments as what went through my mind when reading this

pub enum SelectMenuType {
/// Select menus with a text-based `options` list.
///
/// Select menus of this `kind` *must* set the `options` field to specify the options users
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be writing API documentation for Discord in all places imaginable, one should be enough on the options field

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mention this in the enum documentation, as users may only read its documentation to see which select menu types are available.
Now that the options field is an Option<_>, I think the duplicated documentation is worth it if it saves some people from debugging a Discord API error.
However, I'm open to talking about this instance.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any other opinions on this or do we leave it as is?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree upon that this duplicated documentation is worth it; in terms of helping users understand the requirements on fields for specific select menu types.

As such, I believe that we can keep this as is.

This commit streamlines the language in the `SelectMenu` and
`SelectMenuType` documentation. In particular, it removes duplicated
sections outlining required and optional fields. Moreover, it makes
language describing which menu types use a specific option more in
line with the rest of the library.
@vilgotf
Copy link
Member

vilgotf commented Jul 1, 2023

This PR has three approvals but is still labled as unapproved. What's holding this up?

@Gelbpunkt
Copy link
Member

This PR has three approvals but is still labled as unapproved. What's holding this up?

Nothing, I forgot to remove the label after the PR switched to flattened models

@Gelbpunkt Gelbpunkt removed w-do-not-merge PR is blocked or deferred w-unapproved Proposal for change has *not* been approved by @twilight-rs/core. labels Jul 1, 2023
@HTGAzureX1212
Copy link
Contributor

This PR has three approvals but is still labled as unapproved. What's holding this up?

Nothing, I forgot to remove the label after the PR switched to flattened models

Then I presume that this pull request can be merged.

@vilgotf vilgotf merged commit 089b84d into twilight-rs:next Jul 1, 2023
10 checks passed
@vilgotf
Copy link
Member

vilgotf commented Jul 1, 2023

Amazing work, thank you so much! 🎉

@vilgotf vilgotf linked an issue Jul 1, 2023 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c-cache Affects the cache crate c-model Affects the model crate c-standby Affects the standby crate c-validate Affects the validate crate d-api Change related to Discord's API. d-breaking Code breaks the API because of Discord m-breaking change Breaks the public API. t-feature Addition of a new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support all Select Menu types
7 participants