Skip to main content

gproxy_protocol/openai/
files.rs

1//! OpenAI Files wire shapes.
2//!
3//! The local OpenAI documentation snapshot has no Files control-plane page;
4//! these public fields follow the v2 protocol model and its observed service
5//! responses. Multipart bytes are represented by their normalized data URL.
6
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10use crate::openai::common::{ListObjectType, Rest};
11
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
13#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
14pub struct CreateFileRequest {
15    pub purpose: FilePurpose,
16    pub file: String,
17    #[serde(default, flatten)]
18    pub rest: Rest,
19}
20
21#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
22#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
23pub struct ListFilesRequest {
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub after: Option<String>,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub limit: Option<u32>,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub purpose: Option<FilePurpose>,
30    #[serde(default, flatten)]
31    pub rest: Rest,
32}
33
34#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
35#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
36pub struct RetrieveFileRequest {
37    pub file_id: String,
38    #[serde(default, flatten)]
39    pub rest: Rest,
40}
41
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
43#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
44pub struct RetrieveFileContentRequest {
45    pub file_id: String,
46    #[serde(default, flatten)]
47    pub rest: Rest,
48}
49
50#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
51#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
52pub struct DeleteFileRequest {
53    pub file_id: String,
54    #[serde(default, flatten)]
55    pub rest: Rest,
56}
57
58#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
59#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
60pub struct FileObject {
61    pub id: String,
62    pub object: String,
63    pub bytes: u64,
64    pub created_at: i64,
65    pub filename: String,
66    pub purpose: FilePurpose,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub status: Option<String>,
69    /// Evidence gap: `upstream_docs/openai/` has no `/v1/files` schema;
70    /// preserve the v2 `FileObject.status_details` payload verbatim.
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub status_details: Option<Value>,
73    #[serde(default, flatten)]
74    pub rest: Rest,
75}
76
77#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
78#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
79pub struct ListFilesResponse {
80    pub object: ListObjectType,
81    pub data: Vec<FileObject>,
82    #[serde(default)]
83    pub has_more: bool,
84    #[serde(default, flatten)]
85    pub rest: Rest,
86}
87
88#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, gproxy_protocol_macros::WireBuilder)]
89#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
90pub struct DeleteFileResponse {
91    pub id: String,
92    pub object: String,
93    pub deleted: bool,
94    #[serde(default, flatten)]
95    pub rest: Rest,
96}
97
98/// Body bytes returned by `GET /v1/files/{id}/content`.
99pub type FileContent = Vec<u8>;
100
101macro_rules! extensible_string {
102    ($name:ident, $known:ident { $($variant:ident => $wire:literal),+ $(,)? }) => {
103        #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
104        #[serde(untagged)]
105        pub enum $name {
106            Known($known),
107            Unknown(String),
108        }
109
110        #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
111        pub enum $known {
112            $(#[serde(rename = $wire)] $variant),+
113        }
114    };
115}
116
117extensible_string!(FilePurpose, KnownFilePurpose {
118    Assistants => "assistants",
119});
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use serde_json::json;
125
126    #[test]
127    fn file_models_round_trip_unknown_fields_and_variants() {
128        let value = json!({
129            "id":"file_1",
130            "object":"file.future",
131            "bytes":4,
132            "created_at":1,
133            "filename":"a.bin",
134            "purpose":"future-purpose",
135            "status":"future-status",
136            "future_file":{"x":1}
137        });
138        let file: FileObject = serde_json::from_value(value.clone()).unwrap();
139        assert_eq!(serde_json::to_value(file).unwrap(), value);
140
141        let list = json!({
142            "object":"list",
143            "data":[],
144            "has_more":false,
145            "future_list":true
146        });
147        let parsed: ListFilesResponse = serde_json::from_value(list.clone()).unwrap();
148        assert_eq!(serde_json::to_value(parsed).unwrap(), list);
149    }
150}