avl_storage/
lib.rs

1//! AVL Storage - S3-compatible object storage optimized for Brazil
2//!
3//! # Features
4//!
5//! - **S3-compatible API** (drop-in replacement)
6//! - **Automatic compression** via `avila-compress`
7//! - **3-8ms latency** in Brazil
8//! - **50% cheaper** than AWS S3
9//! - **Multipart uploads** for large files
10//!
11//! # Quick Start
12//!
13//! ```no_run
14//! use avl_storage::{StorageClient, PutObjectRequest};
15//!
16//! #[tokio::main]
17//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
18//!     let client = StorageClient::connect("https://storage.avila.cloud").await?;
19//!
20//!     // Upload file
21//!     client.put_object(PutObjectRequest {
22//!         bucket: "my-bucket".to_string(),
23//!         key: "file.txt".to_string(),
24//!         body: b"Hello AVL Storage!".to_vec(),
25//!         content_type: Some("text/plain".to_string()),
26//!         ..Default::default()
27//!     }).await?;
28//!
29//!     Ok(())
30//! }
31//! ```
32
33use std::collections::HashMap;
34use serde::{Deserialize, Serialize};
35use thiserror::Error;
36
37pub mod client;
38pub mod multipart;
39pub mod object;
40
41pub use client::StorageClient;
42
43/// AVL Storage error types
44#[derive(Error, Debug)]
45pub enum Error {
46    #[error("Bucket not found: {bucket}")]
47    BucketNotFound { bucket: String },
48
49    #[error("Object not found: {bucket}/{key}")]
50    ObjectNotFound { bucket: String, key: String },
51
52    #[error("Invalid bucket name: {name}")]
53    InvalidBucketName { name: String },
54
55    #[error("Invalid object key: {key}")]
56    InvalidKey { key: String },
57
58    #[error("Storage full: {used} / {capacity} bytes")]
59    StorageFull { used: u64, capacity: u64 },
60
61    #[error("Compression error: {0}")]
62    Compression(String),
63
64    #[error("Network error: {0}")]
65    Network(#[from] std::io::Error),
66
67    #[error("Serialization error: {0}")]
68    Serialization(#[from] serde_json::Error),
69
70    #[error("Internal error: {0}")]
71    Internal(String),
72}
73
74pub type Result<T> = std::result::Result<T, Error>;
75
76/// Storage class for objects
77#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
78pub enum StorageClass {
79    /// Standard storage (hot data, LZ4 compression)
80    Standard,
81
82    /// Infrequent access (warm data)
83    InfrequentAccess,
84
85    /// Archive storage (cold data, Zstd compression)
86    Archive,
87}
88
89impl Default for StorageClass {
90    fn default() -> Self {
91        Self::Standard
92    }
93}
94
95/// Request to put an object
96#[derive(Debug, Clone)]
97pub struct PutObjectRequest {
98    pub bucket: String,
99    pub key: String,
100    pub body: Vec<u8>,
101    pub content_type: Option<String>,
102    pub metadata: HashMap<String, String>,
103    pub storage_class: Option<StorageClass>,
104}
105
106impl Default for PutObjectRequest {
107    fn default() -> Self {
108        Self {
109            bucket: String::new(),
110            key: String::new(),
111            body: Vec::new(),
112            content_type: None,
113            metadata: HashMap::new(),
114            storage_class: None,
115        }
116    }
117}
118
119/// Response from put object
120#[derive(Debug, Clone)]
121pub struct PutObjectResponse {
122    pub etag: String,
123    pub version_id: Option<String>,
124}
125
126/// Response from get object
127#[derive(Debug, Clone)]
128pub struct GetObjectResponse {
129    pub body: Vec<u8>,
130    pub content_type: String,
131    pub content_length: usize,
132    pub etag: String,
133    pub last_modified: chrono::DateTime<chrono::Utc>,
134    pub metadata: HashMap<String, String>,
135}
136
137/// Object metadata
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct ObjectInfo {
140    pub key: String,
141    pub size: usize,
142    pub etag: String,
143    pub last_modified: chrono::DateTime<chrono::Utc>,
144    pub storage_class: StorageClass,
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn test_storage_class_default() {
153        let class = StorageClass::default();
154        assert!(matches!(class, StorageClass::Standard));
155    }
156}