Stowage
A unified async storage abstraction for Rust supporting multiple backends.
Features
A single Storage trait works across all backends:
File Transfer Protocols
- SFTP - SSH File Transfer Protocol (
sftpfeature) - FTP - File Transfer Protocol (
ftpfeature)
Cloud Storage
- AWS S3 - Amazon S3 and S3-compatible services (
s3feature) - Azure Blob Storage (
azurefeature) - Google Drive (
gdrivefeature) - Microsoft OneDrive (
onedrivefeature) - Dropbox (
dropboxfeature) - Box.com (
box_storagefeature)
Network Storage
- WebDAV - WebDAV protocol for Nextcloud, ownCloud, etc. (
webdavfeature)
Local Storage
- Local filesystem (
localfeature) - In-memory storage (
memoryfeature)
Multi-Storage Patterns
- FallbackStorage - Automatic failover to secondary backend
- MirrorStorage - Parallel writes to multiple backends for redundancy
- ReadOnlyStorage - Enforce read-only access to any backend
Installation
Add stowage to your Cargo.toml:
[]
= { = "0.1", = ["sftp", "ftp"] }
Usage
SFTP Storage
use ;
async
FTP Storage
use ;
async
Local Filesystem
use ;
async
AWS S3
use ;
async
WebDAV (Nextcloud, ownCloud, etc.)
use ;
async
Core Traits
Storage
All adapters implement the Storage trait with methods for:
exists- Check if an item existsfolder_exists- Check if a folder/directory existsput- Store data from anAsyncReadstreamget_into- Retrieve data to anAsyncWritestreamdelete- Remove an itemlist- List items with optional prefix filtering
Path-Based vs ID-Based Adapters
Path-based adapters use string paths as identifiers:
- Local, S3, Azure, WebDAV, SFTP, FTP, Dropbox
ID-based adapters use native item IDs:
- Google Drive, OneDrive, Box
For ID-based adapters, you must resolve paths to IDs before calling storage methods. Each adapter provides helper methods:
// Google Drive - find folder by name
let folder_id = storage.find_folder_by_name.await?;
if let Some = folder_id
// OneDrive - get folder ID by path
let folder_id = storage.get_folder_id_by_path.await?;
if storage.folder_exists.await?
// Box - find folder by name in parent folder
let folder_id = storage.find_folder_by_name.await?;
if let Some = folder_id
StorageExt
Convenience methods built on Storage:
get_bytes- Download asVec<u8>get_string- Download as UTF-8 stringput_bytes- Upload from byte slicecopy_to- Copy between storage backends
Multi-Storage Patterns
Compose multiple backends for complex architectures:
FallbackStorage
Automatic failover to secondary storage:
use FallbackStorage;
use ;
let storage = new;
// Reads from primary, falls back to backup if not found
let data = storage.get_bytes.await?;
MirrorStorage
Replicate data across multiple backends:
use ;
use ;
let storage = builder
.add_backend
.add_backend
.write_strategy
.build;
// Writes to all backends sequentially
// On partial failure with rollback=true, successful writes are deleted
storage.put_bytes.await?;
Write strategies (all support rollback):
AllOrFail { rollback }- All must succeedAtLeastOne { rollback }- At least one must succeedQuorum { rollback }- Majority must succeed
When rollback is enabled and operation fails, successful writes are automatically deleted.
On failure, Error::MirrorFailure(details) provides detailed information:
// Error contains MirrorFailureDetails with:
match storage.put_bytes.await
ReadOnlyStorage
Prevent all write operations:
use ReadOnlyStorage;
use ;
let storage = new;
// Reads work fine
let data = storage.get_bytes.await;
// Writes are rejected
assert!;
Composing Patterns
All patterns implement Storage and can be composed:
use ;
// Mirrored primary with fallback cache
let mirror = builder
.add_backend
.add_backend
.build;
let storage = new;
Security
Sensitive fields like passwords and tokens are protected using the secrecy crate and will not appear in debug output.
Error Handling
All operations return stowage::Result<T> with a unified Error type.
License
This project is licensed under the MIT License.