1use std::path::PathBuf;
9
10use serde::de::{Deserialize, Deserializer, Error as DeError};
11use serde::ser::{Serialize, SerializeStruct, Serializer};
12use url::Url;
13use uuid::Uuid;
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
18pub enum ImageInput {
19 Url(Url),
21 FileToken(Uuid),
23 Path(PathBuf),
25}
26
27impl ImageInput {
28 #[must_use]
33 pub fn parse(s: &str) -> Self {
34 if let Ok(url) = Url::parse(s)
35 && matches!(url.scheme(), "http" | "https")
36 {
37 return Self::Url(url);
38 }
39 if let Ok(uuid) = Uuid::parse_str(s) {
40 return Self::FileToken(uuid);
41 }
42 Self::Path(PathBuf::from(s))
43 }
44}
45
46impl Serialize for ImageInput {
47 fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
48 let mut st = ser.serialize_struct("FileContent", 2)?;
49 st.serialize_field("type", "jpg")?;
50 match self {
51 Self::Url(u) => st.serialize_field("url", u.as_str())?,
52 Self::FileToken(t) => st.serialize_field("file_token", &t.to_string())?,
53 Self::Path(p) => {
54 return Err(serde::ser::Error::custom(format!(
55 "ImageInput::Path({}) must be uploaded before serialization — call Client::upload_images on the request first",
56 p.display()
57 )));
58 }
59 }
60 st.end()
61 }
62}
63
64impl<'de> Deserialize<'de> for ImageInput {
65 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
66 let v = serde_json::Value::deserialize(d)?;
67 match v {
68 serde_json::Value::String(s) => Ok(Self::parse(&s)),
69 serde_json::Value::Object(mut m) => {
70 m.remove("type");
71 if let Some(url) = m.remove("url").and_then(|v| v.as_str().map(str::to_string)) {
72 Url::parse(&url).map(Self::Url).map_err(DeError::custom)
73 } else if let Some(tok) = m
74 .remove("file_token")
75 .and_then(|v| v.as_str().map(str::to_string))
76 {
77 Uuid::parse_str(&tok)
78 .map(Self::FileToken)
79 .map_err(DeError::custom)
80 } else {
81 Err(DeError::custom(
82 "expected `url` or `file_token` in ImageInput object",
83 ))
84 }
85 }
86 other => Err(DeError::custom(format!(
87 "unexpected ImageInput shape: {other}"
88 ))),
89 }
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn parse_url() {
99 let i = ImageInput::parse("https://example.com/x.jpg");
100 assert!(matches!(i, ImageInput::Url(_)));
101 }
102
103 #[test]
104 fn parse_file_token() {
105 let uuid = "550e8400-e29b-41d4-a716-446655440000";
106 assert!(matches!(ImageInput::parse(uuid), ImageInput::FileToken(_)));
107 }
108
109 #[test]
110 fn parse_file_token_case_insensitive() {
111 let uuid = "550E8400-E29B-41D4-A716-446655440000";
112 assert!(matches!(ImageInput::parse(uuid), ImageInput::FileToken(_)));
113 }
114
115 #[test]
116 fn parse_path() {
117 let i = ImageInput::parse("./photo.png");
118 assert!(matches!(i, ImageInput::Path(_)));
119 }
120
121 #[test]
122 fn serialize_url() {
123 let i = ImageInput::Url("https://example.com/x.jpg".parse().unwrap());
124 let got: serde_json::Value = serde_json::to_value(&i).unwrap();
125 assert_eq!(
126 got,
127 serde_json::json!({"type":"jpg","url":"https://example.com/x.jpg"})
128 );
129 }
130
131 #[test]
132 fn serialize_file_token() {
133 let t = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
134 let got: serde_json::Value = serde_json::to_value(ImageInput::FileToken(t)).unwrap();
135 assert_eq!(
136 got,
137 serde_json::json!({"type":"jpg","file_token":"550e8400-e29b-41d4-a716-446655440000"})
138 );
139 }
140
141 #[test]
142 fn serialize_path_errors() {
143 let err = serde_json::to_value(ImageInput::Path("./x.png".into())).unwrap_err();
144 assert!(err.to_string().contains("must be uploaded"));
145 }
146}