Skip to main content

random_access_storage/
lib.rs

1//! # Abstract interface to implement random-access instances
2//!
3//! This crate defines the shared [RandomAccess] trait that makes it possible to create
4//! different backends for reading, writing and deleting bytes. With a shared interface,
5//! implementations can easily be swapped, depending on the needs and the environment.
6//!
7//! ## Known Implementations
8//!
9//! Full implementations of [RandomAccess] include:
10//!
11//! * [random-access-memory](https://docs.rs/random-access-memory) for in-memory storage
12//! * [random-access-disk](https://docs.rs/random-access-disk) for disk storage
13//!
14//! ## Examples
15//!
16//! Your own random-access backend can be implemented like this:
17//!
18//! ```
19//! use random_access_storage::{BoxFuture, RandomAccess, RandomAccessError};
20//!
21//! struct MyRandomAccess {
22//!     // Add fields here
23//! }
24//!
25//! impl RandomAccess for MyRandomAccess {
26//!     fn write(&self, _offset: u64, _data: &[u8]) -> BoxFuture<Result<(), RandomAccessError>> {
27//!         unimplemented!();
28//!     }
29//!
30//!     fn read(
31//!         &self,
32//!         _offset: u64,
33//!         _length: u64,
34//!     ) -> BoxFuture<Result<Vec<u8>, RandomAccessError>> {
35//!         unimplemented!();
36//!     }
37//!
38//!     fn del(&self, _offset: u64, _length: u64) -> BoxFuture<Result<(), RandomAccessError>> {
39//!         unimplemented!();
40//!     }
41//!
42//!     fn truncate(&self, _length: u64) -> BoxFuture<Result<(), RandomAccessError>> {
43//!         unimplemented!();
44//!     }
45//!
46//!     fn len(&self) -> u64 {
47//!         unimplemented!();
48//!     }
49//! }
50//! ```
51use std::{future::Future, pin::Pin};
52
53use thiserror::Error;
54
55/// Convenience alias for the owned, sendable futures returned by [`RandomAccess::read`].
56pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send + 'static>>;
57
58/// Error type for the [RandomAccess] trait methods.
59#[derive(Error, Debug)]
60pub enum RandomAccessError {
61    /// Given parameters are out of bounds.
62    #[error("{} out of bounds. {} < {}{}",
63          .end.as_ref().map_or_else(|| "Offset", |_| "Range"),
64          .length,
65          .offset,
66          .end.as_ref().map_or_else(String::new, |end| format!("..{}", end)))]
67    OutOfBounds {
68        /// Offset that was out of bounds
69        offset: u64,
70        /// If it was a range that was out of bounds, the end of the range.
71        end: Option<u64>,
72        /// The length in the implementation that was exceeded.
73        length: u64,
74    },
75    /// Unexpected [std::io::Error].
76    #[error("Unrecoverable input/output error occured.{}{}",
77          .return_code.as_ref().map_or_else(String::new, |rc| format!(" Return code: {}.", rc)),
78          .context.as_ref().map_or_else(String::new, |ctx| format!(" Context: {}.", ctx)))]
79    IO {
80        /// Optional system return code that caused the error.
81        return_code: Option<i32>,
82        /// Optional context of the error.
83        context: Option<String>,
84        /// Source of the error.
85        #[source]
86        source: std::io::Error,
87    },
88}
89
90impl From<std::io::Error> for RandomAccessError {
91    fn from(err: std::io::Error) -> Self {
92        Self::IO {
93            return_code: None,
94            context: None,
95            source: err,
96        }
97    }
98}
99
100/// Interface for reading from, writing to and deleting from a
101/// randomly accessible storage of bytes.
102pub trait RandomAccess {
103    /// Write bytes of `data` at an `offset` to the backend.
104    ///
105    /// # Errors
106    ///
107    /// * [RandomAccessError::OutOfBounds] if the backend has
108    ///   a maximum capacity that would be exceeded by the write.
109    ///
110    /// * [RandomAccessError::IO] if an unexpected IO error occurred.
111    fn write(&self, offset: u64, data: &[u8]) -> BoxFuture<Result<(), RandomAccessError>>;
112
113    /// Read a sequence of bytes at an `offset` from the backend.
114    ///
115    /// # Errors
116    ///
117    /// * [RandomAccessError::OutOfBounds] if
118    ///   [RandomAccess::len] > `offset` + `length`.
119    ///
120    /// * [RandomAccessError::IO] if an unexpected IO error occurred.
121    fn read(&self, offset: u64, length: u64) -> BoxFuture<Result<Vec<u8>, RandomAccessError>>;
122
123    /// Delete a sequence of bytes of given `length` at an `offset` from the backend.
124    /// This either sets the bytes in the given slice to zeroes, or if
125    /// `offset` + `length` >= [RandomAccess::len()] is the same as
126    /// `truncate(offset)`.
127    ///
128    /// # Errors
129    ///
130    /// * [RandomAccessError::OutOfBounds] if [RandomAccess::len()] < `offset` + `length`.
131    ///
132    /// * [RandomAccessError::IO] if an unexpected IO error occurred.
133    fn del(&self, offset: u64, length: u64) -> BoxFuture<Result<(), RandomAccessError>>;
134
135    /// Resize the sequence of bytes so that [RandomAccess::len()] is set to
136    /// `length`. If `length` < [RandomAccess::len()], the bytes are disregarded.
137    /// If `length` > [RandomAccess::len()], the storage is zero-padded.
138    ///
139    /// # Errors
140    ///
141    /// * [RandomAccessError::OutOfBounds] if the backend has
142    ///   a maximum capacity smaller than `length`.
143    ///
144    /// * [RandomAccessError::IO] if an unexpected IO error occurred.
145    fn truncate(&self, length: u64) -> BoxFuture<Result<(), RandomAccessError>>;
146
147    /// Get the size of the storage in bytes.
148    fn len(&self) -> u64;
149
150    /// Whether the storage is empty.
151    fn is_empty(&self) -> bool {
152        self.len() == 0
153    }
154
155    /// Flush buffered data on the underlying storage resource.
156    ///
157    /// # Errors
158    ///
159    /// * [RandomAccessError::IO] if an unexpected IO error occurred.
160    fn sync_all(&self) -> BoxFuture<Result<(), RandomAccessError>> {
161        Box::pin(std::future::ready(Ok(())))
162    }
163}