elizaos_plugin_s3_storage/lib.rs
1//! # S3 Storage Plugin
2//!
3//! A Rust plugin for uploading and managing files in Amazon S3 or S3-compatible
4//! storage services. This plugin provides a high-level API for common storage
5//! operations including file uploads, byte uploads, JSON uploads, and signed URL
6//! generation.
7//!
8//! ## Features
9//!
10//! - File upload from local paths
11//! - Direct byte data upload
12//! - JSON serialization and upload
13//! - Pre-signed URL generation for secure access
14//! - Support for custom S3-compatible endpoints
15//!
16//! ## Example
17//!
18//! ```rust,no_run
19//! use elizaos_plugin_s3_storage::{S3StoragePlugin, S3StorageConfig};
20//!
21//! async fn example() -> Result<(), Box<dyn std::error::Error>> {
22//! let config = S3StorageConfig::new(
23//! "access_key",
24//! "secret_key",
25//! "us-east-1",
26//! "my-bucket"
27//! );
28//! let plugin = S3StoragePlugin::new(config).await?;
29//! Ok(())
30//! }
31//! ```
32
33#![warn(missing_docs)]
34
35pub mod client;
36pub mod error;
37pub mod service;
38pub mod types;
39
40pub use client::S3StorageClient;
41pub use error::{Result, S3StorageError};
42pub use service::AwsS3Service;
43pub use types::*;
44
45use anyhow::Result as AnyhowResult;
46
47/// High-level S3 storage plugin for file and data management.
48///
49/// This struct wraps an [`S3StorageClient`] and provides convenient methods
50/// for uploading files, bytes, and JSON data to S3-compatible storage.
51pub struct S3StoragePlugin {
52 client: S3StorageClient,
53}
54
55impl S3StoragePlugin {
56 /// Creates a new S3 storage plugin with the provided configuration.
57 ///
58 /// # Arguments
59 ///
60 /// * `config` - The S3 storage configuration including credentials and bucket info
61 ///
62 /// # Returns
63 ///
64 /// A `Result` containing the initialized plugin or an error if initialization fails.
65 pub async fn new(config: S3StorageConfig) -> Result<Self> {
66 let client = S3StorageClient::new(config).await?;
67 Ok(Self { client })
68 }
69
70 /// Uploads a file from the local filesystem to S3.
71 ///
72 /// # Arguments
73 ///
74 /// * `file_path` - Path to the local file to upload
75 /// * `sub_directory` - Optional subdirectory within the bucket
76 /// * `use_signed_url` - Whether to generate a signed URL for the uploaded file
77 /// * `expires_in` - Expiration time in seconds for the signed URL
78 ///
79 /// # Returns
80 ///
81 /// A `Result` containing the upload result with the file's S3 key and optional signed URL.
82 pub async fn upload_file(
83 &self,
84 file_path: &str,
85 sub_directory: Option<&str>,
86 use_signed_url: bool,
87 expires_in: u64,
88 ) -> Result<UploadResult> {
89 self.client
90 .upload_file(file_path, sub_directory, use_signed_url, expires_in)
91 .await
92 }
93
94 /// Uploads raw byte data to S3.
95 ///
96 /// # Arguments
97 ///
98 /// * `data` - The byte data to upload
99 /// * `file_name` - Name for the file in S3
100 /// * `content_type` - MIME type of the content (e.g., "image/png")
101 /// * `sub_directory` - Optional subdirectory within the bucket
102 /// * `use_signed_url` - Whether to generate a signed URL for the uploaded file
103 /// * `expires_in` - Expiration time in seconds for the signed URL
104 ///
105 /// # Returns
106 ///
107 /// A `Result` containing the upload result with the file's S3 key and optional signed URL.
108 pub async fn upload_bytes(
109 &self,
110 data: bytes::Bytes,
111 file_name: &str,
112 content_type: &str,
113 sub_directory: Option<&str>,
114 use_signed_url: bool,
115 expires_in: u64,
116 ) -> Result<UploadResult> {
117 self.client
118 .upload_bytes(
119 data,
120 file_name,
121 content_type,
122 sub_directory,
123 use_signed_url,
124 expires_in,
125 )
126 .await
127 }
128
129 /// Uploads JSON data to S3.
130 ///
131 /// The data is serialized to JSON format before uploading.
132 ///
133 /// # Arguments
134 ///
135 /// * `json_data` - The JSON value to upload
136 /// * `file_name` - Optional custom filename (auto-generated if not provided)
137 /// * `sub_directory` - Optional subdirectory within the bucket
138 /// * `use_signed_url` - Whether to generate a signed URL for the uploaded file
139 /// * `expires_in` - Expiration time in seconds for the signed URL
140 ///
141 /// # Returns
142 ///
143 /// A `Result` containing the JSON upload result with additional metadata.
144 pub async fn upload_json(
145 &self,
146 json_data: &serde_json::Value,
147 file_name: Option<&str>,
148 sub_directory: Option<&str>,
149 use_signed_url: bool,
150 expires_in: u64,
151 ) -> Result<JsonUploadResult> {
152 self.client
153 .upload_json(
154 json_data,
155 file_name,
156 sub_directory,
157 use_signed_url,
158 expires_in,
159 )
160 .await
161 }
162
163 /// Generates a pre-signed URL for accessing an existing S3 object.
164 ///
165 /// # Arguments
166 ///
167 /// * `key` - The S3 object key
168 /// * `expires_in` - Expiration time in seconds for the signed URL
169 ///
170 /// # Returns
171 ///
172 /// A `Result` containing the pre-signed URL string.
173 pub async fn generate_signed_url(&self, key: &str, expires_in: u64) -> Result<String> {
174 self.client.generate_signed_url(key, expires_in).await
175 }
176
177 /// Returns a reference to the underlying S3 storage client.
178 ///
179 /// This allows direct access to the client for advanced operations
180 /// not covered by the plugin's high-level API.
181 pub fn client(&self) -> &S3StorageClient {
182 &self.client
183 }
184}
185
186/// Creates an S3 storage plugin using environment variables for configuration.
187///
188/// This function reads the following environment variables:
189/// - `AWS_ACCESS_KEY_ID` (required)
190/// - `AWS_SECRET_ACCESS_KEY` (required)
191/// - `AWS_REGION` (required)
192/// - `AWS_S3_BUCKET` (required)
193/// - `AWS_S3_UPLOAD_PATH` (optional)
194/// - `AWS_S3_ENDPOINT` (optional, for S3-compatible services)
195///
196/// # Returns
197///
198/// A `Result` containing the initialized plugin or an error if required
199/// environment variables are missing.
200pub async fn get_s3_storage_plugin() -> AnyhowResult<S3StoragePlugin> {
201 let access_key = std::env::var("AWS_ACCESS_KEY_ID")
202 .map_err(|_| anyhow::anyhow!("AWS_ACCESS_KEY_ID environment variable is required"))?;
203 let secret_key = std::env::var("AWS_SECRET_ACCESS_KEY")
204 .map_err(|_| anyhow::anyhow!("AWS_SECRET_ACCESS_KEY environment variable is required"))?;
205 let region = std::env::var("AWS_REGION")
206 .map_err(|_| anyhow::anyhow!("AWS_REGION environment variable is required"))?;
207 let bucket = std::env::var("AWS_S3_BUCKET")
208 .map_err(|_| anyhow::anyhow!("AWS_S3_BUCKET environment variable is required"))?;
209
210 let mut config = S3StorageConfig::new(&access_key, &secret_key, ®ion, &bucket);
211
212 if let Ok(upload_path) = std::env::var("AWS_S3_UPLOAD_PATH") {
213 config = config.upload_path(&upload_path);
214 }
215
216 if let Ok(endpoint) = std::env::var("AWS_S3_ENDPOINT") {
217 config = config.endpoint(&endpoint);
218 }
219
220 S3StoragePlugin::new(config)
221 .await
222 .map_err(|e| anyhow::anyhow!("Failed to create S3 storage plugin: {}", e))
223}