oci_api/services/object_storage/
client.rs

1//! Object Storage client
2
3use crate::client::Oci;
4use crate::error::{OciError, Result};
5use crate::services::object_storage::models::*;
6use serde::Deserialize;
7use serde::Serialize;
8use serde::de::DeserializeOwned;
9
10/// Object Storage Service Client
11#[derive(Clone)]
12pub struct ObjectStorage {
13    /// OCI HTTP client
14    oci_client: Oci,
15    /// Namespace name
16    pub namespace: String,
17    /// Endpoint (Host)
18    endpoint: String,
19    /// Protocol (http/https)
20    protocol: String,
21}
22
23impl ObjectStorage {
24    /// Create new Object Storage client
25    ///
26    /// # Arguments
27    /// * `oci_client` - OCI HTTP client
28    /// * `namespace` - Object Storage Namespace
29    pub fn new(oci_client: &Oci, namespace: impl Into<String>) -> Self {
30        let region = oci_client.region().to_string();
31        let endpoint = format!("objectstorage.{region}.oraclecloud.com");
32
33        Self {
34            oci_client: oci_client.clone(),
35            namespace: namespace.into(),
36            endpoint,
37            protocol: "https".to_string(),
38        }
39    }
40
41    /// Get Bucket
42    ///
43    /// # Arguments
44    /// * `bucket_name` - Bucket name
45    pub async fn get_bucket(&self, bucket_name: &str) -> Result<Bucket> {
46        // Verify bucket exists
47        let path = format!("/n/{}/b/{}/", self.namespace, bucket_name);
48        let url = format!("{}://{}{}", self.protocol, self.endpoint, path);
49
50        let (date_header, auth_header) =
51            self.oci_client
52                .signer()
53                .sign_request("GET", &path, &self.endpoint, None)?;
54
55        let response = self
56            .oci_client
57            .client()
58            .get(&url)
59            .header("host", &self.endpoint)
60            .header("date", &date_header)
61            .header("authorization", &auth_header)
62            .send()
63            .await?;
64
65        if !response.status().is_success() {
66            let status = response.status();
67            let body = response.text().await?;
68            return Err(OciError::ApiError {
69                code: status.to_string(),
70                message: body,
71            });
72        }
73
74        // If successful, return Bucket struct
75        Ok(Bucket {
76            oci_client: self.oci_client.clone(),
77            namespace: self.namespace.clone(),
78            name: bucket_name.to_string(),
79            endpoint: self.endpoint.clone(),
80            protocol: self.protocol.clone(),
81        })
82    }
83}
84
85/// Bucket
86#[derive(Clone)]
87pub struct Bucket {
88    /// OCI HTTP client
89    oci_client: Oci,
90    /// Namespace
91    pub namespace: String,
92    /// Bucket name
93    pub name: String,
94    /// Endpoint (Host)
95    endpoint: String,
96    /// Protocol (http/https)
97    protocol: String,
98}
99
100impl Bucket {
101    // Helper for making requests
102    async fn request<T, B>(&self, method: &str, path: &str, body: Option<B>) -> Result<T>
103    where
104        T: DeserializeOwned,
105        B: Serialize,
106    {
107        let url = format!("{}://{}{}", self.protocol, self.endpoint, path);
108        let body_str = if let Some(b) = &body {
109            Some(serde_json::to_string(b)?)
110        } else {
111            None
112        };
113
114        let (date_header, auth_header) = self.oci_client.signer().sign_request(
115            method,
116            path,
117            &self.endpoint,
118            body_str.as_deref(),
119        )?;
120
121        let mut request_builder = match method {
122            "GET" => self.oci_client.client().get(&url),
123            "POST" => self.oci_client.client().post(&url),
124            "PUT" => self.oci_client.client().put(&url),
125            "DELETE" => self.oci_client.client().delete(&url),
126            _ => return Err(OciError::Other(format!("Unsupported method: {}", method))),
127        };
128
129        request_builder = request_builder
130            .header("host", &self.endpoint)
131            .header("date", &date_header)
132            .header("authorization", &auth_header);
133
134        if let Some(b_str) = body_str {
135            request_builder = request_builder
136                .header("content-type", "application/json")
137                .header("content-length", b_str.len().to_string())
138                .body(b_str);
139        }
140
141        let response = request_builder.send().await?;
142
143        if !response.status().is_success() {
144            let status = response.status();
145            let body = response.text().await?;
146            return Err(OciError::ApiError {
147                code: status.to_string(),
148                message: body,
149            });
150        }
151
152        let text = response.text().await?;
153        serde_json::from_str(&text).map_err(Into::into)
154    }
155
156    async fn request_no_content<B>(&self, method: &str, path: &str, body: Option<B>) -> Result<()>
157    where
158        B: Serialize,
159    {
160        let url = format!("{}://{}{}", self.protocol, self.endpoint, path);
161        let body_str = if let Some(b) = &body {
162            Some(serde_json::to_string(b)?)
163        } else {
164            None
165        };
166
167        let (date_header, auth_header) = self.oci_client.signer().sign_request(
168            method,
169            path,
170            &self.endpoint,
171            body_str.as_deref(),
172        )?;
173
174        let mut request_builder = match method {
175            "GET" => self.oci_client.client().get(&url),
176            "POST" => self.oci_client.client().post(&url),
177            "PUT" => self.oci_client.client().put(&url),
178            "DELETE" => self.oci_client.client().delete(&url),
179            _ => return Err(OciError::Other(format!("Unsupported method: {}", method))),
180        };
181
182        request_builder = request_builder
183            .header("host", &self.endpoint)
184            .header("date", &date_header)
185            .header("authorization", &auth_header);
186
187        if let Some(b_str) = body_str {
188            request_builder = request_builder
189                .header("content-type", "application/json")
190                .header("content-length", b_str.len().to_string())
191                .body(b_str);
192        }
193
194        let response = request_builder.send().await?;
195
196        if !response.status().is_success() {
197            let status = response.status();
198            let body = response.text().await?;
199            return Err(OciError::ApiError {
200                code: status.to_string(),
201                message: body,
202            });
203        }
204
205        Ok(())
206    }
207
208    /// Put Object
209    ///
210    /// # Arguments
211    /// * `object_name` - Object name
212    /// * `content` - Object content
213    pub async fn put_object(&self, object_name: &str, content: &str) -> Result<Object> {
214        let path = format!("/n/{}/b/{}/o/{}", self.namespace, self.name, object_name);
215        let url = format!("{}://{}{}", self.protocol, self.endpoint, path);
216
217        let (date_header, auth_header) =
218            self.oci_client
219                .signer()
220                .sign_request("PUT", &path, &self.endpoint, Some(content))?;
221
222        let response = self
223            .oci_client
224            .client()
225            .put(&url)
226            .header("host", &self.endpoint)
227            .header("date", &date_header)
228            .header("authorization", &auth_header)
229            .header("content-length", content.len().to_string())
230            .body(content.to_string())
231            .send()
232            .await?;
233
234        if !response.status().is_success() {
235            let status = response.status();
236            let body = response.text().await?;
237            return Err(OciError::ApiError {
238                code: status.to_string(),
239                message: body,
240            });
241        }
242
243        Ok(Object {
244            name: object_name.to_string(),
245            value: content.to_string(),
246        })
247    }
248
249    /// Get Object
250    ///
251    /// # Arguments
252    /// * `object_name` - Object name
253    pub async fn get_object(&self, object_name: &str) -> Result<Object> {
254        let path = format!("/n/{}/b/{}/o/{}", self.namespace, self.name, object_name);
255        let url = format!("{}://{}{}", self.protocol, self.endpoint, path);
256
257        let (date_header, auth_header) =
258            self.oci_client
259                .signer()
260                .sign_request("GET", &path, &self.endpoint, None)?;
261
262        let response = self
263            .oci_client
264            .client()
265            .get(&url)
266            .header("host", &self.endpoint)
267            .header("date", &date_header)
268            .header("authorization", &auth_header)
269            .send()
270            .await?;
271
272        if !response.status().is_success() {
273            let status = response.status();
274            let body = response.text().await?;
275            return Err(OciError::ApiError {
276                code: status.to_string(),
277                message: body,
278            });
279        }
280
281        let value = response.text().await?;
282
283        Ok(Object {
284            name: object_name.to_string(),
285            value,
286        })
287    }
288
289    /// Get or Create Object
290    ///
291    /// Tries to get the object. If it doesn't exist (404), creates it with the provided content.
292    ///
293    /// # Arguments
294    /// * `object_name` - Object name
295    /// * `content` - Content to use if object needs to be created
296    pub async fn get_or_create_object(&self, object_name: &str, content: &str) -> Result<Object> {
297        match self.get_object(object_name).await {
298            Ok(obj) => Ok(obj),
299            Err(OciError::ApiError { code, .. }) if code.contains("404") => {
300                self.put_object(object_name, content).await
301            }
302            Err(e) => Err(e),
303        }
304    }
305
306    /// Get Retention Rules
307    pub async fn get_retention_rules(&self) -> Result<Vec<RetentionRule>> {
308        let path = format!("/n/{}/b/{}/retentionRules", self.namespace, self.name);
309
310        #[derive(Deserialize)]
311        struct ResponseWrapper {
312            items: Vec<RetentionRule>,
313        }
314
315        let wrapper: ResponseWrapper = self
316            .request::<ResponseWrapper, ()>("GET", &path, None)
317            .await?;
318        Ok(wrapper.items)
319    }
320
321    /// Create Retention Rule
322    pub async fn create_retention_rule(
323        &self,
324        details: RetentionRuleDetails,
325    ) -> Result<RetentionRule> {
326        let path = format!("/n/{}/b/{}/retentionRules", self.namespace, self.name);
327        self.request("POST", &path, Some(details)).await
328    }
329
330    /// Get Retention Rule
331    pub async fn get_retention_rule(&self, rule_id: &str) -> Result<RetentionRule> {
332        let path = format!(
333            "/n/{}/b/{}/retentionRules/{}",
334            self.namespace, self.name, rule_id
335        );
336        self.request("GET", &path, None::<()>).await
337    }
338
339    /// Update Retention Rule
340    pub async fn update_retention_rule(
341        &self,
342        rule_id: &str,
343        details: RetentionRuleDetails,
344    ) -> Result<RetentionRule> {
345        let path = format!(
346            "/n/{}/b/{}/retentionRules/{}",
347            self.namespace, self.name, rule_id
348        );
349        self.request("PUT", &path, Some(details)).await
350    }
351
352    /// Delete Retention Rule
353    pub async fn delete_retention_rule(&self, rule_id: &str) -> Result<()> {
354        let path = format!(
355            "/n/{}/b/{}/retentionRules/{}",
356            self.namespace, self.name, rule_id
357        );
358        self.request_no_content("DELETE", &path, None::<()>).await
359    }
360}
361
362#[cfg(test)]
363#[path = "tests.rs"]
364mod tests;