twilight_http/request/guild/emoji/
create_emoji.rs

1use crate::{
2    client::Client,
3    error::Error,
4    request::{self, AuditLogReason, Request, TryIntoRequest},
5    response::{Response, ResponseFuture},
6    routing::Route,
7};
8use serde::Serialize;
9use std::future::IntoFuture;
10use twilight_model::{
11    guild::Emoji,
12    id::{
13        marker::{GuildMarker, RoleMarker},
14        Id,
15    },
16};
17use twilight_validate::request::{audit_reason as validate_audit_reason, ValidationError};
18
19#[derive(Serialize)]
20struct CreateEmojiFields<'a> {
21    image: &'a str,
22    name: &'a str,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    roles: Option<&'a [Id<RoleMarker>]>,
25}
26
27/// Create an emoji in a guild.
28///
29/// The emoji must be a Data URI, in the form of
30/// `data:image/{type};base64,{data}` where `{type}` is the image MIME type and
31/// `{data}` is the base64-encoded image. See [Discord Docs/Image Data].
32///
33/// [Discord Docs/Image Data]: https://discord.com/developers/docs/reference#image-data
34#[must_use = "requests must be configured and executed"]
35pub struct CreateEmoji<'a> {
36    fields: CreateEmojiFields<'a>,
37    guild_id: Id<GuildMarker>,
38    http: &'a Client,
39    reason: Result<Option<&'a str>, ValidationError>,
40}
41
42impl<'a> CreateEmoji<'a> {
43    pub(crate) const fn new(
44        http: &'a Client,
45        guild_id: Id<GuildMarker>,
46        name: &'a str,
47        image: &'a str,
48    ) -> Self {
49        Self {
50            fields: CreateEmojiFields {
51                image,
52                name,
53                roles: None,
54            },
55            guild_id,
56            http,
57            reason: Ok(None),
58        }
59    }
60
61    /// Whitelist roles for this emoji.
62    ///
63    /// See [Discord Docs/Emoji Object].
64    ///
65    /// [Discord Docs/Emoji Object]: https://discord.com/developers/docs/resources/emoji#emoji-object-emoji-structure
66    pub const fn roles(mut self, roles: &'a [Id<RoleMarker>]) -> Self {
67        self.fields.roles = Some(roles);
68
69        self
70    }
71}
72
73impl<'a> AuditLogReason<'a> for CreateEmoji<'a> {
74    fn reason(mut self, reason: &'a str) -> Self {
75        self.reason = validate_audit_reason(reason).and(Ok(Some(reason)));
76
77        self
78    }
79}
80
81impl IntoFuture for CreateEmoji<'_> {
82    type Output = Result<Response<Emoji>, Error>;
83
84    type IntoFuture = ResponseFuture<Emoji>;
85
86    fn into_future(self) -> Self::IntoFuture {
87        let http = self.http;
88
89        match self.try_into_request() {
90            Ok(request) => http.request(request),
91            Err(source) => ResponseFuture::error(source),
92        }
93    }
94}
95
96impl TryIntoRequest for CreateEmoji<'_> {
97    fn try_into_request(self) -> Result<Request, Error> {
98        let mut request = Request::builder(&Route::CreateEmoji {
99            guild_id: self.guild_id.get(),
100        });
101
102        request = request.json(&self.fields);
103
104        if let Some(reason) = self.reason.map_err(Error::validation)? {
105            request = request.headers(request::audit_header(reason)?);
106        }
107
108        request.build()
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use std::error::Error;
116
117    #[test]
118    fn test_create_emoji() -> Result<(), Box<dyn Error>> {
119        const GUILD_ID: Id<GuildMarker> = Id::new(1);
120        const ROLE_ID: Id<RoleMarker> = Id::new(2);
121
122        let client = Client::new("token".into());
123
124        {
125            let expected = r#"{"image":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI","name":"square"}"#;
126            let actual = CreateEmoji::new(
127                &client,
128                GUILD_ID,
129                "square",
130                "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI",
131            )
132            .try_into_request()?;
133
134            assert_eq!(Some(expected.as_bytes()), actual.body());
135        }
136
137        {
138            let expected = r#"{"image":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI","name":"square","roles":["2"]}"#;
139            let actual = CreateEmoji::new(
140                &client,
141                GUILD_ID,
142                "square",
143                "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI",
144            )
145            .roles(&[ROLE_ID])
146            .try_into_request()?;
147
148            assert_eq!(Some(expected.as_bytes()), actual.body());
149        }
150        Ok(())
151    }
152}