metabase_api_rs/repository/
collection.rs1use super::traits::{
6 FilterParams, PaginationParams, Repository, RepositoryError, RepositoryResult,
7};
8use crate::core::models::common::CollectionId;
9use crate::core::models::Collection;
10use crate::transport::http_provider_safe::{HttpProviderExt, HttpProviderSafe};
11use async_trait::async_trait;
12use std::sync::Arc;
13
14#[derive(Debug, Clone, Default)]
16pub struct CollectionFilterParams {
17 pub base: FilterParams,
19 pub parent_id: Option<i32>,
21 pub namespace: Option<String>,
23 pub personal_only: Option<bool>,
25}
26
27impl CollectionFilterParams {
28 pub fn new() -> Self {
30 Self::default()
31 }
32
33 pub fn with_parent(mut self, parent_id: i32) -> Self {
35 self.parent_id = Some(parent_id);
36 self
37 }
38
39 pub fn with_namespace(mut self, namespace: impl Into<String>) -> Self {
41 self.namespace = Some(namespace.into());
42 self
43 }
44
45 pub fn with_personal_only(mut self, personal_only: bool) -> Self {
47 self.personal_only = Some(personal_only);
48 self
49 }
50}
51
52#[async_trait]
54pub trait CollectionRepository:
55 Repository<Entity = Collection, Id = CollectionId> + Send + Sync
56{
57 async fn list_with_filters(
59 &self,
60 pagination: Option<PaginationParams>,
61 filters: Option<CollectionFilterParams>,
62 ) -> RepositoryResult<Vec<Collection>>;
63
64 async fn get_children(&self, parent_id: CollectionId) -> RepositoryResult<Vec<Collection>>;
66
67 async fn get_root_collections(&self) -> RepositoryResult<Vec<Collection>>;
69
70 async fn get_by_parent(
72 &self,
73 parent_id: Option<CollectionId>,
74 ) -> RepositoryResult<Vec<Collection>>;
75
76 async fn get_permissions(&self, id: &CollectionId) -> RepositoryResult<serde_json::Value>;
78
79 async fn update_permissions(
81 &self,
82 id: &CollectionId,
83 permissions: &serde_json::Value,
84 ) -> RepositoryResult<()>;
85
86 async fn move_collection(
88 &self,
89 id: &CollectionId,
90 new_parent_id: Option<CollectionId>,
91 ) -> RepositoryResult<Collection>;
92
93 async fn archive(&self, id: &CollectionId) -> RepositoryResult<()>;
95
96 async fn unarchive(&self, id: &CollectionId) -> RepositoryResult<()>;
98}
99
100pub struct HttpCollectionRepository {
102 http_provider: Arc<dyn HttpProviderSafe>,
103}
104
105impl HttpCollectionRepository {
106 pub fn new(http_provider: Arc<dyn HttpProviderSafe>) -> Self {
108 Self { http_provider }
109 }
110
111 fn build_query_params(
113 &self,
114 pagination: Option<PaginationParams>,
115 filters: Option<FilterParams>,
116 ) -> String {
117 let mut params = Vec::new();
118
119 if let Some(p) = pagination {
120 if let Some(page) = p.page {
121 params.push(format!("page={}", page));
122 }
123 if let Some(limit) = p.limit {
124 params.push(format!("limit={}", limit));
125 }
126 if let Some(offset) = p.offset {
127 params.push(format!("offset={}", offset));
128 }
129 }
130
131 if let Some(f) = filters {
132 if let Some(query) = f.query {
133 params.push(format!("q={}", query.replace(' ', "+")));
134 }
135 if let Some(archived) = f.archived {
136 params.push(format!("archived={}", archived));
137 }
138 }
139
140 if params.is_empty() {
141 String::new()
142 } else {
143 format!("?{}", params.join("&"))
144 }
145 }
146}
147
148#[async_trait]
149impl Repository for HttpCollectionRepository {
150 type Entity = Collection;
151 type Id = CollectionId;
152
153 async fn get(&self, id: &CollectionId) -> RepositoryResult<Collection> {
154 let path = format!("/api/collection/{}", id.0);
155 self.http_provider.get(&path).await.map_err(|e| e.into())
156 }
157
158 async fn list(
159 &self,
160 pagination: Option<PaginationParams>,
161 filters: Option<FilterParams>,
162 ) -> RepositoryResult<Vec<Collection>> {
163 let query = self.build_query_params(pagination, filters);
164 let path = format!("/api/collection{}", query);
165 self.http_provider.get(&path).await.map_err(|e| e.into())
166 }
167
168 async fn create(&self, entity: &Collection) -> RepositoryResult<Collection> {
169 self.http_provider
170 .post("/api/collection", entity)
171 .await
172 .map_err(|e| e.into())
173 }
174
175 async fn update(&self, id: &CollectionId, entity: &Collection) -> RepositoryResult<Collection> {
176 let path = format!("/api/collection/{}", id.0);
177 self.http_provider
178 .put(&path, entity)
179 .await
180 .map_err(|e| e.into())
181 }
182
183 async fn delete(&self, id: &CollectionId) -> RepositoryResult<()> {
184 let path = format!("/api/collection/{}", id.0);
185 self.http_provider.delete(&path).await.map_err(|e| e.into())
186 }
187}
188
189#[async_trait]
190impl CollectionRepository for HttpCollectionRepository {
191 async fn list_with_filters(
192 &self,
193 pagination: Option<PaginationParams>,
194 filters: Option<CollectionFilterParams>,
195 ) -> RepositoryResult<Vec<Collection>> {
196 let base_filters = filters.map(|f| f.base);
198 self.list(pagination, base_filters).await
199 }
200
201 async fn get_children(&self, parent_id: CollectionId) -> RepositoryResult<Vec<Collection>> {
202 let path = format!("/api/collection/{}/children", parent_id.0);
203 self.http_provider.get(&path).await.map_err(|e| e.into())
204 }
205
206 async fn get_root_collections(&self) -> RepositoryResult<Vec<Collection>> {
207 self.http_provider
208 .get("/api/collection/root")
209 .await
210 .map_err(|e| e.into())
211 }
212
213 async fn get_by_parent(
214 &self,
215 parent_id: Option<CollectionId>,
216 ) -> RepositoryResult<Vec<Collection>> {
217 let path = match parent_id {
218 Some(id) => format!("/api/collection?parent_id={}", id.0),
219 None => "/api/collection?parent_id=".to_string(),
220 };
221 self.http_provider.get(&path).await.map_err(|e| e.into())
222 }
223
224 async fn get_permissions(&self, id: &CollectionId) -> RepositoryResult<serde_json::Value> {
225 let path = format!("/api/collection/{}/permissions", id.0);
226 self.http_provider.get(&path).await.map_err(|e| e.into())
227 }
228
229 async fn update_permissions(
230 &self,
231 id: &CollectionId,
232 permissions: &serde_json::Value,
233 ) -> RepositoryResult<()> {
234 let path = format!("/api/collection/{}/permissions", id.0);
235 self.http_provider
236 .put(&path, permissions)
237 .await
238 .map(|_: serde_json::Value| ())
239 .map_err(|e| e.into())
240 }
241
242 async fn move_collection(
243 &self,
244 id: &CollectionId,
245 new_parent_id: Option<CollectionId>,
246 ) -> RepositoryResult<Collection> {
247 let path = format!("/api/collection/{}", id.0);
248 let body = serde_json::json!({
249 "parent_id": new_parent_id.map(|id| id.0)
250 });
251 self.http_provider
252 .put(&path, &body)
253 .await
254 .map_err(|e| e.into())
255 }
256
257 async fn archive(&self, id: &CollectionId) -> RepositoryResult<()> {
258 let path = format!("/api/collection/{}", id.0);
259 let body = serde_json::json!({ "archived": true });
260 self.http_provider
261 .put(&path, &body)
262 .await
263 .map(|_: serde_json::Value| ())
264 .map_err(|e| e.into())
265 }
266
267 async fn unarchive(&self, id: &CollectionId) -> RepositoryResult<()> {
268 let path = format!("/api/collection/{}", id.0);
269 let body = serde_json::json!({ "archived": false });
270 self.http_provider
271 .put(&path, &body)
272 .await
273 .map(|_: serde_json::Value| ())
274 .map_err(|e| e.into())
275 }
276}
277
278pub struct MockCollectionRepository {
280 collections: Arc<tokio::sync::RwLock<Vec<Collection>>>,
281 should_fail: bool,
282}
283
284impl MockCollectionRepository {
285 pub fn new() -> Self {
287 Self {
288 collections: Arc::new(tokio::sync::RwLock::new(Vec::new())),
289 should_fail: false,
290 }
291 }
292
293 pub fn set_should_fail(&mut self, should_fail: bool) {
295 self.should_fail = should_fail;
296 }
297
298 pub async fn add_collection(&self, collection: Collection) {
300 let mut collections = self.collections.write().await;
301 collections.push(collection);
302 }
303}
304
305impl Default for MockCollectionRepository {
306 fn default() -> Self {
307 Self::new()
308 }
309}
310
311#[async_trait]
312impl Repository for MockCollectionRepository {
313 type Entity = Collection;
314 type Id = CollectionId;
315
316 async fn get(&self, id: &CollectionId) -> RepositoryResult<Collection> {
317 if self.should_fail {
318 return Err(RepositoryError::Other("Mock failure".to_string()));
319 }
320
321 let collections = self.collections.read().await;
322 collections
323 .iter()
324 .find(|c| c.id == Some(*id))
325 .cloned()
326 .ok_or_else(|| RepositoryError::NotFound(format!("Collection {} not found", id.0)))
327 }
328
329 async fn list(
330 &self,
331 _pagination: Option<PaginationParams>,
332 _filters: Option<FilterParams>,
333 ) -> RepositoryResult<Vec<Collection>> {
334 if self.should_fail {
335 return Err(RepositoryError::Other("Mock failure".to_string()));
336 }
337
338 let collections = self.collections.read().await;
339 Ok(collections.clone())
340 }
341
342 async fn create(&self, entity: &Collection) -> RepositoryResult<Collection> {
343 if self.should_fail {
344 return Err(RepositoryError::Other("Mock failure".to_string()));
345 }
346
347 let mut collections = self.collections.write().await;
348 let mut new_collection = entity.clone();
349 if new_collection.id.is_none() {
351 new_collection.id = Some(CollectionId((collections.len() + 1) as i32));
352 }
353 collections.push(new_collection.clone());
354 Ok(new_collection)
355 }
356
357 async fn update(&self, id: &CollectionId, entity: &Collection) -> RepositoryResult<Collection> {
358 if self.should_fail {
359 return Err(RepositoryError::Other("Mock failure".to_string()));
360 }
361
362 let mut collections = self.collections.write().await;
363 if let Some(collection) = collections.iter_mut().find(|c| c.id == Some(*id)) {
364 *collection = entity.clone();
365 collection.id = Some(*id); Ok(collection.clone())
367 } else {
368 Err(RepositoryError::NotFound(format!(
369 "Collection {} not found",
370 id.0
371 )))
372 }
373 }
374
375 async fn delete(&self, id: &CollectionId) -> RepositoryResult<()> {
376 if self.should_fail {
377 return Err(RepositoryError::Other("Mock failure".to_string()));
378 }
379
380 let mut collections = self.collections.write().await;
381 let initial_len = collections.len();
382 collections.retain(|c| c.id != Some(*id));
383
384 if collections.len() < initial_len {
385 Ok(())
386 } else {
387 Err(RepositoryError::NotFound(format!(
388 "Collection {} not found",
389 id.0
390 )))
391 }
392 }
393}
394
395#[async_trait]
396impl CollectionRepository for MockCollectionRepository {
397 async fn list_with_filters(
398 &self,
399 pagination: Option<PaginationParams>,
400 filters: Option<CollectionFilterParams>,
401 ) -> RepositoryResult<Vec<Collection>> {
402 let base_filters = filters.map(|f| f.base);
403 self.list(pagination, base_filters).await
404 }
405
406 async fn get_children(&self, parent_id: CollectionId) -> RepositoryResult<Vec<Collection>> {
407 if self.should_fail {
408 return Err(RepositoryError::Other("Mock failure".to_string()));
409 }
410
411 let collections = self.collections.read().await;
412 Ok(collections
413 .iter()
414 .filter(|c| c.parent_id == Some(parent_id.0))
415 .cloned()
416 .collect())
417 }
418
419 async fn get_root_collections(&self) -> RepositoryResult<Vec<Collection>> {
420 if self.should_fail {
421 return Err(RepositoryError::Other("Mock failure".to_string()));
422 }
423
424 let collections = self.collections.read().await;
425 Ok(collections
426 .iter()
427 .filter(|c| c.parent_id.is_none())
428 .cloned()
429 .collect())
430 }
431
432 async fn get_by_parent(
433 &self,
434 parent_id: Option<CollectionId>,
435 ) -> RepositoryResult<Vec<Collection>> {
436 if self.should_fail {
437 return Err(RepositoryError::Other("Mock failure".to_string()));
438 }
439
440 let collections = self.collections.read().await;
441 Ok(collections
442 .iter()
443 .filter(|c| match parent_id {
444 Some(id) => c.parent_id == Some(id.0),
445 None => c.parent_id.is_none(),
446 })
447 .cloned()
448 .collect())
449 }
450
451 async fn get_permissions(&self, id: &CollectionId) -> RepositoryResult<serde_json::Value> {
452 if self.should_fail {
453 return Err(RepositoryError::Other("Mock failure".to_string()));
454 }
455
456 self.get(id).await?;
458
459 Ok(serde_json::json!({
461 "read": ["all"],
462 "write": ["admin"],
463 }))
464 }
465
466 async fn update_permissions(
467 &self,
468 id: &CollectionId,
469 _permissions: &serde_json::Value,
470 ) -> RepositoryResult<()> {
471 if self.should_fail {
472 return Err(RepositoryError::Other("Mock failure".to_string()));
473 }
474
475 self.get(id).await?;
477
478 Ok(())
480 }
481
482 async fn move_collection(
483 &self,
484 id: &CollectionId,
485 new_parent_id: Option<CollectionId>,
486 ) -> RepositoryResult<Collection> {
487 if self.should_fail {
488 return Err(RepositoryError::Other("Mock failure".to_string()));
489 }
490
491 let mut collections = self.collections.write().await;
492 if let Some(collection) = collections.iter_mut().find(|c| c.id == Some(*id)) {
493 collection.parent_id = new_parent_id.map(|id| id.0);
494 Ok(collection.clone())
495 } else {
496 Err(RepositoryError::NotFound(format!(
497 "Collection {} not found",
498 id.0
499 )))
500 }
501 }
502
503 async fn archive(&self, id: &CollectionId) -> RepositoryResult<()> {
504 if self.should_fail {
505 return Err(RepositoryError::Other("Mock failure".to_string()));
506 }
507
508 let mut collections = self.collections.write().await;
509 if let Some(collection) = collections.iter_mut().find(|c| c.id == Some(*id)) {
510 collection.archived = Some(true);
511 Ok(())
512 } else {
513 Err(RepositoryError::NotFound(format!(
514 "Collection {} not found",
515 id.0
516 )))
517 }
518 }
519
520 async fn unarchive(&self, id: &CollectionId) -> RepositoryResult<()> {
521 if self.should_fail {
522 return Err(RepositoryError::Other("Mock failure".to_string()));
523 }
524
525 let mut collections = self.collections.write().await;
526 if let Some(collection) = collections.iter_mut().find(|c| c.id == Some(*id)) {
527 collection.archived = Some(false);
528 Ok(())
529 } else {
530 Err(RepositoryError::NotFound(format!(
531 "Collection {} not found",
532 id.0
533 )))
534 }
535 }
536}