Skip to main content

zai_rs/file/
request.rs

1use serde::{Deserialize, Serialize};
2use validator::Validate;
3
4/// Query parameters for listing files.
5#[derive(Clone, Serialize, Validate)]
6pub struct FileListQuery {
7    /// Pagination cursor
8    #[serde(skip_serializing_if = "Option::is_none")]
9    #[validate(length(min = 1))]
10    pub(crate) after: Option<String>,
11
12    /// Required file-purpose filter.
13    pub(crate) purpose: FileListPurpose,
14
15    /// Sort order (currently only `created_at`)
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub(crate) order: Option<FileOrder>,
18
19    /// Page size 1..=100 (default 20)
20    #[serde(skip_serializing_if = "Option::is_none")]
21    #[validate(range(min = 1, max = 100))]
22    pub(crate) limit: Option<u32>,
23}
24
25impl std::fmt::Debug for FileListQuery {
26    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        formatter
28            .debug_struct("FileListQuery")
29            .field("after", &self.after.as_ref().map(|_| "[REDACTED]"))
30            .field("purpose", &self.purpose)
31            .field("order", &self.order)
32            .field("limit", &self.limit)
33            .finish()
34    }
35}
36
37impl FileListQuery {
38    /// Create a query with the required purpose filter.
39    pub fn new(purpose: FileListPurpose) -> Self {
40        Self {
41            after: None,
42            purpose,
43            order: None,
44            limit: None,
45        }
46    }
47    /// Set the pagination cursor.
48    pub fn with_after(mut self, after: impl Into<String>) -> Self {
49        self.after = Some(after.into());
50        self
51    }
52    /// Set the sort order.
53    pub fn with_order(mut self, o: FileOrder) -> Self {
54        self.order = Some(o);
55        self
56    }
57    /// Set the page size (1..=100).
58    pub fn with_limit(mut self, limit: u32) -> Self {
59        self.limit = Some(limit);
60        self
61    }
62}
63
64/// Purpose filter accepted by the file-list operation.
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
66pub enum FileListPurpose {
67    /// File used as batch-processing input.
68    #[serde(rename = "batch")]
69    Batch,
70    /// File used by the code interpreter.
71    #[serde(rename = "code-interpreter")]
72    CodeInterpreter,
73    /// File attached to an agent.
74    #[serde(rename = "agent")]
75    Agent,
76}
77
78impl FileListPurpose {
79    /// Return the canonical upstream string for this purpose.
80    pub fn as_str(&self) -> &'static str {
81        match self {
82            Self::Batch => "batch",
83            Self::CodeInterpreter => "code-interpreter",
84            Self::Agent => "agent",
85        }
86    }
87}
88
89/// Purpose accepted by the multipart file-upload operation.
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
91pub enum FileUploadPurpose {
92    /// File used as batch-processing input.
93    #[serde(rename = "batch")]
94    Batch,
95    /// File used by the code interpreter.
96    #[serde(rename = "code-interpreter")]
97    CodeInterpreter,
98    /// File attached to an agent.
99    #[serde(rename = "agent")]
100    Agent,
101    /// Sample audio used as voice-clone input.
102    #[serde(rename = "voice-clone-input")]
103    VoiceCloneInput,
104}
105
106impl FileUploadPurpose {
107    /// Return the exact multipart value.
108    pub const fn as_str(self) -> &'static str {
109        match self {
110            Self::Batch => "batch",
111            Self::CodeInterpreter => "code-interpreter",
112            Self::Agent => "agent",
113            Self::VoiceCloneInput => "voice-clone-input",
114        }
115    }
116}
117
118/// Sort order for file listing.
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
120pub enum FileOrder {
121    /// Order by creation time.
122    #[serde(rename = "created_at")]
123    CreatedAt,
124}
125
126impl FileOrder {
127    /// Return the canonical upstream string for this order.
128    pub fn as_str(&self) -> &'static str {
129        match self {
130            FileOrder::CreatedAt => "created_at",
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn list_query_debug_redacts_the_file_cursor() {
141        let query = FileListQuery::new(FileListPurpose::Batch).with_after("private-file-id");
142        assert!(!format!("{query:?}").contains("private-file-id"));
143    }
144
145    #[test]
146    fn operation_specific_purpose_enums_match_the_frozen_values() {
147        assert_eq!(
148            [
149                FileListPurpose::Batch,
150                FileListPurpose::CodeInterpreter,
151                FileListPurpose::Agent,
152            ]
153            .map(|value| serde_json::to_value(value).unwrap()),
154            ["batch", "code-interpreter", "agent"]
155                .map(|value| serde_json::Value::String(value.to_owned()))
156        );
157        assert_eq!(
158            [
159                FileUploadPurpose::Batch,
160                FileUploadPurpose::CodeInterpreter,
161                FileUploadPurpose::Agent,
162                FileUploadPurpose::VoiceCloneInput,
163            ]
164            .map(|value| serde_json::to_value(value).unwrap()),
165            ["batch", "code-interpreter", "agent", "voice-clone-input"]
166                .map(|value| serde_json::Value::String(value.to_owned()))
167        );
168    }
169}