storage_client_interface/
s3.rs

1use async_trait::async_trait;
2use aws_config::meta::region::RegionProviderChain;
3use aws_sdk_s3 as s3;
4use s3::{
5    error::SdkError,
6    operation::{
7        delete_object::DeleteObjectError, get_object::GetObjectError, put_object::PutObjectError,
8    },
9    primitives::ByteStream,
10    Client,
11};
12use thiserror::Error;
13
14use super::{StorageClientError, StorageClientInterface};
15
16#[derive(Error, Debug)]
17pub enum ClientError {
18    #[error("GetObject Error: {0}")]
19    GetObject(#[from] SdkError<GetObjectError>),
20    #[error("PutObject Error: {0}")]
21    PutObject(#[from] SdkError<PutObjectError>),
22    #[error("DeleteObject Error: {0}")]
23    DeleteObject(#[from] SdkError<DeleteObjectError>),
24    #[error("S3 Client Error - {0}")]
25    General(String),
26}
27
28impl From<ClientError> for StorageClientError {
29    fn from(error: ClientError) -> Self {
30        match error {
31            ClientError::GetObject(err) => StorageClientError::GetObject(err.to_string()),
32            ClientError::PutObject(err) => StorageClientError::PutObject(err.to_string()),
33            ClientError::DeleteObject(err) => StorageClientError::DeleteObject(err.to_string()),
34            ClientError::General(err) => StorageClientError::General(err),
35        }
36    }
37}
38#[derive(Clone, Debug)]
39pub struct StorageClient {
40    bucket: String,
41    client: s3::Client,
42}
43
44impl StorageClient {
45    #[allow(unused)]
46    pub async fn new(bucket: String) -> Self {
47        let region_provider = RegionProviderChain::default_provider().or_else("us-east-1");
48        let config = aws_config::from_env().region(region_provider).load().await;
49        let client = Client::new(&config);
50        Self { bucket, client }
51    }
52}
53
54#[async_trait]
55impl StorageClientInterface for StorageClient {
56    async fn get_object(&self, key: String) -> Result<Option<String>, StorageClientError> {
57        let object_res = self
58            .client
59            .get_object()
60            .bucket(&self.bucket)
61            .key(key)
62            .send()
63            .await;
64
65        let object = match object_res {
66            Ok(object) => object,
67            Err(err) => match err.into_service_error() {
68                GetObjectError::NoSuchKey(_) => return Ok(None),
69                err => {
70                    println!("Error getting object from S3: {:?}", err);
71                    return Err(StorageClientError::GetObject(err.to_string()));
72                }
73            },
74        };
75
76        let body_bytes = object
77            .body
78            .collect()
79            .await
80            .map_err(|err| ClientError::General(err.to_string()))?
81            .to_vec();
82
83        let body = String::from_utf8(body_bytes)
84            .map_err(|err| ClientError::General(format!("Failed to parse object body: {err}")))?;
85
86        Ok(Some(body))
87    }
88
89    async fn put_object(&self, key: String, body: String) -> Result<(), StorageClientError> {
90        let body_bytes = body.as_bytes().to_vec();
91
92        let _ = self
93            .client
94            .put_object()
95            .bucket(&self.bucket)
96            .key(key)
97            .body(ByteStream::from(body_bytes))
98            .send()
99            .await
100            .map_err(|err| {
101                println!("Error puttin object to S3: {:?}", err);
102                ClientError::PutObject(err)
103            })?;
104
105        Ok(())
106    }
107
108    async fn delete_object(&self, key: String) -> Result<(), StorageClientError> {
109        let _ = self
110            .client
111            .delete_object()
112            .bucket(&self.bucket)
113            .key(key)
114            .send()
115            .await
116            .map_err(|err| {
117                println!("Error deleting object in S3: {:?}", err);
118                ClientError::DeleteObject(err)
119            })?;
120
121        Ok(())
122    }
123}