|
6 | 6 | //! |
7 | 7 | //! The Client SDK is built primarily as a Rust library, |
8 | 8 | //! and can be exposed to Python through PyO3 as well. |
| 9 | +
|
| 10 | +use std::collections::HashMap; |
| 11 | +use std::sync::{Arc, LazyLock, Mutex}; |
| 12 | + |
| 13 | +use uuid::Uuid; |
| 14 | + |
| 15 | +#[derive(Clone, PartialEq, Eq, Hash)] |
| 16 | +pub struct StorageScope { |
| 17 | + org_id: u64, |
| 18 | +} |
| 19 | +impl StorageScope { |
| 20 | + pub fn for_organization(org_id: u64) -> Self { |
| 21 | + Self { org_id } |
| 22 | + } |
| 23 | +} |
| 24 | + |
| 25 | +pub struct StorageService { |
| 26 | + usecase: &'static str, |
| 27 | +} |
| 28 | +impl StorageService { |
| 29 | + pub const fn for_usecase(usecase: &'static str) -> Self { |
| 30 | + Self { usecase } |
| 31 | + } |
| 32 | + pub fn with_scope(&self, scope: StorageScope) -> StorageClient { |
| 33 | + StorageClient { |
| 34 | + usecase: self.usecase, |
| 35 | + scope, |
| 36 | + } |
| 37 | + } |
| 38 | +} |
| 39 | + |
| 40 | +#[derive(Clone, PartialEq, Eq, Hash)] |
| 41 | +pub struct StorageId { |
| 42 | + id: String, |
| 43 | +} |
| 44 | +impl StorageId { |
| 45 | + pub fn from_str(id: &str) -> Self { |
| 46 | + Self { id: id.into() } |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +static MOCK_STORAGE: LazyLock<Mutex<HashMap<(&'static str, StorageScope, StorageId), Arc<[u8]>>>> = |
| 51 | + LazyLock::new(Default::default); |
| 52 | + |
| 53 | +pub struct StorageClient { |
| 54 | + usecase: &'static str, |
| 55 | + scope: StorageScope, |
| 56 | +} |
| 57 | +// TODO: all this should be async |
| 58 | +impl StorageClient { |
| 59 | + pub fn put_blob(&self, id: Option<StorageId>, contents: &[u8]) -> StorageId { |
| 60 | + let id = id.unwrap_or_else(|| StorageId { |
| 61 | + id: Uuid::new_v4().to_string(), |
| 62 | + }); |
| 63 | + let key = (self.usecase, self.scope.clone(), id.clone()); |
| 64 | + let contents = contents.into(); |
| 65 | + |
| 66 | + let mut storage = MOCK_STORAGE.lock().unwrap(); |
| 67 | + storage.insert(key, contents); |
| 68 | + id |
| 69 | + } |
| 70 | + |
| 71 | + pub fn get_blob(&self, id: StorageId) -> Option<Arc<[u8]>> { |
| 72 | + let key = (self.usecase, self.scope.clone(), id.clone()); |
| 73 | + |
| 74 | + let storage = MOCK_STORAGE.lock().unwrap(); |
| 75 | + storage.get(&key).cloned() |
| 76 | + } |
| 77 | + |
| 78 | + pub fn delete_blob(&self, id: StorageId) { |
| 79 | + let key = (self.usecase, self.scope.clone(), id.clone()); |
| 80 | + |
| 81 | + let mut storage = MOCK_STORAGE.lock().unwrap(); |
| 82 | + storage.remove(&key); |
| 83 | + } |
| 84 | +} |
| 85 | + |
| 86 | +#[cfg(test)] |
| 87 | +mod tests { |
| 88 | + use super::*; |
| 89 | + |
| 90 | + #[test] |
| 91 | + fn api_is_usable() { |
| 92 | + static ATTACHMENTS: StorageService = StorageService::for_usecase("attachments"); |
| 93 | + |
| 94 | + let scope = StorageScope::for_organization(12345); |
| 95 | + let client = ATTACHMENTS.with_scope(scope); |
| 96 | + |
| 97 | + let blob: &[u8] = b"oh hai!"; |
| 98 | + let allocated_id = client.put_blob(None, blob); |
| 99 | + |
| 100 | + let stored_blob = client.get_blob(allocated_id.clone()); |
| 101 | + assert_eq!(stored_blob.unwrap().as_ref(), blob); |
| 102 | + |
| 103 | + client.delete_blob(allocated_id.clone()); |
| 104 | + assert!(client.get_blob(allocated_id).is_none()); |
| 105 | + } |
| 106 | +} |
0 commit comments