reduct_rs/
bucket.rs

1// Copyright 2023 ReductStore
2// This Source Code Form is subject to the terms of the Mozilla Public
3//    License, v. 2.0. If a copy of the MPL was not distributed with this
4//    file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
6mod link;
7mod read;
8mod remove;
9mod update;
10mod write;
11
12use std::sync::Arc;
13
14use reqwest::Method;
15
16use reduct_base::error::ErrorCode;
17use reduct_base::msg::bucket_api::{BucketInfo, BucketSettings, FullBucketInfo, QuotaType};
18use reduct_base::msg::entry_api::{EntryInfo, RenameEntry};
19
20use crate::client::Result;
21use crate::http_client::HttpClient;
22
23/// A bucket to store data in.
24pub struct Bucket {
25    pub(crate) name: String,
26    pub(crate) http_client: Arc<HttpClient>,
27}
28
29pub struct BucketBuilder {
30    name: String,
31    exist_ok: bool,
32    settings: BucketSettings,
33    http_client: Arc<HttpClient>,
34}
35
36impl BucketBuilder {
37    pub(crate) fn new(name: String, http_client: Arc<HttpClient>) -> Self {
38        Self {
39            name,
40            exist_ok: false,
41            settings: BucketSettings::default(),
42            http_client,
43        }
44    }
45
46    /// Don't fail if the bucket already exists.
47    pub fn exist_ok(mut self, exist_ok: bool) -> Self {
48        self.exist_ok = exist_ok;
49        self
50    }
51
52    /// Set the quota type.
53    pub fn quota_type(mut self, quota_type: QuotaType) -> Self {
54        self.settings.quota_type = Some(quota_type);
55        self
56    }
57
58    /// Set the quota size.
59    pub fn quota_size(mut self, quota_size: u64) -> Self {
60        self.settings.quota_size = Some(quota_size);
61        self
62    }
63
64    /// Set the max block size.
65    pub fn max_block_size(mut self, max_block_size: u64) -> Self {
66        self.settings.max_block_size = Some(max_block_size);
67        self
68    }
69
70    /// Set the max block records.
71    pub fn max_block_records(mut self, max_block_records: u64) -> Self {
72        self.settings.max_block_records = Some(max_block_records);
73        self
74    }
75
76    /// Set and overwrite the settings of the bucket.
77    pub fn settings(mut self, settings: BucketSettings) -> Self {
78        self.settings = settings;
79        self
80    }
81
82    /// Create the bucket.
83    pub async fn send(self) -> Result<Bucket> {
84        let result = self
85            .http_client
86            .send_json(Method::POST, &format!("/b/{}", self.name), self.settings)
87            .await;
88        match result {
89            Ok(_) => {}
90            Err(e) => {
91                if !(self.exist_ok && e.status() == ErrorCode::Conflict) {
92                    return Err(e);
93                }
94            }
95        }
96
97        Ok(Bucket {
98            name: self.name.clone(),
99            http_client: Arc::clone(&self.http_client),
100        })
101    }
102}
103
104impl Bucket {
105    /// Name of the bucket.
106    pub fn name(&self) -> &str {
107        &self.name
108    }
109
110    /// URL of the server.
111    pub fn server_url(&self) -> &str {
112        &self.http_client.url()
113    }
114
115    /// Remove the bucket.
116    ///
117    /// # Returns
118    ///
119    /// Returns an error if the bucket could not be removed.
120    pub async fn remove(&self) -> Result<()> {
121        let request = self
122            .http_client
123            .request(Method::DELETE, &format!("/b/{}", self.name));
124        self.http_client.send_request(request).await?;
125        Ok(())
126    }
127
128    /// Get the settings of the bucket.
129    ///
130    /// # Returns
131    ///
132    /// Return settings of the bucket
133    pub async fn settings(&self) -> Result<BucketSettings> {
134        Ok(self.full_info().await?.settings)
135    }
136
137    /// Set the settings of the bucket.
138    ///
139    /// # Arguments
140    ///
141    /// * `settings` - The new settings of the bucket.
142    ///
143    /// # Returns
144    ///
145    ///  Returns an error if the bucket could not be found.
146    pub async fn set_settings(&self, settings: BucketSettings) -> Result<()> {
147        self.http_client
148            .send_json::<BucketSettings>(Method::PUT, &format!("/b/{}", self.name), settings)
149            .await
150    }
151
152    /// Get full information about the bucket (stats, settings, entries).
153    pub async fn full_info(&self) -> Result<FullBucketInfo> {
154        self.http_client
155            .send_and_receive_json::<(), FullBucketInfo>(
156                Method::GET,
157                &format!("/b/{}", self.name),
158                None,
159            )
160            .await
161    }
162
163    /// Get bucket stats.
164    pub async fn info(&self) -> Result<BucketInfo> {
165        Ok(self.full_info().await?.info)
166    }
167
168    /// Get bucket entries.
169    pub async fn entries(&self) -> Result<Vec<EntryInfo>> {
170        Ok(self.full_info().await?.entries)
171    }
172
173    /// Rename an entry in the bucket.
174    ///
175    /// # Arguments
176    ///
177    /// * `entry` - The entry to rename.
178    /// * `new_name` - The new name of the entry.
179    ///
180    /// # Returns
181    ///
182    /// Returns an error if the entry could not be renamed.
183    pub async fn rename_entry(&self, entry: &str, new_name: &str) -> Result<()> {
184        self.http_client
185            .send_json(
186                Method::PUT,
187                &format!("/b/{}/{}/rename", self.name, entry),
188                RenameEntry {
189                    new_name: new_name.to_string(),
190                },
191            )
192            .await
193    }
194
195    /// Rename the bucket.
196    ///
197    /// # Arguments
198    ///
199    /// * `new_name` - The new name of the bucket.
200    ///
201    /// # Returns
202    ///
203    /// Returns an error if the bucket could not be renamed.
204    pub async fn rename(&mut self, new_name: &str) -> Result<()> {
205        self.http_client
206            .send_json(
207                Method::PUT,
208                &format!("/b/{}/rename", self.name),
209                RenameEntry {
210                    new_name: new_name.to_string(),
211                },
212            )
213            .await?;
214        self.name = new_name.to_string();
215        Ok(())
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use rstest::{fixture, rstest};
222
223    use reduct_base::error::ErrorCode;
224
225    use crate::client::tests::{bucket_settings, client};
226    use crate::client::ReductClient;
227
228    use super::*;
229
230    #[rstest]
231    #[tokio::test]
232    async fn test_bucket_full_info(#[future] bucket: Bucket) {
233        let bucket = bucket.await;
234        let FullBucketInfo {
235            info,
236            settings,
237            entries,
238        } = bucket.full_info().await.unwrap();
239        assert_eq!(info, bucket.info().await.unwrap());
240        assert_eq!(settings, bucket.settings().await.unwrap());
241        assert_eq!(entries, bucket.entries().await.unwrap());
242    }
243
244    #[rstest]
245    #[tokio::test]
246    async fn test_bucket_settings(#[future] bucket: Bucket, bucket_settings: BucketSettings) {
247        let bucket = bucket.await;
248        let settings = bucket.settings().await.unwrap();
249        assert_eq!(settings, bucket_settings);
250
251        let new_settings = BucketSettings {
252            quota_size: Some(100),
253            ..BucketSettings::default()
254        };
255
256        bucket.set_settings(new_settings.clone()).await.unwrap();
257        assert_eq!(
258            bucket.settings().await.unwrap(),
259            BucketSettings {
260                quota_size: new_settings.quota_size,
261                ..bucket_settings
262            }
263        );
264    }
265
266    #[rstest]
267    #[tokio::test]
268    async fn test_bucket_remove(#[future] bucket: Bucket) {
269        let bucket = bucket.await;
270        bucket.remove().await.unwrap();
271
272        assert_eq!(
273            bucket.info().await.err().unwrap().status,
274            ErrorCode::NotFound
275        );
276    }
277
278    #[rstest]
279    #[tokio::test]
280    async fn test_bucket_rename_entry(#[future] bucket: Bucket) {
281        let bucket = bucket.await;
282        bucket.rename_entry("entry-1", "new-entry-1").await.unwrap();
283        let entries = bucket.entries().await.unwrap();
284        assert!(entries.iter().any(|entry| entry.name == "new-entry-1"));
285    }
286
287    #[rstest]
288    #[tokio::test]
289    async fn test_bucket_rename(#[future] bucket: Bucket) {
290        let mut bucket = bucket.await;
291        bucket.rename("new-bucket").await.unwrap();
292        assert_eq!(bucket.name(), "new-bucket");
293        assert!(bucket.info().await.is_ok());
294    }
295
296    #[fixture]
297    pub async fn bucket(#[future] client: ReductClient) -> Bucket {
298        client.await.get_bucket("test-bucket-1").await.unwrap()
299    }
300}