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