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