zai_rs/model/gen_image/
image_response.rs1use serde::{Deserialize, Deserializer, Serialize, de::Error as _};
2
3#[derive(Debug, Clone, Serialize)]
8pub struct ImageResponse {
9 #[serde(skip_serializing_if = "Option::is_none")]
11 pub created: Option<u64>,
12
13 #[serde(skip_serializing_if = "Option::is_none")]
15 pub data: Option<Vec<ImageDataItem>>,
16
17 #[serde(skip_serializing_if = "Option::is_none")]
19 pub content_filter: Option<Vec<ImageContentFilterInfo>>,
20}
21
22#[derive(Deserialize)]
23struct ImageResponseWire {
24 created: Option<u64>,
25 data: Option<Vec<ImageDataItem>>,
26 content_filter: Option<Vec<ImageContentFilterInfo>>,
27}
28
29impl<'de> Deserialize<'de> for ImageResponse {
30 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
31 where
32 D: Deserializer<'de>,
33 {
34 let wire = ImageResponseWire::deserialize(deserializer)?;
35 if wire.created.is_none() && wire.data.is_none() && wire.content_filter.is_none() {
36 return Err(D::Error::custom(
37 "image response contained no documented fields",
38 ));
39 }
40 Ok(Self {
41 created: wire.created,
42 data: wire.data,
43 content_filter: wire.content_filter,
44 })
45 }
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct ImageDataItem {
51 pub url: String,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct ImageContentFilterInfo {
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub role: Option<ImageContentFilterRole>,
61 #[serde(
63 default,
64 skip_serializing_if = "Option::is_none",
65 deserialize_with = "deserialize_optional_filter_level"
66 )]
67 pub level: Option<i32>,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
72#[serde(rename_all = "lowercase")]
73pub enum ImageContentFilterRole {
74 Assistant,
76 User,
78 History,
80}
81
82fn deserialize_optional_filter_level<'de, D>(deserializer: D) -> Result<Option<i32>, D::Error>
83where
84 D: Deserializer<'de>,
85{
86 let level = Option::<i32>::deserialize(deserializer)?;
87 if level.is_some_and(|level| !(0..=3).contains(&level)) {
88 return Err(D::Error::custom(
89 "image content-filter level must be between 0 and 3",
90 ));
91 }
92 Ok(level)
93}
94
95impl ImageResponse {
97 pub fn created(&self) -> Option<u64> {
99 self.created
100 }
101 pub fn data(&self) -> Option<&[ImageDataItem]> {
103 self.data.as_deref()
104 }
105 pub fn content_filter(&self) -> Option<&[ImageContentFilterInfo]> {
107 self.content_filter.as_deref()
108 }
109}
110
111impl ImageDataItem {
112 pub fn url(&self) -> &str {
114 &self.url
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn response_requires_one_documented_non_null_field() {
124 assert!(serde_json::from_str::<ImageResponse>("{}").is_err());
125 assert!(serde_json::from_str::<ImageResponse>(r#"{"data":null}"#).is_err());
126 assert!(serde_json::from_str::<ImageResponse>(r#"{"data":[]}"#).is_ok());
127 }
128
129 #[test]
130 fn image_url_is_required_and_filter_constraints_are_enforced() {
131 assert!(serde_json::from_str::<ImageDataItem>("{}").is_err());
132 assert!(serde_json::from_str::<ImageDataItem>(r#"{"url":"relative"}"#).is_ok());
133 assert!(
134 serde_json::from_str::<ImageContentFilterInfo>(r#"{"role":"assistant","level":3}"#)
135 .is_ok()
136 );
137 assert!(serde_json::from_str::<ImageContentFilterInfo>(r#"{"level":4}"#).is_err());
138 assert!(serde_json::from_str::<ImageContentFilterInfo>(r#"{"role":"future"}"#).is_err());
139 }
140}