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 { .. } => {
44 f.write_str("the extension is empty")
45 }
46 ImageSourceAttachmentErrorType::ExtensionMissing { .. } => {
47 f.write_str("the extension is missing")
48 }
49 }
50 }
51}
52
53impl Error for ImageSourceAttachmentError {}
54
55#[derive(Debug)]
57#[non_exhaustive]
58pub enum ImageSourceAttachmentErrorType {
59 ExtensionEmpty,
61 ExtensionMissing,
63}
64
65#[derive(Debug)]
67pub struct ImageSourceUrlError {
68 kind: ImageSourceUrlErrorType,
69}
70
71impl ImageSourceUrlError {
72 #[must_use = "retrieving the type has no effect if left unused"]
74 pub const fn kind(&self) -> &ImageSourceUrlErrorType {
75 &self.kind
76 }
77
78 #[allow(clippy::unused_self)]
80 #[must_use = "consuming the error and retrieving the source has no effect if left unused"]
81 pub fn into_source(self) -> Option<Box<dyn Error + Send + Sync>> {
82 None
83 }
84
85 #[must_use = "consuming the error into its parts has no effect if left unused"]
87 pub fn into_parts(
88 self,
89 ) -> (
90 ImageSourceUrlErrorType,
91 Option<Box<dyn Error + Send + Sync>>,
92 ) {
93 (self.kind, None)
94 }
95}
96
97impl Display for ImageSourceUrlError {
98 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
99 match &self.kind {
100 ImageSourceUrlErrorType::ProtocolUnsupported { .. } => {
101 f.write_str("the provided URL's protocol is unsupported by Discord")
102 }
103 }
104 }
105}
106
107impl Error for ImageSourceUrlError {}
108
109#[derive(Debug)]
111#[non_exhaustive]
112pub enum ImageSourceUrlErrorType {
113 ProtocolUnsupported {
117 url: String,
119 },
120}
121
122#[derive(Clone, Debug, Eq, PartialEq)]
124#[non_exhaustive]
125pub struct ImageSource(pub(super) String);
126
127impl ImageSource {
128 pub fn attachment(filename: impl AsRef<str>) -> Result<Self, ImageSourceAttachmentError> {
140 let filename = filename.as_ref();
141
142 let dot = filename.rfind('.').ok_or(ImageSourceAttachmentError {
143 kind: ImageSourceAttachmentErrorType::ExtensionMissing,
144 })? + 1;
145
146 if filename
147 .get(dot..)
148 .ok_or(ImageSourceAttachmentError {
149 kind: ImageSourceAttachmentErrorType::ExtensionMissing,
150 })?
151 .is_empty()
152 {
153 return Err(ImageSourceAttachmentError {
154 kind: ImageSourceAttachmentErrorType::ExtensionEmpty,
155 });
156 }
157
158 Ok(Self(format!("attachment://{filename}")))
159 }
160
161 pub fn url(url: impl Into<String>) -> Result<Self, ImageSourceUrlError> {
173 let url = url.into();
174
175 if !url.starts_with("https:") && !url.starts_with("http:") {
176 return Err(ImageSourceUrlError {
177 kind: ImageSourceUrlErrorType::ProtocolUnsupported { url },
178 });
179 }
180
181 Ok(Self(url))
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188 use static_assertions::{assert_fields, assert_impl_all};
189 use std::fmt::Debug;
190
191 assert_impl_all!(ImageSourceAttachmentErrorType: Debug, Send, Sync);
192 assert_impl_all!(ImageSourceAttachmentError: Error, Send, Sync);
193 assert_impl_all!(ImageSourceUrlErrorType: Debug, Send, Sync);
194 assert_impl_all!(ImageSourceUrlError: Error, Send, Sync);
195 assert_fields!(ImageSourceUrlErrorType::ProtocolUnsupported: url);
196 assert_impl_all!(ImageSource: Clone, Debug, Eq, PartialEq, Send, Sync);
197
198 #[test]
199 fn attachment() -> Result<(), Box<dyn Error>> {
200 assert!(matches!(
201 ImageSource::attachment("abc").unwrap_err().kind(),
202 ImageSourceAttachmentErrorType::ExtensionMissing
203 ));
204 assert!(matches!(
205 ImageSource::attachment("abc.").unwrap_err().kind(),
206 ImageSourceAttachmentErrorType::ExtensionEmpty
207 ));
208 assert_eq!(
209 ImageSource::attachment("abc.png")?,
210 ImageSource("attachment://abc.png".to_owned()),
211 );
212
213 Ok(())
214 }
215
216 #[test]
217 fn url() -> Result<(), Box<dyn Error>> {
218 assert!(matches!(
219 ImageSource::url("ftp://example.com/foo").unwrap_err().kind(),
220 ImageSourceUrlErrorType::ProtocolUnsupported { url }
221 if url == "ftp://example.com/foo"
222 ));
223 assert_eq!(
224 ImageSource::url("https://example.com")?,
225 ImageSource("https://example.com".to_owned()),
226 );
227 assert_eq!(
228 ImageSource::url("http://example.com")?,
229 ImageSource("http://example.com".to_owned()),
230 );
231
232 Ok(())
233 }
234}