#![forbid(unsafe_code)]
#![warn(missing_docs)]
use aws_config::SdkConfig;
use aws_sdk_s3::presigning::PresignedRequest;
use bytes::{Buf, Bytes};
use bytes_utils::SegmentedBuf;
use chrono::{DateTime, Utc};
use futures::{Stream, StreamExt};
use serde::{Deserialize, Serialize};
use std::{fmt::Debug, pin::Pin, time::Duration};
use thiserror::Error;
pub mod s3;
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "provider", rename_all = "snake_case")]
pub enum StorageLayerFactoryConfig {
S3(s3::S3StorageLayerFactoryConfig),
}
impl Default for StorageLayerFactoryConfig {
fn default() -> Self {
Self::S3(Default::default())
}
}
#[derive(Debug, Error)]
pub enum StorageLayerFactoryConfigError {
#[error(transparent)]
S3(#[from] s3::S3StorageLayerFactoryConfigError),
}
impl StorageLayerFactoryConfig {
pub fn from_env() -> Result<Self, StorageLayerFactoryConfigError> {
s3::S3StorageLayerFactoryConfig::from_env()
.map(Self::S3)
.map_err(StorageLayerFactoryConfigError::S3)
}
}
#[derive(Clone)]
pub enum StorageLayerFactory {
S3(s3::S3StorageLayerFactory),
}
#[derive(Debug, Error)]
pub enum StorageLayerError {
#[error(transparent)]
S3(Box<s3::S3StorageError>),
#[error("failed to collect file contents")]
CollectBytes,
}
impl From<s3::S3StorageError> for StorageLayerError {
fn from(value: s3::S3StorageError) -> Self {
Self::S3(Box::new(value))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StorageLayerOptions {
pub bucket_name: String,
}
impl StorageLayerFactory {
pub fn from_config(aws_config: &SdkConfig, config: StorageLayerFactoryConfig) -> Self {
match config {
StorageLayerFactoryConfig::S3(config) => {
Self::S3(s3::S3StorageLayerFactory::from_config(aws_config, config))
}
}
}
#[cfg(debug_assertions)]
pub fn create_test_layer(&self) -> StorageLayer {
self.create_layer(StorageLayerOptions {
bucket_name: "test".to_string(),
})
}
pub fn create_layer(&self, options: StorageLayerOptions) -> StorageLayer {
match self {
StorageLayerFactory::S3(s3) => {
let layer = s3.create_storage_layer(options.bucket_name);
StorageLayer::S3(layer)
}
}
}
}
#[derive(Clone)]
pub enum StorageLayer {
S3(s3::S3StorageLayer),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CreateBucketOutcome {
New,
Existing,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct UploadFileOptions {
pub content_type: String,
pub tags: Option<Vec<UploadFileTag>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UploadFileTag {
ExpireDays1,
ExpireDays30,
}
impl StorageLayer {
pub fn bucket_name(&self) -> String {
match self {
StorageLayer::S3(layer) => layer.bucket_name(),
}
}
#[tracing::instrument(skip(self))]
pub async fn create_bucket(&self) -> Result<CreateBucketOutcome, StorageLayerError> {
match self {
StorageLayer::S3(layer) => layer.create_bucket().await,
}
}
#[tracing::instrument(skip(self))]
pub async fn bucket_exists(&self) -> Result<bool, StorageLayerError> {
match self {
StorageLayer::S3(layer) => layer.bucket_exists().await,
}
}
#[tracing::instrument(skip(self))]
pub async fn delete_bucket(&self) -> Result<(), StorageLayerError> {
match self {
StorageLayer::S3(layer) => layer.delete_bucket().await,
}
}
#[tracing::instrument(skip(self))]
pub async fn create_presigned(
&self,
key: &str,
size: i64,
) -> Result<(PresignedRequest, DateTime<Utc>), StorageLayerError> {
match self {
StorageLayer::S3(layer) => layer.create_presigned(key, size).await,
}
}
#[tracing::instrument(skip(self))]
pub async fn create_presigned_download(
&self,
key: &str,
expires_in: Duration,
) -> Result<(PresignedRequest, DateTime<Utc>), StorageLayerError> {
match self {
StorageLayer::S3(layer) => layer.create_presigned_download(key, expires_in).await,
}
}
#[tracing::instrument(skip(self, body), fields(body_length = body.len()))]
pub async fn upload_file(
&self,
key: &str,
body: Bytes,
options: UploadFileOptions,
) -> Result<(), StorageLayerError> {
match self {
StorageLayer::S3(layer) => layer.upload_file(key, body, options).await,
}
}
#[tracing::instrument(skip(self))]
pub async fn add_bucket_notifications(&self, sns_arn: &str) -> Result<(), StorageLayerError> {
match self {
StorageLayer::S3(layer) => layer.add_bucket_notifications(sns_arn).await,
}
}
#[tracing::instrument(skip(self))]
pub async fn set_bucket_cors_origins(
&self,
origins: Vec<String>,
) -> Result<(), StorageLayerError> {
match self {
StorageLayer::S3(layer) => layer.set_bucket_cors_origins(origins).await,
}
}
#[tracing::instrument(skip(self))]
pub async fn delete_file(&self, key: &str) -> Result<(), StorageLayerError> {
match self {
StorageLayer::S3(layer) => layer.delete_file(key).await,
}
}
#[tracing::instrument(skip(self))]
pub async fn get_file(&self, key: &str) -> Result<FileStream, StorageLayerError> {
match self {
StorageLayer::S3(layer) => layer.get_file(key).await,
}
}
#[tracing::instrument(skip(self))]
pub async fn get_pending_migrations(
&self,
applied_names: Vec<String>,
) -> Result<Vec<String>, StorageLayerError> {
match self {
StorageLayer::S3(layer) => layer.get_pending_migrations(applied_names).await,
}
}
#[tracing::instrument(skip(self))]
pub async fn apply_migration(&self, name: &str) -> Result<(), StorageLayerError> {
match self {
StorageLayer::S3(layer) => layer.apply_migration(name).await,
}
}
}
pub(crate) trait StorageLayerImpl {
fn bucket_name(&self) -> String;
async fn create_bucket(&self) -> Result<CreateBucketOutcome, StorageLayerError>;
async fn bucket_exists(&self) -> Result<bool, StorageLayerError>;
async fn delete_bucket(&self) -> Result<(), StorageLayerError>;
async fn create_presigned(
&self,
key: &str,
size: i64,
) -> Result<(PresignedRequest, DateTime<Utc>), StorageLayerError>;
async fn create_presigned_download(
&self,
key: &str,
expires_in: Duration,
) -> Result<(PresignedRequest, DateTime<Utc>), StorageLayerError>;
async fn upload_file(
&self,
key: &str,
body: Bytes,
options: UploadFileOptions,
) -> Result<(), StorageLayerError>;
async fn add_bucket_notifications(&self, sns_arn: &str) -> Result<(), StorageLayerError>;
async fn set_bucket_cors_origins(&self, origins: Vec<String>) -> Result<(), StorageLayerError>;
async fn delete_file(&self, key: &str) -> Result<(), StorageLayerError>;
async fn get_file(&self, key: &str) -> Result<FileStream, StorageLayerError>;
async fn get_pending_migrations(
&self,
applied_names: Vec<String>,
) -> Result<Vec<String>, StorageLayerError>;
async fn apply_migration(&self, name: &str) -> Result<(), StorageLayerError>;
}
pub struct FileStream {
pub stream: Pin<Box<dyn Stream<Item = std::io::Result<Bytes>> + Send>>,
}
impl Debug for FileStream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FileStream").finish()
}
}
impl Stream for FileStream {
type Item = std::io::Result<Bytes>;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
self.stream.as_mut().poll_next(cx)
}
}
impl FileStream {
pub async fn collect_bytes(mut self) -> Result<Bytes, StorageLayerError> {
let mut output = SegmentedBuf::new();
while let Some(result) = self.next().await {
let chunk = result.map_err(|error| {
tracing::error!(?error, "failed to collect file stream bytes");
StorageLayerError::CollectBytes
})?;
output.push(chunk);
}
Ok(output.copy_to_bytes(output.remaining()))
}
}