twilight_http/request/channel/webhook/
update_webhook_with_token.rs

1use crate::{
2    client::Client,
3    error::Error,
4    request::{Nullable, Request, TryIntoRequest},
5    response::{Response, ResponseFuture},
6    routing::Route,
7};
8use serde::Serialize;
9use std::future::IntoFuture;
10use twilight_model::{
11    channel::Webhook,
12    id::{marker::WebhookMarker, Id},
13};
14use twilight_validate::request::{webhook_username as validate_webhook_username, ValidationError};
15
16#[derive(Serialize)]
17struct UpdateWebhookWithTokenFields<'a> {
18    #[serde(skip_serializing_if = "Option::is_none")]
19    avatar: Option<Nullable<&'a str>>,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    name: Option<&'a str>,
22}
23
24/// Update a webhook, with a token, by ID.
25#[must_use = "requests must be configured and executed"]
26pub struct UpdateWebhookWithToken<'a> {
27    fields: Result<UpdateWebhookWithTokenFields<'a>, ValidationError>,
28    http: &'a Client,
29    token: &'a str,
30    webhook_id: Id<WebhookMarker>,
31}
32
33impl<'a> UpdateWebhookWithToken<'a> {
34    pub(crate) const fn new(
35        http: &'a Client,
36        webhook_id: Id<WebhookMarker>,
37        token: &'a str,
38    ) -> Self {
39        Self {
40            fields: Ok(UpdateWebhookWithTokenFields {
41                avatar: None,
42                name: None,
43            }),
44            http,
45            token,
46            webhook_id,
47        }
48    }
49
50    /// Set the avatar of the webhook.
51    ///
52    /// This must be a Data URI, in the form of
53    /// `data:image/{type};base64,{data}` where `{type}` is the image MIME type
54    /// and `{data}` is the base64-encoded image. See [Discord Docs/Image Data].
55    ///
56    /// [Discord Docs/Image Data]: https://discord.com/developers/docs/reference#image-data
57    pub fn avatar(mut self, avatar: Option<&'a str>) -> Self {
58        if let Ok(fields) = self.fields.as_mut() {
59            fields.avatar = Some(Nullable(avatar));
60        }
61
62        self
63    }
64
65    /// Change the name of the webhook.
66    ///
67    /// # Errors
68    ///
69    /// Returns an error of type [`WebhookUsername`] if the webhook's name is
70    /// invalid.
71    ///
72    /// [`WebhookUsername`]: twilight_validate::request::ValidationErrorType::WebhookUsername
73    pub fn name(mut self, name: &'a str) -> Self {
74        self.fields = self.fields.and_then(|mut fields| {
75            validate_webhook_username(name)?;
76            fields.name = Some(name);
77
78            Ok(fields)
79        });
80
81        self
82    }
83}
84
85impl IntoFuture for UpdateWebhookWithToken<'_> {
86    type Output = Result<Response<Webhook>, Error>;
87
88    type IntoFuture = ResponseFuture<Webhook>;
89
90    fn into_future(self) -> Self::IntoFuture {
91        let http = self.http;
92
93        match self.try_into_request() {
94            Ok(request) => http.request(request),
95            Err(source) => ResponseFuture::error(source),
96        }
97    }
98}
99
100impl TryIntoRequest for UpdateWebhookWithToken<'_> {
101    fn try_into_request(self) -> Result<Request, Error> {
102        let fields = self.fields.map_err(Error::validation)?;
103
104        Request::builder(&Route::UpdateWebhook {
105            token: Some(self.token),
106            webhook_id: self.webhook_id.get(),
107        })
108        .use_authorization_token(false)
109        .json(&fields)
110        .build()
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use std::error::Error;
118
119    #[test]
120    fn test_update_webhook_with_token() -> Result<(), Box<dyn Error>> {
121        const WEBHOOK_ID: Id<WebhookMarker> = Id::new(1);
122
123        let client = Client::new("token".into());
124
125        {
126            let expected = r"{}";
127            let actual =
128                UpdateWebhookWithToken::new(&client, WEBHOOK_ID, "token").try_into_request()?;
129
130            assert_eq!(Some(expected.as_bytes()), actual.body());
131        }
132
133        {
134            let expected = r#"{"avatar":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI"}"#;
135            let actual = UpdateWebhookWithToken::new(&client, WEBHOOK_ID, "token")
136            .avatar(Some("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI"))
137                .try_into_request()?;
138
139            assert_eq!(Some(expected.as_bytes()), actual.body());
140
141            let expected = r#"{"avatar":null}"#;
142            let actual = UpdateWebhookWithToken::new(&client, WEBHOOK_ID, "token")
143                .avatar(None)
144                .try_into_request()?;
145
146            assert_eq!(Some(expected.as_bytes()), actual.body());
147        }
148
149        {
150            let expected = r#"{"name":"Captain Hook"}"#;
151            let actual = UpdateWebhookWithToken::new(&client, WEBHOOK_ID, "token")
152                .name("Captain Hook")
153                .try_into_request()?;
154
155            assert_eq!(Some(expected.as_bytes()), actual.body());
156        }
157
158        {
159            let expected = r#"{"avatar":null,"name":"Captain Hook"}"#;
160            let actual = UpdateWebhookWithToken::new(&client, WEBHOOK_ID, "token")
161                .avatar(None)
162                .name("Captain Hook")
163                .try_into_request()?;
164
165            assert_eq!(Some(expected.as_bytes()), actual.body());
166        }
167        Ok(())
168    }
169}