gemini_genai_rs/caches/
mod.rs1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct CachedContent {
16 #[serde(default)]
18 pub name: String,
19 #[serde(default)]
21 pub display_name: Option<String>,
22 #[serde(default)]
24 pub model: Option<String>,
25 #[serde(default)]
27 pub system_instruction: Option<Content>,
28 #[serde(default)]
30 pub contents: Option<Vec<Content>>,
31 #[serde(default)]
33 pub expire_time: Option<String>,
34 #[serde(default)]
36 pub ttl: Option<String>,
37 #[serde(default)]
39 pub usage_metadata: Option<CachedContentUsageMetadata>,
40 #[serde(default)]
42 pub create_time: Option<String>,
43 #[serde(default)]
45 pub update_time: Option<String>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct CachedContentUsageMetadata {
52 #[serde(default)]
54 pub total_token_count: u64,
55}
56
57#[derive(Debug, Clone)]
59pub struct CreateCachedContentConfig {
60 pub model: String,
62 pub display_name: Option<String>,
64 pub system_instruction: Option<Content>,
66 pub contents: Vec<Content>,
68 pub ttl: Option<String>,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct UpdateCachedContentRequest {
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub ttl: Option<String>,
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub expire_time: Option<String>,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86#[serde(rename_all = "camelCase")]
87pub struct ListCachedContentsResponse {
88 #[serde(default)]
90 pub cached_contents: Vec<CachedContent>,
91 #[serde(default)]
93 pub next_page_token: Option<String>,
94}
95
96#[derive(Debug, thiserror::Error)]
98pub enum CachesError {
99 #[error(transparent)]
100 Http(#[from] HttpError),
102 #[error("Failed to parse response: {0}")]
103 Parse(#[from] serde_json::Error),
105 #[error("Auth error: {0}")]
106 Auth(#[from] crate::session::AuthError),
108}
109
110impl Client {
111 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 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 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 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 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}