openai-interface 0.10.0

A low-level Rust interface for the OpenAI API
Documentation
//! Create an image with a model.
//!
//! Endpoint: `POST /images/generations` (JSON request / JSON response).
//!
//! > ![warn] This module is untested!
//! > No OpenAI-compatible provider accessible to this project implements
//! > this endpoint, and no OpenAI API key was available for testing. If you
//! > encounter any issues, please report them on the repository.

use serde::Serialize;
use url::Url;

use crate::{
    errors::OapiError,
    images::{Background, ImageResponseFormat, OutputFormat},
    rest::post::{Post, PostNoStream},
};

/// Creates an image given a prompt.
#[derive(Debug, Serialize, Default, Clone)]
pub struct ImageGenerateRequest {
    /// A text description of the desired image(s).
    ///
    /// The maximum length is 32000 characters for the GPT image models, 1000
    /// characters for `dall-e-2` and 4000 characters for `dall-e-3`.
    pub prompt: String,
    /// The model to use for image generation. One of `dall-e-2`,
    /// `dall-e-3`, or a GPT image model (`gpt-image-1`, `gpt-image-1-mini`,
    /// `gpt-image-1.5`, `gpt-image-2`, or `gpt-image-2-2026-04-21`).
    /// Defaults to `dall-e-2` unless a parameter specific to the GPT image
    /// models is used.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub model: Option<String>,
    /// Allows to set transparency for the background of the generated
    /// image(s). Must be one of `transparent`, `opaque`, or `auto` (default
    /// value).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub background: Option<Background>,
    /// Control the content-moderation level for images generated by the GPT
    /// image models. Must be either `low` for less restrictive filtering or
    /// `auto` (default value).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub moderation: Option<Moderation>,
    /// The number of images to generate. Must be between 1 and 10. For
    /// `dall-e-3`, only `n=1` is supported.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub n: Option<u32>,
    /// The compression level (0-100%) for the generated images.
    ///
    /// This parameter is only supported for the GPT image models with the
    /// `webp` or `jpeg` output formats, and defaults to 100.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub output_compression: Option<u32>,
    /// The format in which the generated images are returned.
    ///
    /// This parameter is only supported for the GPT image models. Must be
    /// one of `png`, `jpeg`, or `webp`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub output_format: Option<OutputFormat>,
    /// The quality of the image that will be generated.
    ///
    /// - `auto` (default value) will automatically select the best quality
    ///   for the given model.
    /// - `high`, `medium` and `low` are supported for the GPT image models.
    /// - `hd` and `standard` are supported for `dall-e-3`.
    /// - `standard` is the only option for `dall-e-2`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub quality: Option<Quality>,
    /// The format in which generated images with `dall-e-2` and `dall-e-3`
    /// are returned. Must be one of `url` or `b64_json`. URLs are only valid
    /// for 60 minutes after the image has been generated. This parameter
    /// isn't supported for the GPT image models, which always return
    /// base64-encoded images.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub response_format: Option<ImageResponseFormat>,
    /// The size of the generated images.
    ///
    /// For the GPT image models, the standard sizes `1024x1024`,
    /// `1536x1024`, and `1024x1536` are supported (`gpt-image-2` also
    /// accepts arbitrary `WIDTHxHEIGHT` strings); `auto` is supported for
    /// models that allow automatic sizing. For `dall-e-2`, use one of
    /// `256x256`, `512x512`, or `1024x1024`. For `dall-e-3`, use one of
    /// `1024x1024`, `1792x1024`, or `1024x1792`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub size: Option<String>,
    /// The style of the generated images.
    ///
    /// This parameter is only supported for `dall-e-3`. Must be one of
    /// `vivid` or `natural`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub style: Option<Style>,
    /// A unique identifier representing your end-user, which can help OpenAI
    /// to monitor and detect abuse.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub user: Option<String>,
}

/// Control the content-moderation level for images generated by the GPT
/// image models.
#[derive(Debug, Serialize, Clone, Copy)]
#[serde(rename_all = "snake_case")]
pub enum Moderation {
    Low,
    Auto,
}

/// The quality of the image that will be generated.
#[derive(Debug, Serialize, Clone, Copy)]
#[serde(rename_all = "snake_case")]
pub enum Quality {
    Standard,
    Hd,
    Low,
    Medium,
    High,
    Auto,
}

/// The style of the generated images. Only supported for `dall-e-3`.
#[derive(Debug, Serialize, Clone, Copy)]
#[serde(rename_all = "snake_case")]
pub enum Style {
    Vivid,
    Natural,
}

impl Post for ImageGenerateRequest {
    #[inline]
    fn is_streaming(&self) -> bool {
        false
    }

    /// Builds the URL for the request.
    ///
    /// `base_url` should be like <https://api.openai.com/v1>
    fn build_url(&self, base_url: &str) -> Result<String, OapiError> {
        let mut url = Url::parse(base_url.trim_end_matches('/')).map_err(OapiError::UrlError)?;
        url.path_segments_mut()
            .map_err(|_| OapiError::UrlCannotBeBase(base_url.to_string()))?
            .push("images")
            .push("generations");

        Ok(url.to_string())
    }
}

impl PostNoStream for ImageGenerateRequest {
    type Response = crate::images::ImagesResponse;
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Serializes a `dall-e-3` generation request.
    #[test]
    fn dall_e_3_serialization() {
        let request = ImageGenerateRequest {
            prompt: "A nebula painted in watercolor".to_string(),
            model: Some("dall-e-3".to_string()),
            n: Some(1),
            quality: Some(Quality::Hd),
            response_format: Some(ImageResponseFormat::Url),
            size: Some("1792x1024".to_string()),
            style: Some(Style::Vivid),
            ..Default::default()
        };

        let json = serde_json::to_string(&request).unwrap();
        assert!(
            json.contains(r#""prompt":"A nebula painted in watercolor""#),
            "json: {json}"
        );
        assert!(json.contains(r#""model":"dall-e-3""#), "json: {json}");
        assert!(json.contains(r#""quality":"hd""#), "json: {json}");
        assert!(json.contains(r#""response_format":"url""#), "json: {json}");
        assert!(json.contains(r#""size":"1792x1024""#), "json: {json}");
        assert!(json.contains(r#""style":"vivid""#), "json: {json}");
        // Optional fields must be omitted entirely.
        assert!(!json.contains(r#""background""#), "json: {json}");
        assert!(!json.contains(r#""moderation""#), "json: {json}");
        assert!(!json.contains(r#""user""#), "json: {json}");
    }

    /// Serializes a GPT image model generation request, including the
    /// `b64_json` response format naming.
    #[test]
    fn gpt_image_serialization() {
        let request = ImageGenerateRequest {
            prompt: "A painted nebula".to_string(),
            model: Some("gpt-image-1".to_string()),
            background: Some(Background::Transparent),
            moderation: Some(Moderation::Low),
            output_compression: Some(80),
            output_format: Some(OutputFormat::Webp),
            ..Default::default()
        };

        let json = serde_json::to_string(&request).unwrap();
        assert!(
            json.contains(r#""background":"transparent""#),
            "json: {json}"
        );
        assert!(json.contains(r#""moderation":"low""#), "json: {json}");
        assert!(json.contains(r#""output_compression":80"#), "json: {json}");
        assert!(json.contains(r#""output_format":"webp""#), "json: {json}");
    }

    #[test]
    fn test_build_url() {
        let request = ImageGenerateRequest::default();
        let url = request.build_url("https://api.openai.com/v1/").unwrap();
        assert_eq!(url, "https://api.openai.com/v1/images/generations");
    }
}