Skip to main content

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::{Deserialize, 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, Deserialize, 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, Deserialize, 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, Deserialize, 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, Deserialize, 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}