Skip to main content

openai_rust/resources/
models.rs

1use std::{collections::BTreeMap, fmt::Write as _, sync::Arc};
2
3use serde::Deserialize;
4use serde_json::Value;
5
6use crate::{
7    OpenAIError,
8    core::{request::RequestOptions, response::ApiResponse, runtime::ClientRuntime},
9    error::ErrorKind,
10};
11
12/// Models API family.
13#[derive(Clone, Debug)]
14pub struct Models {
15    runtime: Arc<ClientRuntime>,
16}
17
18impl Models {
19    pub(crate) fn new(runtime: Arc<ClientRuntime>) -> Self {
20        Self { runtime }
21    }
22
23    /// Retrieves a model by id without rewriting caller-supplied ids.
24    pub fn retrieve(&self, model_id: &str) -> Result<ApiResponse<Model>, OpenAIError> {
25        let model_id = encode_path_id(validate_path_id("model_id", model_id)?);
26        self.runtime.execute_json(
27            "GET",
28            format!("/models/{model_id}"),
29            RequestOptions::default(),
30        )
31    }
32
33    /// Lists models as a single forward-compatible page.
34    pub fn list(&self) -> Result<ApiResponse<ModelsPage>, OpenAIError> {
35        self.runtime
36            .execute_json("GET", "/models", RequestOptions::default())
37    }
38
39    /// Deletes an owned model and preserves server permission/not-found semantics.
40    pub fn delete(&self, model_id: &str) -> Result<ApiResponse<DeletedModel>, OpenAIError> {
41        let model_id = encode_path_id(validate_path_id("model_id", model_id)?);
42        self.runtime.execute_json(
43            "DELETE",
44            format!("/models/{model_id}"),
45            RequestOptions::default(),
46        )
47    }
48}
49
50/// Typed model object.
51#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
52pub struct Model {
53    pub id: String,
54    #[serde(default)]
55    pub object: String,
56    #[serde(default)]
57    pub created: Option<i64>,
58    #[serde(default)]
59    pub owned_by: Option<String>,
60    #[serde(flatten)]
61    pub extra: BTreeMap<String, Value>,
62}
63
64/// Single-page models list response.
65#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
66pub struct ModelsPage {
67    #[serde(default)]
68    pub object: String,
69    #[serde(default)]
70    pub data: Vec<Model>,
71    #[serde(flatten)]
72    pub extra: BTreeMap<String, Value>,
73}
74
75impl ModelsPage {
76    pub fn has_next_page(&self) -> bool {
77        false
78    }
79
80    pub fn next_after(&self) -> Option<&str> {
81        None
82    }
83}
84
85/// Model deletion marker.
86#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
87pub struct DeletedModel {
88    pub id: String,
89    #[serde(default)]
90    pub object: String,
91    pub deleted: bool,
92    #[serde(flatten)]
93    pub extra: BTreeMap<String, Value>,
94}
95
96fn validate_path_id<'a>(label: &str, value: &'a str) -> Result<&'a str, OpenAIError> {
97    if value.trim().is_empty() {
98        return Err(OpenAIError::new(
99            ErrorKind::Validation,
100            format!("{label} cannot be blank"),
101        ));
102    }
103    Ok(value)
104}
105
106fn encode_path_id(value: &str) -> String {
107    let mut encoded = String::with_capacity(value.len());
108    for byte in value.bytes() {
109        if matches!(
110            byte,
111            b'A'..=b'Z'
112                | b'a'..=b'z'
113                | b'0'..=b'9'
114                | b'-'
115                | b'.'
116                | b'_'
117                | b'~'
118                | b'!'
119                | b'$'
120                | b'&'
121                | b'\''
122                | b'('
123                | b')'
124                | b'*'
125                | b'+'
126                | b','
127                | b';'
128                | b'='
129                | b':'
130                | b'@'
131        ) {
132            encoded.push(byte as char);
133        } else {
134            let _ = write!(&mut encoded, "%{byte:02X}");
135        }
136    }
137    encoded
138}