twilight_util/link/
webhook.rs1use std::{
7 error::Error,
8 fmt::{Display, Formatter, Result as FmtResult},
9 num::NonZeroU64,
10};
11use twilight_model::id::{marker::WebhookMarker, Id};
12
13#[derive(Debug)]
17pub struct WebhookParseError {
18 kind: WebhookParseErrorType,
19 source: Option<Box<dyn Error + Send + Sync>>,
20}
21
22impl WebhookParseError {
23 #[must_use = "retrieving the type has no effect if left unused"]
25 pub const fn kind(&self) -> &WebhookParseErrorType {
26 &self.kind
27 }
28
29 #[must_use = "consuming the error and retrieving the source has no effect if left unused"]
31 pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync>> {
32 self.source
33 }
34
35 #[must_use = "consuming the error into its parts has no effect if left unused"]
37 pub fn into_parts(self) -> (WebhookParseErrorType, Option<Box<dyn Error + Send + Sync>>) {
38 (self.kind, self.source)
39 }
40}
41
42impl Display for WebhookParseError {
43 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
44 match self.kind {
45 WebhookParseErrorType::IdInvalid => f.write_str("url path segment isn't a valid ID"),
46 WebhookParseErrorType::SegmentMissing => {
47 f.write_str("url is missing a required path segment")
48 }
49 }
50 }
51}
52
53impl Error for WebhookParseError {
54 fn source(&self) -> Option<&(dyn Error + 'static)> {
55 self.source
56 .as_ref()
57 .map(|source| &**source as &(dyn Error + 'static))
58 }
59}
60
61#[derive(Debug)]
63#[non_exhaustive]
64pub enum WebhookParseErrorType {
65 IdInvalid,
67 SegmentMissing,
69}
70
71pub fn parse(url: &str) -> Result<(Id<WebhookMarker>, Option<&str>), WebhookParseError> {
118 let mut segments = {
119 let mut start = url.split("discord.com/api/webhooks/");
120 let path = start.nth(1).ok_or(WebhookParseError {
121 kind: WebhookParseErrorType::SegmentMissing,
122 source: None,
123 })?;
124
125 path.split('/')
126 };
127
128 let id_segment = segments.next().ok_or(WebhookParseError {
129 kind: WebhookParseErrorType::SegmentMissing,
130 source: None,
131 })?;
132
133 if id_segment.is_empty() {
135 return Err(WebhookParseError {
136 kind: WebhookParseErrorType::SegmentMissing,
137 source: None,
138 });
139 }
140
141 let id = id_segment
142 .parse::<NonZeroU64>()
143 .map_err(|source| WebhookParseError {
144 kind: WebhookParseErrorType::IdInvalid,
145 source: Some(Box::new(source)),
146 })?;
147 let mut token = segments.next();
148
149 if token.is_some_and(str::is_empty) {
151 token = None;
152 }
153
154 Ok((Id::from(id), token))
155}
156
157#[cfg(test)]
158mod tests {
159 use super::{WebhookParseError, WebhookParseErrorType};
160 use static_assertions::assert_impl_all;
161 use std::{error::Error, fmt::Debug};
162 use twilight_model::id::Id;
163
164 assert_impl_all!(WebhookParseErrorType: Debug, Send, Sync);
165 assert_impl_all!(WebhookParseError: Debug, Error, Send, Sync);
166
167 #[test]
168 fn parse_no_token() {
169 assert_eq!(
170 (Id::new(123), None),
171 super::parse("https://discord.com/api/webhooks/123").unwrap(),
172 );
173 assert_eq!(
176 (Id::new(123), None),
177 super::parse("https://discord.com/api/webhooks/123").unwrap(),
178 );
179 assert!(super::parse("https://discord.com/api/webhooks/123/")
180 .unwrap()
181 .1
182 .is_none());
183 }
184
185 #[test]
186 fn parse_with_token() {
187 assert_eq!(
188 super::parse("https://discord.com/api/webhooks/456/token").unwrap(),
189 (Id::new(456), Some("token")),
190 );
191 assert_eq!(
193 super::parse("https://discord.com/api/webhooks/456/token/github").unwrap(),
194 (Id::new(456), Some("token")),
195 );
196 assert_eq!(
197 super::parse("https://discord.com/api/webhooks/456/token/slack").unwrap(),
198 (Id::new(456), Some("token")),
199 );
200 assert_eq!(
201 super::parse("https://discord.com/api/webhooks/456/token/randomsegment").unwrap(),
202 (Id::new(456), Some("token")),
203 );
204 assert_eq!(
205 super::parse("https://discord.com/api/webhooks/456/token/one/two/three").unwrap(),
206 (Id::new(456), Some("token")),
207 );
208 }
209
210 #[test]
211 fn parse_invalid() {
212 assert!(matches!(
214 super::parse("https://discord.com/foo/bar/456")
215 .unwrap_err()
216 .kind(),
217 &WebhookParseErrorType::SegmentMissing,
218 ));
219 assert!(matches!(
221 super::parse("https://discord.com/api/webhooks/")
222 .unwrap_err()
223 .kind(),
224 &WebhookParseErrorType::SegmentMissing,
225 ));
226 assert!(matches!(
228 super::parse("https://discord.com/api/webhooks/notaninteger")
229 .unwrap_err()
230 .kind(),
231 &WebhookParseErrorType::IdInvalid,
232 ));
233 }
234}