openai_interface/images/generate.rs
1//! Create an image with a model.
2//!
3//! Endpoint: `POST /images/generations` (JSON request / JSON response).
4//!
5//! > ![warn] This module is untested!
6//! > No OpenAI-compatible provider accessible to this project implements
7//! > this endpoint, and no OpenAI API key was available for testing. If you
8//! > encounter any issues, please report them on the repository.
9
10use serde::Serialize;
11use url::Url;
12
13use crate::{
14 errors::OapiError,
15 images::{Background, ImageResponseFormat, OutputFormat},
16 rest::post::{Post, PostNoStream},
17};
18
19/// Creates an image given a prompt.
20#[derive(Debug, Serialize, Default, Clone)]
21pub struct ImageGenerateRequest {
22 /// A text description of the desired image(s).
23 ///
24 /// The maximum length is 32000 characters for the GPT image models, 1000
25 /// characters for `dall-e-2` and 4000 characters for `dall-e-3`.
26 pub prompt: String,
27 /// The model to use for image generation. One of `dall-e-2`,
28 /// `dall-e-3`, or a GPT image model (`gpt-image-1`, `gpt-image-1-mini`,
29 /// `gpt-image-1.5`, `gpt-image-2`, or `gpt-image-2-2026-04-21`).
30 /// Defaults to `dall-e-2` unless a parameter specific to the GPT image
31 /// models is used.
32 #[serde(skip_serializing_if = "Option::is_none")]
33 pub model: Option<String>,
34 /// Allows to set transparency for the background of the generated
35 /// image(s). Must be one of `transparent`, `opaque`, or `auto` (default
36 /// value).
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub background: Option<Background>,
39 /// Control the content-moderation level for images generated by the GPT
40 /// image models. Must be either `low` for less restrictive filtering or
41 /// `auto` (default value).
42 #[serde(skip_serializing_if = "Option::is_none")]
43 pub moderation: Option<Moderation>,
44 /// The number of images to generate. Must be between 1 and 10. For
45 /// `dall-e-3`, only `n=1` is supported.
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub n: Option<u32>,
48 /// The compression level (0-100%) for the generated images.
49 ///
50 /// This parameter is only supported for the GPT image models with the
51 /// `webp` or `jpeg` output formats, and defaults to 100.
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub output_compression: Option<u32>,
54 /// The format in which the generated images are returned.
55 ///
56 /// This parameter is only supported for the GPT image models. Must be
57 /// one of `png`, `jpeg`, or `webp`.
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub output_format: Option<OutputFormat>,
60 /// The quality of the image that will be generated.
61 ///
62 /// - `auto` (default value) will automatically select the best quality
63 /// for the given model.
64 /// - `high`, `medium` and `low` are supported for the GPT image models.
65 /// - `hd` and `standard` are supported for `dall-e-3`.
66 /// - `standard` is the only option for `dall-e-2`.
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub quality: Option<Quality>,
69 /// The format in which generated images with `dall-e-2` and `dall-e-3`
70 /// are returned. Must be one of `url` or `b64_json`. URLs are only valid
71 /// for 60 minutes after the image has been generated. This parameter
72 /// isn't supported for the GPT image models, which always return
73 /// base64-encoded images.
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub response_format: Option<ImageResponseFormat>,
76 /// The size of the generated images.
77 ///
78 /// For the GPT image models, the standard sizes `1024x1024`,
79 /// `1536x1024`, and `1024x1536` are supported (`gpt-image-2` also
80 /// accepts arbitrary `WIDTHxHEIGHT` strings); `auto` is supported for
81 /// models that allow automatic sizing. For `dall-e-2`, use one of
82 /// `256x256`, `512x512`, or `1024x1024`. For `dall-e-3`, use one of
83 /// `1024x1024`, `1792x1024`, or `1024x1792`.
84 #[serde(skip_serializing_if = "Option::is_none")]
85 pub size: Option<String>,
86 /// The style of the generated images.
87 ///
88 /// This parameter is only supported for `dall-e-3`. Must be one of
89 /// `vivid` or `natural`.
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub style: Option<Style>,
92 /// A unique identifier representing your end-user, which can help OpenAI
93 /// to monitor and detect abuse.
94 #[serde(skip_serializing_if = "Option::is_none")]
95 pub user: Option<String>,
96}
97
98/// Control the content-moderation level for images generated by the GPT
99/// image models.
100#[derive(Debug, Serialize, Clone, Copy)]
101#[serde(rename_all = "snake_case")]
102pub enum Moderation {
103 Low,
104 Auto,
105}
106
107/// The quality of the image that will be generated.
108#[derive(Debug, Serialize, Clone, Copy)]
109#[serde(rename_all = "snake_case")]
110pub enum Quality {
111 Standard,
112 Hd,
113 Low,
114 Medium,
115 High,
116 Auto,
117}
118
119/// The style of the generated images. Only supported for `dall-e-3`.
120#[derive(Debug, Serialize, Clone, Copy)]
121#[serde(rename_all = "snake_case")]
122pub enum Style {
123 Vivid,
124 Natural,
125}
126
127impl Post for ImageGenerateRequest {
128 #[inline]
129 fn is_streaming(&self) -> bool {
130 false
131 }
132
133 /// Builds the URL for the request.
134 ///
135 /// `base_url` should be like <https://api.openai.com/v1>
136 fn build_url(&self, base_url: &str) -> Result<String, OapiError> {
137 let mut url = Url::parse(base_url.trim_end_matches('/')).map_err(OapiError::UrlError)?;
138 url.path_segments_mut()
139 .map_err(|_| OapiError::UrlCannotBeBase(base_url.to_string()))?
140 .push("images")
141 .push("generations");
142
143 Ok(url.to_string())
144 }
145}
146
147impl PostNoStream for ImageGenerateRequest {
148 type Response = crate::images::ImagesResponse;
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 /// Serializes a `dall-e-3` generation request.
156 #[test]
157 fn dall_e_3_serialization() {
158 let request = ImageGenerateRequest {
159 prompt: "A nebula painted in watercolor".to_string(),
160 model: Some("dall-e-3".to_string()),
161 n: Some(1),
162 quality: Some(Quality::Hd),
163 response_format: Some(ImageResponseFormat::Url),
164 size: Some("1792x1024".to_string()),
165 style: Some(Style::Vivid),
166 ..Default::default()
167 };
168
169 let json = serde_json::to_string(&request).unwrap();
170 assert!(
171 json.contains(r#""prompt":"A nebula painted in watercolor""#),
172 "json: {json}"
173 );
174 assert!(json.contains(r#""model":"dall-e-3""#), "json: {json}");
175 assert!(json.contains(r#""quality":"hd""#), "json: {json}");
176 assert!(json.contains(r#""response_format":"url""#), "json: {json}");
177 assert!(json.contains(r#""size":"1792x1024""#), "json: {json}");
178 assert!(json.contains(r#""style":"vivid""#), "json: {json}");
179 // Optional fields must be omitted entirely.
180 assert!(!json.contains(r#""background""#), "json: {json}");
181 assert!(!json.contains(r#""moderation""#), "json: {json}");
182 assert!(!json.contains(r#""user""#), "json: {json}");
183 }
184
185 /// Serializes a GPT image model generation request, including the
186 /// `b64_json` response format naming.
187 #[test]
188 fn gpt_image_serialization() {
189 let request = ImageGenerateRequest {
190 prompt: "A painted nebula".to_string(),
191 model: Some("gpt-image-1".to_string()),
192 background: Some(Background::Transparent),
193 moderation: Some(Moderation::Low),
194 output_compression: Some(80),
195 output_format: Some(OutputFormat::Webp),
196 ..Default::default()
197 };
198
199 let json = serde_json::to_string(&request).unwrap();
200 assert!(
201 json.contains(r#""background":"transparent""#),
202 "json: {json}"
203 );
204 assert!(json.contains(r#""moderation":"low""#), "json: {json}");
205 assert!(json.contains(r#""output_compression":80"#), "json: {json}");
206 assert!(json.contains(r#""output_format":"webp""#), "json: {json}");
207 }
208
209 #[test]
210 fn test_build_url() {
211 let request = ImageGenerateRequest::default();
212 let url = request.build_url("https://api.openai.com/v1/").unwrap();
213 assert_eq!(url, "https://api.openai.com/v1/images/generations");
214 }
215}