# Crypsol Storage
A production-ready AWS S3 storage library for Rust services with image processing, validation, and thumbnail generation.
[](https://crates.io/crates/crypsol_storage)
[](https://docs.rs/crypsol_storage)
[](https://opensource.org/licenses/MIT)
## Features
- **Image Upload with Processing**: Automatic resizing and thumbnail generation
- **Multiple Format Support**: JPEG, PNG, GIF, WebP
- **High-Quality Resizing**: Uses Lanczos3 filter for optimal quality
- **Configurable Dimensions**: Customize image and thumbnail sizes
- **File Validation**: Content-type and file size validation
- **Presigned URLs**: Generate temporary access URLs for private objects
- **Async/Await**: Built on Tokio for high-performance async operations
- **Serde Support**: Optional serialization support for API responses
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
crypsol_storage = "0.1"
```
With Serde support:
```toml
[dependencies]
crypsol_storage = { version = "0.1", features = ["serde"] }
```
## Environment Variables
| `S3_AWS_ACCESS_KEY` | Yes | - | AWS Access Key ID |
| `S3_AWS_SECRET_KEY` | Yes | - | AWS Secret Access Key |
| `S3_AWS_REGION` | No | `us-east-1` | AWS Region |
| `S3_BUCKET_NAME` | No | `crypsol-storage` | S3 Bucket Name |
| `S3_PUBLIC_BASE_URL` | No | Auto-generated | CDN or custom domain URL |
| `S3_MAX_FILE_SIZE_MB` | No | `5` | Maximum file size in MB |
## Quick Start
### Upload Image with Thumbnail
```rust
use crypsol_storage::{upload_image_with_config, ImageUploadConfig, Error};
#[tokio::main]
async fn main() -> Result<(), Error> {
// Read image bytes
let image_data = std::fs::read("photo.jpg")?;
// Upload with default config (200x200 main, 50x50 thumbnail)
let config = ImageUploadConfig::default();
let result = upload_image_with_config(&image_data, "image/jpeg", &config).await?;
println!("Image URL: {}", result.url);
println!("Thumbnail URL: {}", result.thumbnail_url);
Ok(())
}
```
### Upload with Custom Dimensions
```rust
use crypsol_storage::{upload_image_with_config, ImageUploadConfig};
let config = ImageUploadConfig {
width: 800,
height: 600,
thumbnail_width: 150,
thumbnail_height: 150,
folder: "products".to_string(),
maintain_aspect_ratio: true,
};
let result = upload_image_with_config(&image_data, "image/png", &config).await?;
```
### Upload with Custom S3 Key
```rust
use crypsol_storage::upload_image_with_key;
// Full control over the S3 path
let result = upload_image_with_key(
&image_data,
"image/jpeg",
"users/123/profile.jpg", // Custom key
200, // width
200, // height
50, // thumbnail_width
50, // thumbnail_height
).await?;
```
### Upload Raw File (No Processing)
```rust
use crypsol_storage::upload_file;
let pdf_data = std::fs::read("document.pdf")?;
let result = upload_file(&pdf_data, "application/pdf", "documents", "pdf").await?;
println!("File URL: {}", result.url);
```
### Delete Image with Thumbnail
```rust
use crypsol_storage::delete_image_with_thumbnail;
// Deletes both main image and thumbnail
delete_image_with_thumbnail("profiles/2025/01/21/abc123.jpg").await?;
```
### Check if File Exists
```rust
use crypsol_storage::file_exists;
if file_exists("profiles/2025/01/21/abc123.jpg").await? {
println!("File exists!");
}
```
### Generate Presigned URL
```rust
use crypsol_storage::generate_presigned_url;
// Generate URL valid for 1 hour (3600 seconds)
let url = generate_presigned_url("private/document.pdf", 3600).await?;
println!("Temporary URL: {}", url);
```
### Extract Key from URL
```rust
use crypsol_storage::extract_key_from_url;
let url = "https://bucket.s3.us-east-1.amazonaws.com/profiles/image.jpg";
if let Some(key) = extract_key_from_url(url) {
println!("Key: {}", key); // "profiles/image.jpg"
}
```
## Validation
### Content Type Validation
```rust
use crypsol_storage::{validate_content_type, ALLOWED_IMAGE_TYPES};
// Check if content type is allowed
validate_content_type("image/jpeg")?; // Ok
validate_content_type("application/pdf")?; // Error
// Allowed types: image/jpeg, image/png, image/gif, image/webp
println!("Allowed types: {:?}", ALLOWED_IMAGE_TYPES);
```
### File Size Validation
```rust
use crypsol_storage::{validate_file_size, get_max_file_size};
let file_size = 2 * 1024 * 1024; // 2MB
validate_file_size(file_size)?; // Ok if under limit
println!("Max size: {} bytes", get_max_file_size());
```
## Error Handling
```rust
use crypsol_storage::Error;
match upload_image_with_config(&data, content_type, &config).await {
Ok(result) => println!("Uploaded: {}", result.url),
Err(Error::InvalidFileType(got, allowed)) => {
println!("Invalid type: {}. Allowed: {}", got, allowed);
}
Err(Error::FileTooLarge(size, max)) => {
println!("File too large: {} bytes (max: {})", size, max);
}
Err(Error::ImageProcessing(msg)) => {
println!("Image processing failed: {}", msg);
}
Err(Error::AwsS3(msg)) => {
println!("S3 error: {}", msg);
}
Err(e) => println!("Error: {}", e),
}
```
## S3 Bucket Configuration
### Required IAM Permissions
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:HeadObject"
],
"Resource": "arn:aws:s3:::your-bucket-name/*"
}
]
}
```
### CORS Configuration (for browser uploads)
```json
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
"AllowedOrigins": ["https://your-domain.com"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]
```
## License
MIT License - see [LICENSE](LICENSE) for details.
## Author
Zuhair Thabit - [admin@crypsol.tech](mailto:admin@crypsol.tech)