1use crate::service::{ArtifactService, ListRequest, LoadRequest, SaveRequest};
2use adk_core::{Artifacts, Part, Result};
3use async_trait::async_trait;
4use std::sync::Arc;
5
6pub struct ScopedArtifacts {
38 service: Arc<dyn ArtifactService>,
39 app_name: String,
40 user_id: String,
41 session_id: String,
42}
43
44impl ScopedArtifacts {
45 pub fn new(
54 service: Arc<dyn ArtifactService>,
55 app_name: String,
56 user_id: String,
57 session_id: String,
58 ) -> Self {
59 Self { service, app_name, user_id, session_id }
60 }
61}
62
63#[async_trait]
64impl Artifacts for ScopedArtifacts {
65 async fn save(&self, name: &str, data: &Part) -> Result<i64> {
66 let resp = self
67 .service
68 .save(SaveRequest {
69 app_name: self.app_name.clone(),
70 user_id: self.user_id.clone(),
71 session_id: self.session_id.clone(),
72 file_name: name.to_string(),
73 part: data.clone(),
74 version: None,
75 })
76 .await?;
77 Ok(resp.version)
78 }
79
80 async fn load(&self, name: &str) -> Result<Part> {
81 let resp = self
82 .service
83 .load(LoadRequest {
84 app_name: self.app_name.clone(),
85 user_id: self.user_id.clone(),
86 session_id: self.session_id.clone(),
87 file_name: name.to_string(),
88 version: None,
89 })
90 .await?;
91 Ok(resp.part)
92 }
93
94 async fn list(&self) -> Result<Vec<String>> {
95 let resp = self
96 .service
97 .list(ListRequest {
98 app_name: self.app_name.clone(),
99 user_id: self.user_id.clone(),
100 session_id: self.session_id.clone(),
101 })
102 .await?;
103 Ok(resp.file_names)
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use crate::InMemoryArtifactService;
111
112 #[tokio::test]
113 async fn test_scoped_artifacts_session_isolation() {
114 let service = Arc::new(InMemoryArtifactService::new());
115
116 let sess1 = ScopedArtifacts::new(
117 service.clone(),
118 "app".to_string(),
119 "user".to_string(),
120 "sess1".to_string(),
121 );
122 let sess2 = ScopedArtifacts::new(
123 service.clone(),
124 "app".to_string(),
125 "user".to_string(),
126 "sess2".to_string(),
127 );
128
129 sess1.save("file.txt", &Part::Text { text: "session 1 data".to_string() }).await.unwrap();
131 sess2.save("file.txt", &Part::Text { text: "session 2 data".to_string() }).await.unwrap();
132
133 let loaded1 = sess1.load("file.txt").await.unwrap();
135 let loaded2 = sess2.load("file.txt").await.unwrap();
136
137 match (loaded1, loaded2) {
138 (Part::Text { text: text1 }, Part::Text { text: text2 }) => {
139 assert_eq!(text1, "session 1 data");
140 assert_eq!(text2, "session 2 data");
141 }
142 _ => panic!("Expected Text parts"),
143 }
144 }
145
146 #[tokio::test]
147 async fn test_scoped_artifacts_list_isolation() {
148 let service = Arc::new(InMemoryArtifactService::new());
149
150 let sess1 = ScopedArtifacts::new(
151 service.clone(),
152 "app".to_string(),
153 "user".to_string(),
154 "sess1".to_string(),
155 );
156 let sess2 = ScopedArtifacts::new(
157 service.clone(),
158 "app".to_string(),
159 "user".to_string(),
160 "sess2".to_string(),
161 );
162
163 sess1.save("file1.txt", &Part::Text { text: "data1".to_string() }).await.unwrap();
165 sess2.save("file2.txt", &Part::Text { text: "data2".to_string() }).await.unwrap();
166
167 let files1 = sess1.list().await.unwrap();
169 let files2 = sess2.list().await.unwrap();
170
171 assert_eq!(files1, vec!["file1.txt"]);
172 assert_eq!(files2, vec!["file2.txt"]);
173 }
174
175 #[tokio::test]
176 async fn test_scoped_artifacts_user_prefix() {
177 let service = Arc::new(InMemoryArtifactService::new());
178
179 let sess1 = ScopedArtifacts::new(
180 service.clone(),
181 "app".to_string(),
182 "user1".to_string(),
183 "sess1".to_string(),
184 );
185 let sess2 = ScopedArtifacts::new(
186 service.clone(),
187 "app".to_string(),
188 "user1".to_string(),
189 "sess2".to_string(),
190 );
191
192 sess1
194 .save("user:shared.txt", &Part::Text { text: "shared data".to_string() })
195 .await
196 .unwrap();
197
198 let loaded1 = sess1.load("user:shared.txt").await.unwrap();
200 let loaded2 = sess2.load("user:shared.txt").await.unwrap();
201
202 match (loaded1, loaded2) {
203 (Part::Text { text: text1 }, Part::Text { text: text2 }) => {
204 assert_eq!(text1, "shared data");
205 assert_eq!(text2, "shared data");
206 }
207 _ => panic!("Expected Text parts"),
208 }
209 }
210}