openai-interface 0.6.0

A low-level Rust interface for the OpenAI API
Documentation
//! List the currently available models, and provide basic information about
//! each one such as the owner and availability.
//!
//! The list is tested against the DeepSeek `GET /models` endpoint. Other
//! OpenAI-compatible providers may return a different set of fields; unknown
//! fields are ignored during deserialization.

pub mod request {
    use url::Url;

    use crate::{
        errors::OapiError,
        rest::get::{Get, GetNoStream},
    };

    /// Request parameters for listing models. The endpoint takes no query
    /// parameters.
    #[derive(Debug, Clone, Copy, Default)]
    pub struct ListModelsRequest;

    impl Get for ListModelsRequest {
        /// base_url should look 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("models");

            Ok(url.to_string())
        }
    }

    impl GetNoStream for ListModelsRequest {
        type Response = super::response::ListModelsResponse;
    }
}

pub mod response {
    use serde::Deserialize;

    /// The response of a model list request.
    #[derive(Debug, Deserialize, Clone)]
    pub struct ListModelsResponse {
        /// The list of available models.
        pub data: Vec<crate::models::Model>,
        /// The object type, which is always `list`. Some OpenAI-compatible
        /// providers omit this field.
        pub object: Option<String>,
    }

    crate::impl_from_str!(ListModelsResponse);
}

#[cfg(test)]
mod tests {
    use super::request::ListModelsRequest;
    use crate::rest::get::{Get, GetNoStream};

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

    /// Deserializes a model list response.
    ///
    /// Fixture is a verbatim response captured from
    /// `GET https://api.deepseek.com/models` (2026-09-01). Note the absence
    /// of the `created` field, which OpenAI returns but DeepSeek omits.
    #[test]
    fn test_parse_list_response() {
        let content = r#"{"object":"list","data":[{"id":"deepseek-v4-flash","object":"model","owned_by":"deepseek"},{"id":"deepseek-v4-pro","object":"model","owned_by":"deepseek"},{"id":"deepseek-v4-flash-vision-exp","object":"model","owned_by":"deepseek"}]}"#;

        let response: super::response::ListModelsResponse = content.parse().unwrap();
        assert_eq!(response.data.len(), 3);
        assert_eq!(response.data[0].id, "deepseek-v4-flash");
        assert_eq!(response.data[0].object, crate::models::ModelObject::Model);
        assert_eq!(response.data[0].owned_by, "deepseek");
        assert_eq!(response.data[0].created, None);
        assert_eq!(response.data[0].shutdown_date, None);
        assert_eq!(response.data[2].id, "deepseek-v4-flash-vision-exp");
    }

    /// DeepSeek documents `GET /models`; run a live request when an API key
    /// is available.
    #[tokio::test]
    async fn test_query_deepseek_models() -> Result<(), anyhow::Error> {
        let Some(api_key) = std::env::var("DEEPSEEK_API_KEY")
            .ok()
            .map(|key| key.trim().to_string())
            .filter(|key| !key.is_empty())
        else {
            println!("Skipping: set DEEPSEEK_API_KEY to run this test");
            return Ok(());
        };

        const DEEPSEEK_BASE_URL: &str = "https://api.deepseek.com";

        let response = ListModelsRequest
            .get_response(&crate::rest::default_client(), DEEPSEEK_BASE_URL, &api_key)
            .await?;
        assert!(!response.data.is_empty());
        println!("DeepSeek models: {:?}", response.data);
        Ok(())
    }
}