Skip to main content

gproxy_protocol/protocol/openai/images/
requests.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize, de, de::DeserializeOwned};
4use serde_json::{Map, Value};
5
6use super::super::common::*;
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9pub struct ImageGenerationRequest {
10    pub prompt: String,
11    #[serde(skip_serializing_if = "Option::is_none")]
12    pub background: Option<ImageBackground>,
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub model: Option<OpenAiModelId>,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub moderation: Option<ImageModeration>,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub n: Option<u32>,
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub output_compression: Option<u32>,
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub output_format: Option<ImageOutputFormat>,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub partial_images: Option<u32>,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub quality: Option<ImageQuality>,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub response_format: Option<ImageResponseFormat>,
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub size: Option<ImageSize>,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub stream: Option<bool>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub style: Option<ImageStyle>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub user: Option<String>,
37    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
38    pub extra: Extra,
39}
40
41#[derive(Debug, Clone, PartialEq, Serialize)]
42pub struct ImageEditRequest {
43    pub images: Vec<ImageReference>,
44    pub prompt: String,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub background: Option<ImageBackground>,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub input_fidelity: Option<ImageInputFidelity>,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub mask: Option<ImageReference>,
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub model: Option<OpenAiModelId>,
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub moderation: Option<ImageModeration>,
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub n: Option<u32>,
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub output_compression: Option<u32>,
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub output_format: Option<ImageOutputFormat>,
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub partial_images: Option<u32>,
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub quality: Option<ImageEditQuality>,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub size: Option<ImageEditSize>,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub stream: Option<bool>,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub user: Option<String>,
71    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
72    pub extra: Extra,
73}
74
75impl<'de> Deserialize<'de> for ImageEditRequest {
76    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
77    where
78        D: serde::Deserializer<'de>,
79    {
80        let value = Value::deserialize(deserializer)?;
81        let Value::Object(mut map) = value else {
82            return Err(de::Error::custom("image edit request must be an object"));
83        };
84
85        Ok(Self {
86            images: take_image_references(&mut map).map_err(de::Error::custom)?,
87            prompt: take_required(&mut map, "prompt").map_err(de::Error::custom)?,
88            background: take_optional(&mut map, "background").map_err(de::Error::custom)?,
89            input_fidelity: take_optional(&mut map, "input_fidelity").map_err(de::Error::custom)?,
90            mask: take_optional_image_reference(&mut map, "mask").map_err(de::Error::custom)?,
91            model: take_optional(&mut map, "model").map_err(de::Error::custom)?,
92            moderation: take_optional(&mut map, "moderation").map_err(de::Error::custom)?,
93            n: take_optional_u32(&mut map, "n").map_err(de::Error::custom)?,
94            output_compression: take_optional_u32(&mut map, "output_compression")
95                .map_err(de::Error::custom)?,
96            output_format: take_optional(&mut map, "output_format").map_err(de::Error::custom)?,
97            partial_images: take_optional_u32(&mut map, "partial_images")
98                .map_err(de::Error::custom)?,
99            quality: take_optional(&mut map, "quality").map_err(de::Error::custom)?,
100            size: take_optional(&mut map, "size").map_err(de::Error::custom)?,
101            stream: take_optional_bool(&mut map, "stream").map_err(de::Error::custom)?,
102            user: take_optional(&mut map, "user").map_err(de::Error::custom)?,
103            extra: map.into_iter().collect(),
104        })
105    }
106}
107
108#[derive(Debug, Clone, PartialEq, Serialize)]
109pub struct ImageReference {
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub file_id: Option<String>,
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub image_url: Option<String>,
114    #[serde(default, flatten, skip_serializing_if = "BTreeMap::is_empty")]
115    pub extra: Extra,
116}
117
118impl<'de> Deserialize<'de> for ImageReference {
119    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
120    where
121        D: serde::Deserializer<'de>,
122    {
123        let value = Value::deserialize(deserializer)?;
124        if let Value::String(value) = value {
125            return string_image_reference(value).map_err(de::Error::custom);
126        }
127
128        #[derive(Deserialize)]
129        struct RawImageReference {
130            file_id: Option<String>,
131            image_url: Option<String>,
132            #[serde(default, flatten)]
133            extra: Extra,
134        }
135
136        let raw: RawImageReference = serde_json::from_value(value).map_err(de::Error::custom)?;
137        match (raw.file_id.is_some(), raw.image_url.is_some()) {
138            (true, false) | (false, true) => Ok(Self {
139                file_id: raw.file_id,
140                image_url: raw.image_url,
141                extra: raw.extra,
142            }),
143            (true, true) => Err(de::Error::custom(
144                "image reference must contain exactly one of file_id or image_url",
145            )),
146            (false, false) => Err(de::Error::custom(
147                "image reference must contain file_id or image_url",
148            )),
149        }
150    }
151}
152
153fn take_required<T: DeserializeOwned>(
154    map: &mut Map<String, Value>,
155    key: &str,
156) -> Result<T, String> {
157    let Some(value) = map.remove(key) else {
158        return Err(format!("missing required field `{key}`"));
159    };
160    serde_json::from_value(value).map_err(|e| format!("{key}: {e}"))
161}
162
163fn take_optional<T: DeserializeOwned>(
164    map: &mut Map<String, Value>,
165    key: &str,
166) -> Result<Option<T>, String> {
167    match map.remove(key) {
168        Some(Value::Null) | None => Ok(None),
169        Some(value) => serde_json::from_value(value)
170            .map(Some)
171            .map_err(|e| format!("{key}: {e}")),
172    }
173}
174
175fn take_optional_u32(map: &mut Map<String, Value>, key: &str) -> Result<Option<u32>, String> {
176    match map.remove(key) {
177        Some(Value::Null) | None => Ok(None),
178        Some(Value::String(value)) => value
179            .parse::<u32>()
180            .map(Some)
181            .map_err(|e| format!("{key}: {e}")),
182        Some(value) => serde_json::from_value(value)
183            .map(Some)
184            .map_err(|e| format!("{key}: {e}")),
185    }
186}
187
188fn take_optional_bool(map: &mut Map<String, Value>, key: &str) -> Result<Option<bool>, String> {
189    match map.remove(key) {
190        Some(Value::Null) | None => Ok(None),
191        Some(Value::String(value)) => value
192            .parse::<bool>()
193            .map(Some)
194            .map_err(|e| format!("{key}: {e}")),
195        Some(value) => serde_json::from_value(value)
196            .map(Some)
197            .map_err(|e| format!("{key}: {e}")),
198    }
199}
200
201fn take_image_references(map: &mut Map<String, Value>) -> Result<Vec<ImageReference>, String> {
202    let mut images = Vec::new();
203    if let Some(value) = map.remove("image") {
204        images.extend(image_references_from_value(value)?);
205    }
206    if let Some(value) = map.remove("images") {
207        images.extend(image_references_from_value(value)?);
208    }
209    if images.is_empty() {
210        return Err("missing required field `images`".to_owned());
211    }
212    Ok(images)
213}
214
215fn take_optional_image_reference(
216    map: &mut Map<String, Value>,
217    key: &str,
218) -> Result<Option<ImageReference>, String> {
219    match map.remove(key) {
220        Some(Value::Null) | None => Ok(None),
221        Some(value) => serde_json::from_value(value)
222            .map(Some)
223            .map_err(|e| format!("{key}: {e}")),
224    }
225}
226
227fn image_references_from_value(value: Value) -> Result<Vec<ImageReference>, String> {
228    match value {
229        Value::Array(values) => values
230            .into_iter()
231            .map(|value| serde_json::from_value(value).map_err(|e| e.to_string()))
232            .collect(),
233        value => serde_json::from_value(value)
234            .map(|reference| vec![reference])
235            .map_err(|e| e.to_string()),
236    }
237}
238
239fn string_image_reference(value: String) -> Result<ImageReference, String> {
240    if value.trim().is_empty() {
241        return Err("image reference string must not be empty".to_owned());
242    }
243    if value.starts_with("http://") || value.starts_with("https://") || value.starts_with("data:") {
244        Ok(ImageReference {
245            file_id: None,
246            image_url: Some(value),
247            extra: Default::default(),
248        })
249    } else {
250        Ok(ImageReference {
251            file_id: Some(value),
252            image_url: None,
253            extra: Default::default(),
254        })
255    }
256}