#[cfg(any(test, feature = "conformance-storage"))]
pub mod conformance;
#[cfg(all(feature = "storage-file", not(target_arch = "wasm32")))]
pub mod file;
#[cfg(all(feature = "storage-memory", not(target_arch = "wasm32")))]
pub mod memory;
use std::collections::HashMap;
use std::pin::Pin;
use async_trait::async_trait;
use bytes::Bytes;
use futures_core::Stream;
use crate::error::Result;
use crate::runtime::MaybeSendSync;
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
pub trait Storage: MaybeSendSync {
fn name(&self) -> &'static str;
async fn create(&self, upload_id: &str) -> Result<StorageHandle>;
async fn append(&self, request: AppendRequest) -> Result<StorageHandle>;
async fn concat(&self, request: ConcatRequest) -> Result<StorageHandle>;
async fn delete(&self, handle: &StorageHandle) -> Result<()>;
async fn size(&self, handle: &StorageHandle) -> Result<Option<u64>>;
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
pub trait StorageReader: MaybeSendSync {
async fn stream(&self, handle: &StorageHandle) -> Result<ByteStream>;
async fn stream_range(
&self,
handle: &StorageHandle,
start: u64,
end: Option<u64>,
) -> Result<ByteStream>;
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct StorageHandle {
key: String,
internal: HashMap<String, String>,
}
impl StorageHandle {
pub fn new(key: impl Into<String>) -> Self {
Self {
key: key.into(),
internal: HashMap::new(),
}
}
pub fn from_parts(key: impl Into<String>, internal: HashMap<String, String>) -> Self {
Self {
key: key.into(),
internal,
}
}
pub fn key(&self) -> &str {
&self.key
}
pub fn into_parts(self) -> (String, HashMap<String, String>) {
(self.key, self.internal)
}
pub fn set_internal(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.internal.insert(key.into(), value.into());
}
pub fn internal(&self, key: &str) -> Option<&str> {
self.internal.get(key).map(String::as_str)
}
pub fn remove_internal(&mut self, key: &str) -> Option<String> {
self.internal.remove(key)
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct AppendRequest {
pub handle: StorageHandle,
pub expected_offset: u64,
pub data: ChunkStream,
pub completes_upload: bool,
}
impl AppendRequest {
#[must_use]
pub fn new(
handle: StorageHandle,
expected_offset: u64,
data: ChunkStream,
completes_upload: bool,
) -> Self {
Self {
handle,
expected_offset,
data,
completes_upload,
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct ConcatRequest {
pub target: StorageHandle,
pub parts: Vec<StorageHandle>,
}
impl ConcatRequest {
#[must_use]
pub fn new(target: StorageHandle, parts: Vec<StorageHandle>) -> Self {
Self { target, parts }
}
}
pub enum ChunkStream {
Buffered(Bytes),
Stream(ByteStream),
}
impl ChunkStream {
#[must_use]
pub fn from_bytes(bytes: Bytes) -> Self {
ChunkStream::Buffered(bytes)
}
#[must_use]
pub fn from_stream(stream: ByteStream) -> Self {
ChunkStream::Stream(stream)
}
#[must_use]
pub fn empty() -> Self {
ChunkStream::Buffered(Bytes::new())
}
}
impl std::fmt::Debug for ChunkStream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ChunkStream::Buffered(b) => write!(f, "ChunkStream::Buffered({} bytes)", b.len()),
ChunkStream::Stream(_) => write!(f, "ChunkStream::Stream(..)"),
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub type ByteStream = Pin<Box<dyn Stream<Item = std::io::Result<Bytes>> + Send>>;
#[cfg(target_arch = "wasm32")]
pub type ByteStream = Pin<Box<dyn Stream<Item = std::io::Result<Bytes>>>>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chunk_stream_from_bytes() {
let bytes = Bytes::from("hello world");
let stream = ChunkStream::from_bytes(bytes.clone());
match stream {
ChunkStream::Buffered(b) => assert_eq!(b, bytes),
_ => panic!("expected Buffered variant"),
}
}
#[test]
fn test_chunk_stream_debug() {
let stream = ChunkStream::from_bytes(Bytes::from("test"));
let debug_str = format!("{:?}", stream);
assert!(debug_str.contains("4 bytes"));
}
#[cfg(target_arch = "wasm32")]
#[test]
fn test_byte_stream_accepts_non_send_streams_in_local_mode() {
use std::rc::Rc;
let marker = Rc::new(());
let stream: ByteStream = Box::pin(futures::stream::once({
let marker = marker.clone();
async move {
let _marker = marker;
Ok(Bytes::from_static(b"local"))
}
}));
let chunk = ChunkStream::from_stream(stream);
assert!(matches!(chunk, ChunkStream::Stream(_)));
}
}