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}