use std::path::PathBuf;
use serde::de::{Deserialize, Deserializer, Error as DeError};
use serde::ser::{Serialize, SerializeStruct, Serializer};
use url::Url;
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum ImageInput {
Url(Url),
FileToken(Uuid),
Path(PathBuf),
}
impl ImageInput {
#[must_use]
pub fn parse(s: &str) -> Self {
if let Ok(url) = Url::parse(s)
&& matches!(url.scheme(), "http" | "https")
{
return Self::Url(url);
}
if let Ok(uuid) = Uuid::parse_str(s) {
return Self::FileToken(uuid);
}
Self::Path(PathBuf::from(s))
}
}
impl Serialize for ImageInput {
fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
let mut st = ser.serialize_struct("FileContent", 2)?;
st.serialize_field("type", "jpg")?;
match self {
Self::Url(u) => st.serialize_field("url", u.as_str())?,
Self::FileToken(t) => st.serialize_field("file_token", &t.to_string())?,
Self::Path(p) => {
return Err(serde::ser::Error::custom(format!(
"ImageInput::Path({}) must be uploaded before serialization — call Client::upload_images on the request first",
p.display()
)));
}
}
st.end()
}
}
impl<'de> Deserialize<'de> for ImageInput {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let v = serde_json::Value::deserialize(d)?;
match v {
serde_json::Value::String(s) => Ok(Self::parse(&s)),
serde_json::Value::Object(mut m) => {
m.remove("type");
if let Some(url) = m.remove("url").and_then(|v| v.as_str().map(str::to_string)) {
Url::parse(&url).map(Self::Url).map_err(DeError::custom)
} else if let Some(tok) = m
.remove("file_token")
.and_then(|v| v.as_str().map(str::to_string))
{
Uuid::parse_str(&tok)
.map(Self::FileToken)
.map_err(DeError::custom)
} else {
Err(DeError::custom(
"expected `url` or `file_token` in ImageInput object",
))
}
}
other => Err(DeError::custom(format!(
"unexpected ImageInput shape: {other}"
))),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_url() {
let i = ImageInput::parse("https://example.com/x.jpg");
assert!(matches!(i, ImageInput::Url(_)));
}
#[test]
fn parse_file_token() {
let uuid = "550e8400-e29b-41d4-a716-446655440000";
assert!(matches!(ImageInput::parse(uuid), ImageInput::FileToken(_)));
}
#[test]
fn parse_file_token_case_insensitive() {
let uuid = "550E8400-E29B-41D4-A716-446655440000";
assert!(matches!(ImageInput::parse(uuid), ImageInput::FileToken(_)));
}
#[test]
fn parse_path() {
let i = ImageInput::parse("./photo.png");
assert!(matches!(i, ImageInput::Path(_)));
}
#[test]
fn serialize_url() {
let i = ImageInput::Url("https://example.com/x.jpg".parse().unwrap());
let got: serde_json::Value = serde_json::to_value(&i).unwrap();
assert_eq!(
got,
serde_json::json!({"type":"jpg","url":"https://example.com/x.jpg"})
);
}
#[test]
fn serialize_file_token() {
let t = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
let got: serde_json::Value = serde_json::to_value(ImageInput::FileToken(t)).unwrap();
assert_eq!(
got,
serde_json::json!({"type":"jpg","file_token":"550e8400-e29b-41d4-a716-446655440000"})
);
}
#[test]
fn serialize_path_errors() {
let err = serde_json::to_value(ImageInput::Path("./x.png".into())).unwrap_err();
assert!(err.to_string().contains("must be uploaded"));
}
}