diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 0000000..cda8d17 --- /dev/null +++ b/.clippy.toml @@ -0,0 +1 @@ +avoid-breaking-exported-api = false diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..adbe5db --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,5 @@ +# groups 'use' statements by crate +imports_granularity = "crate" +# formats code within doc tests +# requires: cargo +nightly fmt (otherwise rustfmt will warn, but pass) +format_code_in_doc_comments = true diff --git a/CHANGELOG.md b/CHANGELOG.md index d99efe6..bb2fd23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,32 @@ -## 2023-04-12, Version 5.0.0 +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + + +## [Unreleased] - ReleaseDate + +### Added + +* Add `BoxFuture` type alias for owned, sendable futures + +### Changed + +* Bump edition to 2024 +* Remove `async-trait` dependency — all trait methods now return `BoxFuture` directly +* `RandomAccess::read`, `write`, `del`, `truncate`, `sync_all` now take `&self` and return `BoxFuture` +* `RandomAccess::len` is now synchronous and takes `&self` + +**Note: `write` data parameter is under consideration.** The `write` method currently takes `&[u8]`, but since `BoxFuture` is `'static` the data must either be cloned before being moved into the future, or the caller must ensure it lives long enough. We haven't settled on the best owned type for this parameter. Candidates are `Vec` (no new dependency, universal), `Bytes` (cheaply cloneable, idiomatic in async I/O) and keeping `&[u8]` with a lifetime-bound future (zero-copy but no longer `'static`). Feedback welcome. + +### Removed + + + +## [5.0.0] - 2023-04-12 ### Commits - [[`8a55cc70fd`](https://github.com/datrs/random-access-storage/commit/8a55cc70fd3195dbc8ca8495309ff918b9c39d1f)] Switch from Travis to GHA (Timo Tiuraniemi) - [[`3d2f8ebc6d`](https://github.com/datrs/random-access-storage/commit/3d2f8ebc6d05da5d98a21f25fec31d5799dfed97)] Document crate properly, opting for documentation in lib.rs instead of just README.md. (Timo Tiuraniemi) @@ -21,7 +49,7 @@ ``` -## 2020-03-03, Version 4.0.0 +## [4.0.0] - 2020-03-03 ### Commits - [[`064eb1d6c9`](https://github.com/datrs/random-access-storage/commit/064eb1d6c9c1110f7cb01bfbaa6eb43d52330cd4)] (cargo-release) version 4.0.0 (Bruno Tavares) - [[`0e7ab518e5`](https://github.com/datrs/random-access-storage/commit/0e7ab518e5d529160073e5aa295ff6a711472944)] Merge pull request #22 from bltavares/async-trait (Bruno Tavares) @@ -43,7 +71,7 @@ ``` -## 2019-07-27, Version 3.0.0 +## [3.0.0] - 2019-07-27 ### Commits - [[`49c25778a0`](https://github.com/datrs/random-access-storage/commit/49c25778a0f80db733028c059958303e374b5965)] (cargo-release) version 3.0.0 (Yoshua Wuyts) - [[`553af611fd`](https://github.com/datrs/random-access-storage/commit/553af611fde9a22e92b17c9b52cc1379cd4dc57d)] u64 file offsets (#17) (James Halliday) @@ -61,7 +89,7 @@ ``` -## 2018-12-18, Version 2.0.0 +## [2.0.0] - 2018-12-18 ### Commits - [[`55ab88f0fd`](https://github.com/datrs/random-access-storage/commit/55ab88f0fd5114f8911442bc665fcca6949516ac)] (cargo-release) version 2.0.0 (Yoshua Wuyts) - [[`81319ec100`](https://github.com/datrs/random-access-storage/commit/81319ec100e196c0ad79528d966d7717005c38f1)] len() and is_empty() (#16) (James Halliday) @@ -77,7 +105,7 @@ ``` -## 2018-11-20, Version 1.0.0 +## [1.0.0] - 2018-11-20 ### Commits - [[`2e9f4090d7`](https://github.com/datrs/random-access-storage/commit/2e9f4090d766a8fbaf6301e789b7db1439e383ad)] (cargo-release) version 1.0.0 (Yoshua Wuyts) - [[`5e423e35ff`](https://github.com/datrs/random-access-storage/commit/5e423e35ff60ad7ed918a883c776c287a4b9b3fe)] truncate method (#15) (James Halliday) @@ -95,7 +123,7 @@ ``` -## 2018-08-29, Version 0.6.0 +## [0.6.0] - 2018-08-29 ### Commits - [[`23e48f8e29`](https://github.com/datrs/random-access-storage/commits/23e48f8e29fc5cb0eaf7a0e77485cd6d23884771)] (cargo-release) version 0.6.0 (Yoshua Wuyts) - [[`f1fc4982aa`](https://github.com/datrs/random-access-storage/commits/f1fc4982aa1f1a4e6b919344810f87ec36be36c7)] Fixes #7: Make RandomAccess always open (#11) (Szabolcs Berecz) @@ -113,4 +141,10 @@ 4 files changed, 29 insertions(+), 64 deletions(-) ``` - + +[Unreleased]: https://github.com/datrs/random-access-storage/compare/v5.0.0...HEAD +[5.0.0]: https://github.com/datrs/random-access-storage/compare/v4.0.0...v5.0.0 +[4.0.0]: https://github.com/datrs/random-access-storage/compare/v3.0.0...v4.0.0 +[3.0.0]: https://github.com/datrs/random-access-storage/compare/v2.0.0...v3.0.0 +[2.0.0]: https://github.com/datrs/random-access-storage/compare/v1.0.0...v2.0.0 +[1.0.0]: https://github.com/datrs/random-access-storage/compare/v0.6.0...v1.0.0 diff --git a/Cargo.toml b/Cargo.toml index 5f1a59e..32e6129 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,13 @@ [package] name = "random-access-storage" -version = "5.0.0" +version = "6.0.0-alpha" license = "MIT OR Apache-2.0" repository = "https://github.com/datrs/random-access-storage" documentation = "https://docs.rs/random-access-storage" description = "Abstract interface to implement random-access instances." authors = ["Yoshua Wuyts ", "Timo Tiuraniemi "] readme = "README.md" -edition = "2021" +edition = "2024" [dependencies] -async-trait = "0.1" thiserror = "1" diff --git a/release.toml b/release.toml new file mode 100644 index 0000000..3c84b41 --- /dev/null +++ b/release.toml @@ -0,0 +1,7 @@ +pre-release-replacements = [ + {file="CHANGELOG.md", search="Unreleased", replace="{{version}}"}, + {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, + {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}"}, + {file="CHANGELOG.md", search="", replace="\n\n## [Unreleased] - ReleaseDate\n\n### Added\n\n### Changed\n\n### Removed\n\n", exactly=1}, + {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/datrs/random-access-storage/compare/{{tag_name}}...HEAD", exactly=1}, +] diff --git a/src/lib.rs b/src/lib.rs index fea5037..ef79e92 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,3 @@ -#![deny(missing_docs)] -#![cfg_attr(test, deny(warnings))] -#![doc(test(attr(deny(warnings))))] //! # Abstract interface to implement random-access instances //! //! This crate defines the shared [RandomAccess] trait that makes it possible to create @@ -19,168 +16,148 @@ //! Your own random-access backend can be implemented like this: //! //! ``` -//! use random_access_storage::{RandomAccess, RandomAccessError}; -//! use async_trait::async_trait; +//! use random_access_storage::{BoxFuture, RandomAccess, RandomAccessError}; //! //! struct MyRandomAccess { -//! // Add fields here +//! // Add fields here //! } //! -//! #[async_trait] //! impl RandomAccess for MyRandomAccess { -//! async fn write(&mut self, _offset: u64, _data: &[u8]) -> Result<(), RandomAccessError> { -//! unimplemented!(); -//! } -// -//! async fn read(&mut self, _offset: u64, _length: u64) -> Result, RandomAccessError> { -//! unimplemented!(); -//! } +//! fn write(&self, _offset: u64, _data: &[u8]) -> BoxFuture> { +//! unimplemented!(); +//! } //! -//! async fn del(&mut self, _offset: u64, _length: u64) -> Result<(), RandomAccessError> { -//! unimplemented!(); -//! } +//! fn read( +//! &self, +//! _offset: u64, +//! _length: u64, +//! ) -> BoxFuture, RandomAccessError>> { +//! unimplemented!(); +//! } //! -//! async fn truncate(&mut self, _length: u64) -> Result<(), RandomAccessError> { -//! unimplemented!(); -//! } +//! fn del(&self, _offset: u64, _length: u64) -> BoxFuture> { +//! unimplemented!(); +//! } //! -//! async fn len(&mut self) -> Result { -//! unimplemented!(); -//! } +//! fn truncate(&self, _length: u64) -> BoxFuture> { +//! unimplemented!(); +//! } //! -//! async fn is_empty(&mut self) -> Result { -//! unimplemented!(); -//! } -//! -//! async fn sync_all(&mut self) -> Result<(), RandomAccessError> { -//! unimplemented!(); -//! } +//! fn len(&self) -> u64 { +//! unimplemented!(); +//! } //! } //! ``` +use std::{future::Future, pin::Pin}; + use thiserror::Error; +/// Convenience alias for the owned, sendable futures returned by [`RandomAccess::read`]. +pub type BoxFuture = Pin + Send + 'static>>; + /// Error type for the [RandomAccess] trait methods. #[derive(Error, Debug)] pub enum RandomAccessError { - /// Given parameters are out of bounds. - #[error("{} out of bounds. {} < {}{}", + /// Given parameters are out of bounds. + #[error("{} out of bounds. {} < {}{}", .end.as_ref().map_or_else(|| "Offset", |_| "Range"), .length, .offset, .end.as_ref().map_or_else(String::new, |end| format!("..{}", end)))] - OutOfBounds { - /// Offset that was out of bounds - offset: u64, - /// If it was a range that was out of bounds, the end of the range. - end: Option, - /// The length in the implementation that was exceeded. - length: u64, - }, - /// Unexpected [std::io::Error]. - #[error("Unrecoverable input/output error occured.{}{}", + OutOfBounds { + /// Offset that was out of bounds + offset: u64, + /// If it was a range that was out of bounds, the end of the range. + end: Option, + /// The length in the implementation that was exceeded. + length: u64, + }, + /// Unexpected [std::io::Error]. + #[error("Unrecoverable input/output error occured.{}{}", .return_code.as_ref().map_or_else(String::new, |rc| format!(" Return code: {}.", rc)), .context.as_ref().map_or_else(String::new, |ctx| format!(" Context: {}.", ctx)))] - IO { - /// Optional system return code that caused the error. - return_code: Option, - /// Optional context of the error. - context: Option, - /// Source of the error. - #[source] - source: std::io::Error, - }, + IO { + /// Optional system return code that caused the error. + return_code: Option, + /// Optional context of the error. + context: Option, + /// Source of the error. + #[source] + source: std::io::Error, + }, } impl From for RandomAccessError { - fn from(err: std::io::Error) -> Self { - Self::IO { - return_code: None, - context: None, - source: err, + fn from(err: std::io::Error) -> Self { + Self::IO { + return_code: None, + context: None, + source: err, + } } - } } /// Interface for reading from, writing to and deleting from a /// randomly accessible storage of bytes. -#[async_trait::async_trait] pub trait RandomAccess { - /// Write bytes of `data` at an `offset` to the backend. - /// - /// # Errors - /// - /// * [RandomAccessError::OutOfBounds] if the backend has - /// a maximum capacity that would be exceeded by the write. - /// - /// * [RandomAccessError::IO] if an unexpected IO error occurred. - async fn write( - &mut self, - offset: u64, - data: &[u8], - ) -> Result<(), RandomAccessError>; + /// Write bytes of `data` at an `offset` to the backend. + /// + /// # Errors + /// + /// * [RandomAccessError::OutOfBounds] if the backend has + /// a maximum capacity that would be exceeded by the write. + /// + /// * [RandomAccessError::IO] if an unexpected IO error occurred. + fn write(&self, offset: u64, data: &[u8]) -> BoxFuture>; - /// Read a sequence of bytes at an `offset` from the backend. - /// - /// # Errors - /// - /// * [RandomAccessError::OutOfBounds] if - /// [RandomAccess::len] > `offset` + `length`. - /// - /// * [RandomAccessError::IO] if an unexpected IO error occurred. - async fn read( - &mut self, - offset: u64, - length: u64, - ) -> Result, RandomAccessError>; + /// Read a sequence of bytes at an `offset` from the backend. + /// + /// # Errors + /// + /// * [RandomAccessError::OutOfBounds] if + /// [RandomAccess::len] > `offset` + `length`. + /// + /// * [RandomAccessError::IO] if an unexpected IO error occurred. + fn read(&self, offset: u64, length: u64) -> BoxFuture, RandomAccessError>>; - /// Delete a sequence of bytes of given `length` at an `offset` from the backend. - /// This either sets the bytes in the given slice to zeroes, or if - /// `offset` + `length` >= [RandomAccess::len()] is the same as - /// `truncate(offset)`. - /// - /// # Errors - /// - /// * [RandomAccessError::OutOfBounds] if [RandomAccess::len()] < `offset` + `length`. - /// - /// * [RandomAccessError::IO] if an unexpected IO error occurred. - async fn del( - &mut self, - offset: u64, - length: u64, - ) -> Result<(), RandomAccessError>; + /// Delete a sequence of bytes of given `length` at an `offset` from the backend. + /// This either sets the bytes in the given slice to zeroes, or if + /// `offset` + `length` >= [RandomAccess::len()] is the same as + /// `truncate(offset)`. + /// + /// # Errors + /// + /// * [RandomAccessError::OutOfBounds] if [RandomAccess::len()] < `offset` + `length`. + /// + /// * [RandomAccessError::IO] if an unexpected IO error occurred. + fn del(&self, offset: u64, length: u64) -> BoxFuture>; - /// Resize the sequence of bytes so that [RandomAccess::len()] is set to - /// `length`. If `length` < [RandomAccess::len()], the bytes are disregarded. - /// If `length` > [RandomAccess::len()], the storage is zero-padded. - /// - /// # Errors - /// - /// * [RandomAccessError::OutOfBounds] if the backend has - /// a maximum capacity smaller than `length`. - /// - /// * [RandomAccessError::IO] if an unexpected IO error occurred. - async fn truncate(&mut self, length: u64) -> Result<(), RandomAccessError>; + /// Resize the sequence of bytes so that [RandomAccess::len()] is set to + /// `length`. If `length` < [RandomAccess::len()], the bytes are disregarded. + /// If `length` > [RandomAccess::len()], the storage is zero-padded. + /// + /// # Errors + /// + /// * [RandomAccessError::OutOfBounds] if the backend has + /// a maximum capacity smaller than `length`. + /// + /// * [RandomAccessError::IO] if an unexpected IO error occurred. + fn truncate(&self, length: u64) -> BoxFuture>; - /// Get the size of the storage in bytes. - /// - /// # Errors - /// - /// * [RandomAccessError::IO] if an unexpected IO error occurred. - async fn len(&mut self) -> Result; + /// Get the size of the storage in bytes. + fn len(&self) -> u64; - /// Whether the storage is empty. For some storage backends it may be - /// cheaper to calculate whether the storage is empty than to calculate the - /// length. - /// - /// # Errors - /// - /// * [RandomAccessError::IO] if an unexpected IO error occurred. - async fn is_empty(&mut self) -> Result; + /// Whether the storage is empty. + fn is_empty(&self) -> bool { + self.len() == 0 + } - /// Flush buffered data on the underlying storage resource. - /// - /// # Errors - /// - /// * [RandomAccessError::IO] if an unexpected IO error occurred. - async fn sync_all(&mut self) -> Result<(), RandomAccessError>; + /// Flush buffered data on the underlying storage resource. + /// + /// # Errors + /// + /// * [RandomAccessError::IO] if an unexpected IO error occurred. + fn sync_all(&self) -> BoxFuture> { + Box::pin(std::future::ready(Ok(()))) + } }