twilight_model/application/command/
permissions.rs

1//! Limit who and where commands can be executed.
2
3use crate::id::{
4    marker::{
5        ApplicationMarker, ChannelMarker, CommandMarker, GenericMarker, GuildMarker, RoleMarker,
6        UserMarker,
7    },
8    Id,
9};
10use serde::{de::Deserializer, ser::Serializer, Deserialize, Serialize};
11use serde_repr::{Deserialize_repr, Serialize_repr};
12
13/// List of [`CommandPermission`]s for a command in a guild.
14#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
15pub struct GuildCommandPermissions {
16    /// ID of the application the command belongs to.
17    pub application_id: Id<ApplicationMarker>,
18    /// ID of the guild.
19    pub guild_id: Id<GuildMarker>,
20    /// ID of the command.
21    pub id: Id<CommandMarker>,
22    /// Command permissions in the guild.
23    ///
24    /// Max 100.
25    pub permissions: Vec<CommandPermission>,
26}
27
28/// Member, channel or role explicit permission to use a command.
29#[derive(Clone, Debug, Eq, Hash, PartialEq)]
30pub struct CommandPermission {
31    /// Affected resource.
32    pub id: CommandPermissionType,
33    /// Whether the resource is allowed or disallowed to use the command.
34    pub permission: bool,
35}
36
37/// Resources commands can allow or disallow from executing them.
38#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
39pub enum CommandPermissionType {
40    /// Affected channel.
41    ///
42    /// Use `@everyone - 1` for all channels in the guild.
43    Channel(Id<ChannelMarker>),
44    /// Affected role.
45    ///
46    /// The `@everyone` role is permitted.
47    Role(Id<RoleMarker>),
48    /// Affected member.
49    User(Id<UserMarker>),
50}
51
52impl CommandPermissionType {
53    /// Get the inner ID.
54    const fn id(self) -> Id<GenericMarker> {
55        match self {
56            Self::Channel(id) => id.cast(),
57            Self::Role(id) => id.cast(),
58            Self::User(id) => id.cast(),
59        }
60    }
61
62    /// Get the associated resource type.
63    const fn kind(self) -> CommandPermissionDataType {
64        match self {
65            Self::Channel(_) => CommandPermissionDataType::Channel,
66            Self::Role(_) => CommandPermissionDataType::Role,
67            Self::User(_) => CommandPermissionDataType::User,
68        }
69    }
70}
71
72#[derive(Deserialize, Serialize)]
73struct CommandPermissionData {
74    /// Affected resource.
75    id: Id<GenericMarker>,
76    /// Resource type.
77    #[serde(rename = "type")]
78    kind: CommandPermissionDataType,
79    /// Whether the resource is allowed or disallowed.
80    permission: bool,
81}
82
83#[derive(Clone, Debug, Deserialize_repr, Eq, PartialEq, Serialize_repr)]
84#[non_exhaustive]
85#[repr(u8)]
86enum CommandPermissionDataType {
87    Role = 1,
88    User = 2,
89    Channel = 3,
90}
91
92impl<'de> Deserialize<'de> for CommandPermission {
93    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
94        let data = CommandPermissionData::deserialize(deserializer)?;
95
96        let id = match data.kind {
97            CommandPermissionDataType::Role => CommandPermissionType::Role(data.id.cast()),
98            CommandPermissionDataType::User => CommandPermissionType::User(data.id.cast()),
99            CommandPermissionDataType::Channel => CommandPermissionType::Channel(data.id.cast()),
100        };
101
102        Ok(Self {
103            id,
104            permission: data.permission,
105        })
106    }
107}
108
109impl Serialize for CommandPermission {
110    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
111        let data = CommandPermissionData {
112            id: self.id.id(),
113            kind: self.id.kind(),
114            permission: self.permission,
115        };
116
117        data.serialize(serializer)
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::{
124        CommandPermission, CommandPermissionDataType, CommandPermissionType,
125        GuildCommandPermissions,
126    };
127    use crate::id::Id;
128    use serde_test::Token;
129
130    #[test]
131    fn serde_command_permission() {
132        let value = CommandPermission {
133            id: CommandPermissionType::Role(Id::new(100)),
134            permission: true,
135        };
136
137        serde_test::assert_tokens(
138            &value,
139            &[
140                Token::Struct {
141                    name: "CommandPermissionData",
142                    len: 3,
143                },
144                Token::Str("id"),
145                Token::NewtypeStruct { name: "Id" },
146                Token::Str("100"),
147                Token::Str("type"),
148                Token::U8(CommandPermissionDataType::Role as u8),
149                Token::Str("permission"),
150                Token::Bool(true),
151                Token::StructEnd,
152            ],
153        );
154    }
155
156    #[test]
157    fn serde_guild_command_permission() {
158        let value = GuildCommandPermissions {
159            application_id: Id::new(1),
160            guild_id: Id::new(2),
161            id: Id::new(3),
162            permissions: Vec::from([
163                CommandPermission {
164                    id: CommandPermissionType::Channel(Id::new(50)),
165                    permission: false,
166                },
167                CommandPermission {
168                    id: CommandPermissionType::User(Id::new(200)),
169                    permission: true,
170                },
171            ]),
172        };
173
174        serde_test::assert_tokens(
175            &value,
176            &[
177                Token::Struct {
178                    name: "GuildCommandPermissions",
179                    len: 4,
180                },
181                Token::Str("application_id"),
182                Token::NewtypeStruct { name: "Id" },
183                Token::Str("1"),
184                Token::Str("guild_id"),
185                Token::NewtypeStruct { name: "Id" },
186                Token::Str("2"),
187                Token::Str("id"),
188                Token::NewtypeStruct { name: "Id" },
189                Token::Str("3"),
190                Token::Str("permissions"),
191                Token::Seq { len: Some(2) },
192                Token::Struct {
193                    name: "CommandPermissionData",
194                    len: 3,
195                },
196                Token::Str("id"),
197                Token::NewtypeStruct { name: "Id" },
198                Token::Str("50"),
199                Token::Str("type"),
200                Token::U8(CommandPermissionDataType::Channel as u8),
201                Token::Str("permission"),
202                Token::Bool(false),
203                Token::StructEnd,
204                Token::Struct {
205                    name: "CommandPermissionData",
206                    len: 3,
207                },
208                Token::Str("id"),
209                Token::NewtypeStruct { name: "Id" },
210                Token::Str("200"),
211                Token::Str("type"),
212                Token::U8(CommandPermissionDataType::User as u8),
213                Token::Str("permission"),
214                Token::Bool(true),
215                Token::StructEnd,
216                Token::SeqEnd,
217                Token::StructEnd,
218            ],
219        );
220    }
221}