twilight_util/builder/embed/
image_source.rs1use std::{
4 error::Error,
5 fmt::{Display, Formatter, Result as FmtResult},
6};
7
8#[derive(Debug)]
10pub struct ImageSourceAttachmentError {
11 kind: ImageSourceAttachmentErrorType,
12}
13
14impl ImageSourceAttachmentError {
15 #[must_use = "retrieving the type has no effect if left unused"]
17 pub const fn kind(&self) -> &ImageSourceAttachmentErrorType {
18 &self.kind
19 }
20
21 #[allow(clippy::unused_self)]
23 #[must_use = "consuming the error and retrieving the source has no effect if left unused"]
24 pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync>> {
25 None
26 }
27
28 #[must_use = "consuming the error into its parts has no effect if left unused"]
30 pub fn into_parts(
31 self,
32 ) -> (
33 ImageSourceAttachmentErrorType,
34 Option<Box<dyn Error + Send + Sync>>,
35 ) {
36 (self.kind, None)
37 }
38}
39
40impl Display for ImageSourceAttachmentError {
41 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
42 match &self.kind {
43 ImageSourceAttachmentErrorType::ExtensionEmpty => f.write_str("the extension is empty"),
44 ImageSourceAttachmentErrorType::ExtensionMissing => {
45 f.write_str("the extension is missing")
46 }
47 }
48 }
49}
50
51impl Error for ImageSourceAttachmentError {}
52
53#[derive(Debug)]
55#[non_exhaustive]
56pub enum ImageSourceAttachmentErrorType {
57 ExtensionEmpty,
59 ExtensionMissing,
61}
62
63#[derive(Debug)]
65pub struct ImageSourceUrlError {
66 kind: ImageSourceUrlErrorType,
67}
68
69impl ImageSourceUrlError {
70 #[must_use = "retrieving the type has no effect if left unused"]
72 pub const fn kind(&self) -> &ImageSourceUrlErrorType {
73 &self.kind
74 }
75
76 #[allow(clippy::unused_self)]
78 #[must_use = "consuming the error and retrieving the source has no effect if left unused"]
79 pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync>> {
80 None
81 }
82
83 #[must_use = "consuming the error into its parts has no effect if left unused"]
85 pub fn into_parts(
86 self,
87 ) -> (
88 ImageSourceUrlErrorType,
89 Option<Box<dyn Error + Send + Sync>>,
90 ) {
91 (self.kind, None)
92 }
93}
94
95impl Display for ImageSourceUrlError {
96 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
97 match &self.kind {
98 ImageSourceUrlErrorType::ProtocolUnsupported { .. } => {
99 f.write_str("the provided URL's protocol is unsupported by Discord")
100 }
101 }
102 }
103}
104
105impl Error for ImageSourceUrlError {}
106
107#[derive(Debug)]
109#[non_exhaustive]
110pub enum ImageSourceUrlErrorType {
111 ProtocolUnsupported {
115 url: String,
117 },
118}
119
120#[derive(Clone, Debug, Eq, PartialEq)]
122#[non_exhaustive]
123pub struct ImageSource(pub(super) String);
124
125impl ImageSource {
126 pub fn attachment(filename: impl AsRef<str>) -> Result<Self, ImageSourceAttachmentError> {
138 let filename = filename.as_ref();
139
140 let dot = filename.rfind('.').ok_or(ImageSourceAttachmentError {
141 kind: ImageSourceAttachmentErrorType::ExtensionMissing,
142 })? + 1;
143
144 if filename
145 .get(dot..)
146 .ok_or(ImageSourceAttachmentError {
147 kind: ImageSourceAttachmentErrorType::ExtensionMissing,
148 })?
149 .is_empty()
150 {
151 return Err(ImageSourceAttachmentError {
152 kind: ImageSourceAttachmentErrorType::ExtensionEmpty,
153 });
154 }
155
156 Ok(Self(format!("attachment://{filename}")))
157 }
158
159 pub fn url(url: impl Into<String>) -> Result<Self, ImageSourceUrlError> {
171 let url = url.into();
172
173 if !url.starts_with("https:") && !url.starts_with("http:") {
174 return Err(ImageSourceUrlError {
175 kind: ImageSourceUrlErrorType::ProtocolUnsupported { url },
176 });
177 }
178
179 Ok(Self(url))
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use static_assertions::{assert_fields, assert_impl_all};
187 use std::fmt::Debug;
188
189 assert_impl_all!(ImageSourceAttachmentErrorType: Debug, Send, Sync);
190 assert_impl_all!(ImageSourceAttachmentError: Error, Send, Sync);
191 assert_impl_all!(ImageSourceUrlErrorType: Debug, Send, Sync);
192 assert_impl_all!(ImageSourceUrlError: Error, Send, Sync);
193 assert_fields!(ImageSourceUrlErrorType::ProtocolUnsupported: url);
194 assert_impl_all!(ImageSource: Clone, Debug, Eq, PartialEq, Send, Sync);
195
196 #[test]
197 fn attachment() -> Result<(), Box<dyn Error>> {
198 assert!(matches!(
199 ImageSource::attachment("abc").unwrap_err().kind(),
200 ImageSourceAttachmentErrorType::ExtensionMissing
201 ));
202 assert!(matches!(
203 ImageSource::attachment("abc.").unwrap_err().kind(),
204 ImageSourceAttachmentErrorType::ExtensionEmpty
205 ));
206 assert_eq!(
207 ImageSource::attachment("abc.png")?,
208 ImageSource("attachment://abc.png".to_owned()),
209 );
210
211 Ok(())
212 }
213
214 #[test]
215 fn url() -> Result<(), Box<dyn Error>> {
216 assert!(matches!(
217 ImageSource::url("ftp://example.com/foo").unwrap_err().kind(),
218 ImageSourceUrlErrorType::ProtocolUnsupported { url }
219 if url == "ftp://example.com/foo"
220 ));
221 assert_eq!(
222 ImageSource::url("https://example.com")?,
223 ImageSource("https://example.com".to_owned()),
224 );
225 assert_eq!(
226 ImageSource::url("http://example.com")?,
227 ImageSource("http://example.com".to_owned()),
228 );
229
230 Ok(())
231 }
232}