gemini_rust/file_search/
model.rs

1//! Data models for File Search API
2//!
3//! This module contains the types used for file search operations,
4//! including stores, documents, operations, and metadata.
5
6use serde::{Deserialize, Serialize};
7use strum::AsRefStr;
8use time::OffsetDateTime;
9
10use crate::common::serde::{
11    deserialize_optional_string_to_i64, deserialize_string_to_i64, mime_as_string,
12};
13
14/// A file search store is a container for document embeddings.
15///
16/// Stores persist indefinitely until deleted, unlike raw files which expire after 48 hours.
17/// You can create multiple stores to organize your documents.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct FileSearchStore {
21    /// Resource name (e.g., "fileSearchStores/my-store-123")
22    pub name: String,
23
24    /// Human-readable display name (max 512 chars)
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub display_name: Option<String>,
27
28    /// Creation timestamp
29    #[serde(with = "time::serde::rfc3339")]
30    pub create_time: OffsetDateTime,
31
32    /// Last update timestamp
33    #[serde(with = "time::serde::rfc3339")]
34    pub update_time: OffsetDateTime,
35
36    /// Number of active documents
37    #[serde(
38        default,
39        skip_serializing_if = "Option::is_none",
40        deserialize_with = "deserialize_optional_string_to_i64"
41    )]
42    pub active_documents_count: Option<i64>,
43
44    /// Number of pending documents
45    #[serde(
46        default,
47        skip_serializing_if = "Option::is_none",
48        deserialize_with = "deserialize_optional_string_to_i64"
49    )]
50    pub pending_documents_count: Option<i64>,
51
52    /// Number of failed documents
53    #[serde(
54        default,
55        skip_serializing_if = "Option::is_none",
56        deserialize_with = "deserialize_optional_string_to_i64"
57    )]
58    pub failed_documents_count: Option<i64>,
59
60    /// Total size in bytes
61    #[serde(
62        default,
63        skip_serializing_if = "Option::is_none",
64        deserialize_with = "deserialize_optional_string_to_i64"
65    )]
66    pub size_bytes: Option<i64>,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
70#[serde(rename_all = "camelCase")]
71pub struct CreateFileSearchStoreRequest {
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub display_name: Option<String>,
74}
75
76/// A document represents a file within a file search store.
77///
78/// Documents are automatically chunked, embedded, and indexed when uploaded.
79#[derive(Debug, Clone, Serialize, Deserialize)]
80#[serde(rename_all = "camelCase")]
81pub struct Document {
82    /// Resource name (e.g., "fileSearchStores/*/documents/doc-123")
83    pub name: String,
84
85    /// Human-readable display name (max 512 chars)
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub display_name: Option<String>,
88
89    /// Custom metadata (max 20 items)
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub custom_metadata: Option<Vec<CustomMetadata>>,
92
93    /// Last update timestamp
94    #[serde(with = "time::serde::rfc3339")]
95    pub update_time: OffsetDateTime,
96
97    /// Creation timestamp
98    #[serde(with = "time::serde::rfc3339")]
99    pub create_time: OffsetDateTime,
100
101    /// Current state
102    pub state: DocumentState,
103
104    /// Size in bytes
105    #[serde(deserialize_with = "deserialize_string_to_i64")]
106    pub size_bytes: i64,
107
108    /// MIME type
109    #[serde(with = "mime_as_string")]
110    pub mime_type: mime::Mime,
111}
112
113/// The lifecycle state of a document.
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, AsRefStr)]
115#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
116#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
117pub enum DocumentState {
118    /// Unknown state
119    StateUnspecified,
120    /// Document chunks are being processed (embedding and indexing)
121    StatePending,
122    /// All chunks processed and ready for querying
123    StateActive,
124    /// Some chunks failed processing
125    StateFailed,
126}
127
128/// Custom metadata for filtering and organizing documents.
129///
130/// Documents can have up to 20 metadata entries. Metadata can be used
131/// to filter searches using AIP-160 syntax (e.g., `category = "api-docs"`).
132#[derive(Debug, Clone, Serialize, Deserialize)]
133#[serde(rename_all = "camelCase")]
134pub struct CustomMetadata {
135    /// Metadata key
136    pub key: String,
137    /// Metadata value (string, string list, or numeric)
138    #[serde(flatten)]
139    pub value: CustomMetadataValue,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
143#[serde(untagged)]
144pub enum CustomMetadataValue {
145    StringValue { string_value: String },
146    StringListValue { string_list_value: StringList },
147    NumericValue { numeric_value: f64 },
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct StringList {
152    pub values: Vec<String>,
153}
154
155/// Configuration for how documents are chunked.
156///
157/// Chunking splits documents into smaller pieces for more precise retrieval.
158#[derive(Debug, Clone, Serialize, Deserialize)]
159#[serde(rename_all = "camelCase")]
160pub struct ChunkingConfig {
161    /// White space-based chunking configuration
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub white_space_config: Option<WhiteSpaceConfig>,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
167#[serde(rename_all = "camelCase")]
168pub struct WhiteSpaceConfig {
169    /// Maximum tokens per chunk
170    pub max_tokens_per_chunk: u32,
171
172    /// Maximum overlapping tokens between chunks
173    pub max_overlap_tokens: u32,
174}
175
176/// A long-running operation for file uploads and imports.
177///
178/// Operations track the progress of file processing, including
179/// chunking, embedding, and indexing.
180#[derive(Debug, Clone, Serialize, Deserialize)]
181#[serde(rename_all = "camelCase")]
182pub struct Operation {
183    /// Operation name (e.g., "fileSearchStores/*/operations/*")
184    pub name: String,
185
186    /// Service-specific metadata
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub metadata: Option<serde_json::Value>,
189
190    /// Whether operation is complete
191    #[serde(default, skip_serializing_if = "Option::is_none")]
192    pub done: Option<bool>,
193
194    /// Result (error or response)
195    #[serde(flatten)]
196    pub result: Option<OperationResult>,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
200#[serde(untagged)]
201pub enum OperationResult {
202    Error { error: Status },
203    Response { response: serde_json::Value },
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct Status {
208    pub code: i32,
209    pub message: String,
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub details: Option<Vec<serde_json::Value>>,
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
215#[serde(rename_all = "camelCase")]
216pub struct UploadToFileSearchStoreRequest {
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub display_name: Option<String>,
219
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub custom_metadata: Option<Vec<CustomMetadata>>,
222
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub chunking_config: Option<ChunkingConfig>,
225
226    #[serde(
227        with = "mime_as_string::optional",
228        skip_serializing_if = "Option::is_none"
229    )]
230    pub mime_type: Option<mime::Mime>,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
234#[serde(rename_all = "camelCase")]
235pub struct ImportFileRequest {
236    /// File resource name from Files API
237    pub file_name: String,
238
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub custom_metadata: Option<Vec<CustomMetadata>>,
241
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub chunking_config: Option<ChunkingConfig>,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
247#[serde(rename_all = "camelCase")]
248pub struct ListFileSearchStoresResponse {
249    pub file_search_stores: Vec<FileSearchStore>,
250
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub next_page_token: Option<String>,
253}
254
255#[derive(Debug, Clone, Serialize, Deserialize)]
256#[serde(rename_all = "camelCase")]
257pub struct ListDocumentsResponse {
258    pub documents: Vec<Document>,
259
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub next_page_token: Option<String>,
262}
263
264pub fn extract_store_name(full_name: &str) -> Result<String, crate::client::Error> {
265    // Extract store name from "fileSearchStores/{store}/documents/{doc}"
266    let mut parts = full_name.split('/');
267    if parts.next() == Some("fileSearchStores") {
268        if let Some(store_id) = parts.next() {
269            return Ok(format!("fileSearchStores/{}", store_id));
270        }
271    }
272    Err(crate::client::Error::InvalidResourceName {
273        name: full_name.to_string(),
274    })
275}
276
277pub fn extract_document_id(full_name: &str) -> Result<String, crate::client::Error> {
278    // Extract document ID from "fileSearchStores/{store}/documents/{doc}"
279    let mut parts = full_name.split('/');
280    if parts.next() == Some("fileSearchStores") {
281        if let Some(_store) = parts.next() {
282            if parts.next() == Some("documents") {
283                if let Some(doc_id) = parts.next() {
284                    return Ok(doc_id.to_string());
285                }
286            }
287        }
288    }
289    Err(crate::client::Error::InvalidResourceName {
290        name: full_name.to_string(),
291    })
292}