gemini_genai_rs/caches/
mod.rs

1//! Caches API — create, list, get, update, delete cached content.
2//!
3//! Feature-gated behind `caches`.
4
5use serde::{Deserialize, Serialize};
6
7use crate::client::Client;
8use crate::client::http::HttpError;
9use crate::protocol::types::Content;
10use crate::transport::auth::ServiceEndpoint;
11
12/// Cached content resource.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct CachedContent {
16    /// Resource name (e.g., `cachedContents/abc123`).
17    #[serde(default)]
18    pub name: String,
19    /// Display name.
20    #[serde(default)]
21    pub display_name: Option<String>,
22    /// The model used for this cached content.
23    #[serde(default)]
24    pub model: Option<String>,
25    /// System instruction.
26    #[serde(default)]
27    pub system_instruction: Option<Content>,
28    /// Cached content items.
29    #[serde(default)]
30    pub contents: Option<Vec<Content>>,
31    /// Expiration time (RFC3339).
32    #[serde(default)]
33    pub expire_time: Option<String>,
34    /// TTL duration string (e.g., "3600s").
35    #[serde(default)]
36    pub ttl: Option<String>,
37    /// Usage metadata.
38    #[serde(default)]
39    pub usage_metadata: Option<CachedContentUsageMetadata>,
40    /// Creation time (RFC3339).
41    #[serde(default)]
42    pub create_time: Option<String>,
43    /// Update time (RFC3339).
44    #[serde(default)]
45    pub update_time: Option<String>,
46}
47
48/// Usage metadata for cached content.
49#[derive(Debug, Clone, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct CachedContentUsageMetadata {
52    /// Total token count of the cached content.
53    #[serde(default)]
54    pub total_token_count: u64,
55}
56
57/// Configuration for creating cached content.
58#[derive(Debug, Clone)]
59pub struct CreateCachedContentConfig {
60    /// Model to use for caching.
61    pub model: String,
62    /// Display name.
63    pub display_name: Option<String>,
64    /// System instruction to cache.
65    pub system_instruction: Option<Content>,
66    /// Content to cache.
67    pub contents: Vec<Content>,
68    /// TTL duration string (e.g., "3600s").
69    pub ttl: Option<String>,
70}
71
72/// Updates to apply to a cached content resource.
73#[derive(Debug, Clone, Serialize, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct UpdateCachedContentRequest {
76    /// New TTL duration string.
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub ttl: Option<String>,
79    /// New expiration time (RFC3339).
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub expire_time: Option<String>,
82}
83
84/// Response from listCachedContents.
85#[derive(Debug, Clone, Serialize, Deserialize)]
86#[serde(rename_all = "camelCase")]
87pub struct ListCachedContentsResponse {
88    /// List of cached contents.
89    #[serde(default)]
90    pub cached_contents: Vec<CachedContent>,
91    /// Pagination token for the next page.
92    #[serde(default)]
93    pub next_page_token: Option<String>,
94}
95
96/// Errors from the Caches API.
97#[derive(Debug, thiserror::Error)]
98pub enum CachesError {
99    #[error(transparent)]
100    /// Transport-level HTTP failure.
101    Http(#[from] HttpError),
102    #[error("Failed to parse response: {0}")]
103    /// Response body failed to parse.
104    Parse(#[from] serde_json::Error),
105    #[error("Auth error: {0}")]
106    /// Authentication/authorization failure.
107    Auth(#[from] crate::session::AuthError),
108}
109
110impl Client {
111    /// List cached contents.
112    pub async fn list_cached_contents(&self) -> Result<ListCachedContentsResponse, CachesError> {
113        let url = self.rest_url(ServiceEndpoint::CachedContents);
114        let headers = self.auth_headers().await?;
115        let json = self.http_client().get_json(&url, headers).await?;
116        if json.is_null() {
117            return Ok(ListCachedContentsResponse {
118                cached_contents: vec![],
119                next_page_token: None,
120            });
121        }
122        Ok(serde_json::from_value(json)?)
123    }
124
125    /// Create a new cached content.
126    pub async fn create_cached_content(
127        &self,
128        config: CreateCachedContentConfig,
129    ) -> Result<CachedContent, CachesError> {
130        let url = self.rest_url(ServiceEndpoint::CachedContents);
131        let headers = self.auth_headers().await?;
132
133        let mut body = serde_json::json!({
134            "model": config.model,
135            "contents": config.contents,
136        });
137
138        if let Some(name) = config.display_name {
139            body["displayName"] = serde_json::Value::String(name);
140        }
141        if let Some(instruction) = &config.system_instruction {
142            body["systemInstruction"] = serde_json::to_value(instruction).unwrap();
143        }
144        if let Some(ttl) = config.ttl {
145            body["ttl"] = serde_json::Value::String(ttl);
146        }
147
148        let json = self.http_client().post_json(&url, headers, &body).await?;
149        Ok(serde_json::from_value(json)?)
150    }
151
152    /// Get a cached content by name.
153    pub async fn get_cached_content(&self, name: &str) -> Result<CachedContent, CachesError> {
154        let base_url = self.rest_url(ServiceEndpoint::CachedContents);
155        let url = format!("{base_url}/{name}");
156        let headers = self.auth_headers().await?;
157        let json = self.http_client().get_json(&url, headers).await?;
158        Ok(serde_json::from_value(json)?)
159    }
160
161    /// Update a cached content (TTL or expiration time).
162    pub async fn update_cached_content(
163        &self,
164        name: &str,
165        updates: UpdateCachedContentRequest,
166    ) -> Result<CachedContent, CachesError> {
167        let base_url = self.rest_url(ServiceEndpoint::CachedContents);
168        let url = format!("{base_url}/{name}");
169        let headers = self.auth_headers().await?;
170        let json = self
171            .http_client()
172            .patch_json(&url, headers, &updates)
173            .await?;
174        Ok(serde_json::from_value(json)?)
175    }
176
177    /// Delete a cached content by name.
178    pub async fn delete_cached_content(&self, name: &str) -> Result<(), CachesError> {
179        let base_url = self.rest_url(ServiceEndpoint::CachedContents);
180        let url = format!("{base_url}/{name}");
181        let headers = self.auth_headers().await?;
182        self.http_client().delete(&url, headers).await?;
183        Ok(())
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    #[test]
192    fn parse_cached_content() {
193        let json = serde_json::json!({
194            "name": "cachedContents/abc123",
195            "displayName": "my-cache",
196            "model": "models/gemini-1.5-flash",
197            "expireTime": "2026-03-02T12:00:00Z",
198            "usageMetadata": {
199                "totalTokenCount": 5000
200            },
201            "createTime": "2026-03-01T12:00:00Z",
202            "updateTime": "2026-03-01T12:00:00Z"
203        });
204        let cached: CachedContent = serde_json::from_value(json).unwrap();
205        assert_eq!(cached.name, "cachedContents/abc123");
206        assert_eq!(cached.display_name, Some("my-cache".to_string()));
207        assert_eq!(cached.model, Some("models/gemini-1.5-flash".to_string()));
208        assert_eq!(cached.usage_metadata.unwrap().total_token_count, 5000);
209    }
210
211    #[test]
212    fn parse_list_cached_contents_response() {
213        let json = serde_json::json!({
214            "cachedContents": [
215                {"name": "cachedContents/a", "model": "models/gemini-1.5-flash"},
216                {"name": "cachedContents/b", "model": "models/gemini-1.5-pro"}
217            ],
218            "nextPageToken": "token123"
219        });
220        let resp: ListCachedContentsResponse = serde_json::from_value(json).unwrap();
221        assert_eq!(resp.cached_contents.len(), 2);
222        assert_eq!(resp.next_page_token, Some("token123".to_string()));
223    }
224
225    #[test]
226    fn update_request_serialization() {
227        let update = UpdateCachedContentRequest {
228            ttl: Some("7200s".to_string()),
229            expire_time: None,
230        };
231        let json = serde_json::to_value(&update).unwrap();
232        assert_eq!(json["ttl"], "7200s");
233        assert!(json.get("expireTime").is_none());
234    }
235
236    #[test]
237    fn usage_metadata_serialization() {
238        let meta = CachedContentUsageMetadata {
239            total_token_count: 12345,
240        };
241        let json = serde_json::to_value(&meta).unwrap();
242        assert_eq!(json["totalTokenCount"], 12345);
243    }
244
245    #[test]
246    fn empty_list_response() {
247        let json = serde_json::json!({"cachedContents": []});
248        let resp: ListCachedContentsResponse = serde_json::from_value(json).unwrap();
249        assert!(resp.cached_contents.is_empty());
250        assert!(resp.next_page_token.is_none());
251    }
252}