diff --git a/Cargo.toml b/Cargo.toml index c086a1e..5ad1c51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["codegen", "tools/create-data-file", "tools/dump-data-file"] [package] name = "data_bucket" -version = "0.5.7" +version = "0.6.0" edition = "2021" authors = ["Handy-caT"] license = "MIT" @@ -13,7 +13,6 @@ description = "DataBucket is container for WorkTable's data" [dependencies] data_bucket_derive = { path = "codegen", version = "^0.3" } -eyre = "0.6.12" derive_more = { version = "1.0.0", features = ["from", "error", "display", "into"] } rkyv = { version = "0.8.17", features = ["uuid-1"] } uuid = { version = "1.11.0", features = ["v4"] } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..4e28872 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,154 @@ +//! What this crate can refuse on. +//! +//! # Why this exists rather than `eyre` +//! +//! `eyre::Report` is a `std` type, and it was in the return type of every +//! fallible function here, so the whole crate reached `std` through its own +//! signatures. Nothing else about page framing needs an operating system: the +//! layout is bytes, the checks are arithmetic. This is the type that lets the +//! rest of the crate say so. +//! +//! It is also more useful than a formatted string. A caller that wants to +//! distinguish "this page is full" from "these bytes are damaged" could not, +//! because both arrived as a `Report` carrying prose. +//! +//! Consumers keep working: `Error` implements `core::error::Error`, so `?` +//! into an `eyre::Result` converts exactly as it did before. + +use core::fmt::{Display, Formatter, Result as FmtResult}; + +use crate::page::PageId; + +/// The result of anything in this crate that can fail. +pub type Result = core::result::Result; + +/// A refusal, with the numbers that justify it. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Error { + /// A write's length does not match the link it was given. + /// + /// A `Link` names an exact byte range, so a write of another size is not + /// a resize: it is a write that would land on the neighbouring row. + LinkLengthMismatch { + /// The length the link reserves. + expected: u32, + /// The length the caller supplied. + found: usize, + }, + /// A link's range runs past the end of the page it points into. + /// + /// Summed in `u64`, because `offset + length` in `u32` can wrap past 4 GiB + /// and slip under the bound. + LinkOutOfBounds { + /// Where the link starts. + offset: u32, + /// How far it runs. + length: u32, + /// How much the page holds. + capacity: usize, + }, + /// A page's contents do not fit the page. + /// + /// Raised where the write is prepared rather than where it lands, so an + /// over-budget page fails in its own persist instead of quietly writing + /// into its neighbour. + PageOverflow { + /// The page whose write is over budget. + page: PageId, + /// Bytes the write needs. + needed: usize, + /// Bytes available. + capacity: usize, + }, + /// Bytes that should have been a structure were not one. + /// + /// The label says which structure, because "corrupt" on its own does not + /// tell an operator which part of a file to distrust. + Corrupt { + /// What failed to parse. + what: &'static str, + }, + /// A value would not archive. + /// + /// rkyv reports this when its allocator refuses, which on this path means + /// the process is already out of memory. + Encode, + /// The file would not answer. + /// + /// Held as the raw OS code rather than an `io::Error`, because that type + /// is `std` and this one is the reason the crate stops needing it. The + /// code is what an operator acts on; the message that came with it says + /// nothing the code does not. + Io { + /// `raw_os_error`, when the failure came from the operating system. + code: Option, + }, + /// A change-data event arrived that this page cannot apply. + /// + /// `SplitNode`, `CreateNode` and `RemoveNode` change which pages exist, + /// which is the caller's business rather than one page's. + UnapplicableEvent, +} + +impl Display for Error { + fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult { + match self { + Self::LinkLengthMismatch { expected, found } => write!( + formatter, + "a {found} byte write does not match its {expected} byte link" + ), + Self::LinkOutOfBounds { + offset, + length, + capacity, + } => write!( + formatter, + "a link at {offset} running {length} bytes leaves a {capacity} byte page" + ), + Self::PageOverflow { + page, + needed, + capacity, + } => write!( + formatter, + "page {page:?} needs {needed} bytes of a {capacity} byte page" + ), + Self::Corrupt { what } => write!(formatter, "torn or corrupt {what}"), + Self::Encode => write!(formatter, "a value would not archive"), + Self::Io { code: Some(code) } => write!(formatter, "the file failed, os error {code}"), + Self::Io { code: None } => write!(formatter, "the file failed"), + Self::UnapplicableEvent => write!( + formatter, + "events of `SplitNode`, `CreateNode` or `RemoveNode` cannot be applied to a page" + ), + } + } +} + +impl core::error::Error for Error {} + +// Not gated yet: the crate still reaches the file system directly. When that +// moves behind a trait this impl goes with it. +impl From for Error { + fn from(error: std::io::Error) -> Self { + Self::Io { + code: error.raw_os_error(), + } + } +} + +impl From for Error { + fn from(_: rkyv::rancor::Error) -> Self { + Self::Encode + } +} + +impl From for Error { + fn from(error: crate::page::PageOverflowError) -> Self { + Self::PageOverflow { + page: error.page_id, + needed: error.data_length, + capacity: error.capacity, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 46812d9..b695443 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ extern crate core; // uses its own derive: alias ourselves so the generated code resolves here too. extern crate self as data_bucket; +pub mod error; pub mod link; pub mod page; pub mod persistence; diff --git a/src/page/data.rs b/src/page/data.rs index ae7ad1c..bd27a05 100644 --- a/src/page/data.rs +++ b/src/page/data.rs @@ -1,6 +1,6 @@ +use crate::error::{Error, Result}; use crate::Link; use crate::Persistable; -use eyre::{eyre, Result}; #[derive(Debug)] pub struct DataPage { @@ -11,11 +11,10 @@ pub struct DataPage { impl DataPage { pub fn update_at(&mut self, link: Link, new_data: &[u8]) -> Result<()> { if new_data.len() as u32 != link.length { - return Err(eyre!( - "New data length {} does not match link length {}", - new_data.len(), - link.length - )); + return Err(Error::LinkLengthMismatch { + expected: link.length, + found: new_data.len(), + }); } // Sum in usize: `offset + length` in u32 can wrap past 4 GiB and @@ -23,12 +22,11 @@ impl DataPage { let start = link.offset as usize; let end = link.offset as usize + link.length as usize; if end > DATA_LENGTH { - return Err(eyre!( - "Link range (offset: {}, length: {}) exceeds data bounds ({})", - link.offset, - link.length, - DATA_LENGTH - )); + return Err(Error::LinkOutOfBounds { + offset: link.offset, + length: link.length, + capacity: DATA_LENGTH, + }); } self.data[start..end].copy_from_slice(new_data); @@ -42,12 +40,11 @@ impl DataPage { let start = link.offset as usize; let end = link.offset as usize + link.length as usize; if end > DATA_LENGTH { - return Err(eyre!( - "Link range (offset: {}, length: {}) exceeds data bounds ({})", - link.offset, - link.length, - DATA_LENGTH - )); + return Err(Error::LinkOutOfBounds { + offset: link.offset, + length: link.length, + capacity: DATA_LENGTH, + }); } Ok(&self.data[start..end]) @@ -105,9 +102,13 @@ mod tests { }; let err = data.update_at(link, &[1, 2]).unwrap_err(); - assert!(err - .to_string() - .contains("New data length 2 does not match link length 3")); + assert_eq!( + err, + Error::LinkLengthMismatch { + expected: 3, + found: 2 + } + ); } #[test] @@ -124,9 +125,14 @@ mod tests { }; let err = data.update_at(link, &[1, 2, 3]).unwrap_err(); - assert!(err - .to_string() - .contains("Link range (offset: 98, length: 3) exceeds data bounds (100)")); + assert_eq!( + err, + Error::LinkOutOfBounds { + offset: 98, + length: 3, + capacity: 100 + } + ); } #[test] @@ -145,7 +151,7 @@ mod tests { }; let err = data.update_at(link, &[1, 2, 3, 4, 5, 6, 7, 8]).unwrap_err(); - assert!(err.to_string().contains("exceeds data bounds")); + assert!(matches!(err, Error::LinkOutOfBounds { .. })); } #[test] @@ -162,7 +168,7 @@ mod tests { }; let err = data.get_at(link).unwrap_err(); - assert!(err.to_string().contains("exceeds data bounds")); + assert!(matches!(err, Error::LinkOutOfBounds { .. })); } #[test] @@ -179,8 +185,13 @@ mod tests { }; let err = data.get_at(link).unwrap_err(); - assert!(err - .to_string() - .contains("Link range (offset: 98, length: 3) exceeds data bounds (100)")); + assert_eq!( + err, + Error::LinkOutOfBounds { + offset: 98, + length: 3, + capacity: 100 + } + ); } } diff --git a/src/page/index/mod.rs b/src/page/index/mod.rs index 81e46d7..9df1444 100644 --- a/src/page/index/mod.rs +++ b/src/page/index/mod.rs @@ -7,7 +7,6 @@ use rkyv::{Archive, Deserialize, Serialize}; use tokio::fs::File; use tokio::io::{AsyncSeekExt, AsyncWriteExt}; -use crate::page::PageOverflowError; use crate::{ align, align_to, seek_to_page_start, Link, Persistable, SizeMeasurable, VariableSizeMeasurable, GENERAL_HEADER_SIZE, INNER_PAGE_SIZE, @@ -33,24 +32,24 @@ pub trait IndexPageUtility { fn parse_index_page_utility( file: &mut File, page_id: PageId, - ) -> impl std::future::Future> + Send; + ) -> impl std::future::Future> + Send; fn persist_index_page_utility( file: &mut File, page_id: PageId, utility: Self::Utility, - ) -> impl std::future::Future> + Send { + ) -> impl std::future::Future> + Send { async move { let bytes = utility.as_bytes(); let utility_length = bytes.as_ref().len(); // An oversized utility must fail here, in its own persist, // instead of writing past the page slot into the neighbor page. if utility_length > INNER_PAGE_SIZE { - return Err(eyre::Report::new(PageOverflowError { - page_id, - data_length: utility_length, + return Err(crate::error::Error::PageOverflow { + page: page_id, + needed: utility_length, capacity: INNER_PAGE_SIZE, - })); + }); } seek_to_page_start(file, page_id.0).await?; file.seek(SeekFrom::Current(GENERAL_HEADER_SIZE as i64)) diff --git a/src/page/index/page.rs b/src/page/index/page.rs index cccb1cf..a11fdca 100644 --- a/src/page/index/page.rs +++ b/src/page/index/page.rs @@ -88,7 +88,7 @@ where async fn parse_index_page_utility( file: &mut File, page_id: PageId, - ) -> eyre::Result { + ) -> crate::error::Result { seek_to_page_start(file, page_id.0).await?; let offset = GENERAL_HEADER_SIZE as i64; file.seek(SeekFrom::Current(offset)).await?; @@ -100,7 +100,9 @@ where let archived = crate::access_archived::<::Archived>( &size_bytes[0..SizedIndexPageUtility::::size_size()], ) - .map_err(|error| eyre::eyre!("torn or corrupt index page size field: {error}"))?; + .map_err(|_| crate::error::Error::Corrupt { + what: "index page size field", + })?; let size = rkyv::deserialize::(archived).expect("data should be valid"); @@ -160,7 +162,7 @@ impl IndexPage { new_page } - async fn read_value(file: &mut File) -> eyre::Result> + async fn read_value(file: &mut File) -> crate::error::Result> where T: Archive, ::Archived: Deserialize> @@ -177,7 +179,9 @@ impl IndexPage { v.extend_from_slice(bytes.as_slice()); // Validated: a torn index entry must be an error, not a dangling link. let archived = crate::access_archived::< as Archive>::Archived>(&v[..]) - .map_err(|error| eyre::eyre!("torn or corrupt index entry: {error}"))?; + .map_err(|_| crate::error::Error::Corrupt { + what: "index entry", + })?; Ok(rkyv::deserialize(archived).expect("data should be valid")) } @@ -186,7 +190,7 @@ impl IndexPage { page_id: PageId, size: usize, index: usize, - ) -> eyre::Result> + ) -> crate::error::Result> where T: Archive, ::Archived: Deserialize> @@ -245,7 +249,7 @@ impl IndexPage { size: usize, value: IndexValue, mut value_index: u16, - ) -> eyre::Result + ) -> crate::error::Result where T: Archive + Eq @@ -283,7 +287,7 @@ impl IndexPage { page_id: PageId, size: usize, value_index: u16, - ) -> eyre::Result<()> + ) -> crate::error::Result<()> where T: Archive + Default @@ -415,7 +419,7 @@ mod tests { #[tokio::test] async fn persist_and_remove_value_reject_writes_past_the_page_slot() { - use super::{IndexPageUtility, PageOverflowError, SizedIndexPageUtility}; + use super::{IndexPageUtility, SizedIndexPageUtility}; let path = std::env::temp_dir().join(format!( "data_bucket_slot_write_bounds_{}.wt", @@ -449,16 +453,16 @@ mod tests { .await .unwrap_err(); assert!( - err.downcast_ref::().is_some(), - "expected PageOverflowError, got: {err}" + matches!(err, crate::error::Error::PageOverflow { .. }), + "expected a page overflow, got: {err}" ); let err = IndexPage::::remove_value(&mut file, 1.into(), 4, 2000) .await .unwrap_err(); assert!( - err.downcast_ref::().is_some(), - "expected PageOverflowError, got: {err}" + matches!(err, crate::error::Error::PageOverflow { .. }), + "expected a page overflow, got: {err}" ); // A utility larger than the page slot must be rejected too. @@ -477,8 +481,8 @@ mod tests { .await .unwrap_err(); assert!( - err.downcast_ref::().is_some(), - "expected PageOverflowError, got: {err}" + matches!(err, crate::error::Error::PageOverflow { .. }), + "expected a page overflow, got: {err}" ); // Nothing was written by the rejected operations. diff --git a/src/page/index/page_cdc_impl.rs b/src/page/index/page_cdc_impl.rs index e49e943..1d826dd 100644 --- a/src/page/index/page_cdc_impl.rs +++ b/src/page/index/page_cdc_impl.rs @@ -1,6 +1,5 @@ use std::fmt::Debug; -use eyre::bail; use indexset::cdc::change::ChangeEvent; use indexset::core::pair::Pair; use rkyv::de::Pool; @@ -32,7 +31,10 @@ where + PartialOrd + Debug, { - pub fn apply_change_event(&mut self, event: ChangeEvent>) -> eyre::Result<()> { + pub fn apply_change_event( + &mut self, + event: ChangeEvent>, + ) -> crate::error::Result<()> { match event.clone() { ChangeEvent::InsertAt { event_id: _, @@ -67,13 +69,11 @@ where } ChangeEvent::SplitNode { .. } | ChangeEvent::CreateNode { .. } - | ChangeEvent::RemoveNode { .. } => { - bail!("Events of `SplitNode`, `CreateNode` or `RemoveNode` can not be applied") - } + | ChangeEvent::RemoveNode { .. } => Err(crate::error::Error::UnapplicableEvent), } } - fn apply_insert_at(&mut self, index: usize, value: Pair) -> eyre::Result<()> { + fn apply_insert_at(&mut self, index: usize, value: Pair) -> crate::error::Result<()> { // For insert we first add slot entry for our new index value self.slots.insert(index, self.current_index); self.slots.remove(self.size as usize); @@ -101,7 +101,7 @@ where Ok(()) } - fn apply_remove_at(&mut self, index: usize) -> eyre::Result<()> { + fn apply_remove_at(&mut self, index: usize) -> crate::error::Result<()> { // For remove we first remove slot entry for index value let value_position = self.slots.remove(index); // We push 0 in the tail because slots size should be fixed. diff --git a/src/page/index/page_for_unsized.rs b/src/page/index/page_for_unsized.rs index de25d9d..7e47bc5 100644 --- a/src/page/index/page_for_unsized.rs +++ b/src/page/index/page_for_unsized.rs @@ -14,7 +14,7 @@ use tokio::fs::File; use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; use crate::page::index::IndexPageUtility; -use crate::page::{PageId, PageOverflowError}; +use crate::page::PageId; use crate::{align8, VariableSizeMeasurable}; use crate::{seek_to_page_start, IndexValue, SizeMeasurable, GENERAL_HEADER_SIZE, INNER_PAGE_SIZE}; use crate::{Link, Persistable}; @@ -47,7 +47,7 @@ pub struct UnsizedIndexPageUtility UnsizedIndexPageUtility { - pub fn update_node_id(&mut self, node_id: IndexValue) -> eyre::Result<()> { + pub fn update_node_id(&mut self, node_id: IndexValue) -> crate::error::Result<()> { self.node_id_size = node_id.aligned_size() as u16; self.node_id = node_id; @@ -72,7 +72,7 @@ where async fn parse_index_page_utility( file: &mut File, page_id: PageId, - ) -> eyre::Result { + ) -> crate::error::Result { seek_to_page_start(file, page_id.0).await?; let offset = GENERAL_HEADER_SIZE as i64; file.seek(SeekFrom::Current(offset)).await?; @@ -84,7 +84,9 @@ where let archived = crate::access_archived::<::Archived>( &slot_size_bytes[0..UnsizedIndexPageUtility::::slots_size_size()], ) - .map_err(|error| eyre::eyre!("torn or corrupt unsized index page (slots size): {error}"))?; + .map_err(|_| crate::error::Error::Corrupt { + what: "unsized index page slots size", + })?; let slots_size = rkyv::deserialize::(archived).expect("data should be valid"); let mut node_id_size_bytes = vec![0u8; UnsizedIndexPageUtility::::node_id_size_size()]; @@ -92,8 +94,8 @@ where let archived = crate::access_archived::<::Archived>( &node_id_size_bytes[0..UnsizedIndexPageUtility::::node_id_size_size()], ) - .map_err(|error| { - eyre::eyre!("torn or corrupt unsized index page (node id size): {error}") + .map_err(|_error| crate::error::Error::Corrupt { + what: "unsized index page node id size", })?; let node_id_size = rkyv::deserialize::(archived).expect("data should be valid"); @@ -128,7 +130,7 @@ where ::Archived: Deserialize> + for<'a> rkyv::bytecheck::CheckBytes>, { - pub fn new(node_id: IndexValue) -> eyre::Result { + pub fn new(node_id: IndexValue) -> crate::error::Result { let len = node_id.aligned_size() as u32; Ok(Self { slots_size: 1, @@ -200,7 +202,7 @@ where page_id: PageId, current_offset: u32, value: IndexValue, - ) -> eyre::Result + ) -> crate::error::Result where T: Archive + Eq @@ -218,11 +220,11 @@ where // page end: once it passes the inner-page budget the write would // land in this page's header, or before it in the previous page. if offset > INNER_PAGE_SIZE as u64 { - return Err(eyre::Report::new(PageOverflowError { - page_id, - data_length: offset as usize, + return Err(crate::error::Error::PageOverflow { + page: page_id, + needed: offset as usize, capacity: INNER_PAGE_SIZE, - })); + }); } // We seek to page's end and will write values from tail. @@ -233,7 +235,7 @@ where Ok(offset as u32) } - async fn read_value(file: &mut File, len: u16) -> eyre::Result> + async fn read_value(file: &mut File, len: u16) -> crate::error::Result> where T: Archive, ::Archived: Deserialize> @@ -250,7 +252,9 @@ where v.extend_from_slice(bytes.as_slice()); // Validated: a torn index entry must be an error, not a dangling link. let archived = crate::access_archived::< as Archive>::Archived>(&v[..]) - .map_err(|error| eyre::eyre!("torn or corrupt unsized index entry: {error}"))?; + .map_err(|_| crate::error::Error::Corrupt { + what: "unsized index entry", + })?; Ok(rkyv::deserialize(archived).expect("data should be valid")) } @@ -259,7 +263,7 @@ where page_id: PageId, offset: u32, len: u16, - ) -> eyre::Result> + ) -> crate::error::Result> where T: Archive, ::Archived: Deserialize> @@ -386,7 +390,6 @@ where #[cfg(test)] mod test { - use crate::page::PageOverflowError; use crate::{IndexValue, Link, Persistable, UnsizedIndexPage, INNER_PAGE_SIZE}; #[tokio::test] @@ -428,8 +431,8 @@ mod test { .await .unwrap_err(); assert!( - err.downcast_ref::().is_some(), - "expected PageOverflowError, got: {err}" + matches!(err, crate::error::Error::PageOverflow { .. }), + "expected a page overflow, got: {err}" ); // A huge current offset used to wrap the u32 arithmetic and seek @@ -443,8 +446,8 @@ mod test { .await .unwrap_err(); assert!( - err.downcast_ref::().is_some(), - "expected PageOverflowError, got: {err}" + matches!(err, crate::error::Error::PageOverflow { .. }), + "expected a page overflow, got: {err}" ); // Nothing was written by the rejected operations. diff --git a/src/page/index/page_for_unsized_cdc_impl.rs b/src/page/index/page_for_unsized_cdc_impl.rs index 2ecc4c3..179a730 100644 --- a/src/page/index/page_for_unsized_cdc_impl.rs +++ b/src/page/index/page_for_unsized_cdc_impl.rs @@ -1,4 +1,3 @@ -use eyre::bail; use indexset::cdc::change::ChangeEvent; use indexset::core::pair::Pair; use rkyv::de::Pool; @@ -29,7 +28,10 @@ where ::Archived: Deserialize> + for<'a> rkyv::bytecheck::CheckBytes>, { - pub fn apply_change_event(&mut self, event: ChangeEvent>) -> eyre::Result<()> { + pub fn apply_change_event( + &mut self, + event: ChangeEvent>, + ) -> crate::error::Result<()> { match event { ChangeEvent::InsertAt { event_id: _, @@ -70,13 +72,11 @@ where } ChangeEvent::SplitNode { .. } | ChangeEvent::CreateNode { .. } - | ChangeEvent::RemoveNode { .. } => { - bail!("Events of `SplitNode`, `CreateNode` or `RemoveNode` can not be applied") - } + | ChangeEvent::RemoveNode { .. } => Err(crate::error::Error::UnapplicableEvent), } } - fn apply_insert_at(&mut self, index: usize, value: Pair) -> eyre::Result<()> { + fn apply_insert_at(&mut self, index: usize, value: Pair) -> crate::error::Result<()> { // For insert we first add slot entry for our new index value let index_value = IndexValue { key: value.key.clone(), @@ -99,7 +99,7 @@ where Ok(()) } - fn apply_remove_at(&mut self, index: usize) -> eyre::Result<()> { + fn apply_remove_at(&mut self, index: usize) -> crate::error::Result<()> { self.slots.remove(index); self.slots_size -= 1; let v = self.index_values.remove(index); diff --git a/src/page/util.rs b/src/page/util.rs index af1dd77..3ffd0c3 100644 --- a/src/page/util.rs +++ b/src/page/util.rs @@ -1,4 +1,4 @@ -use eyre::eyre; +use crate::error::Error; use rkyv::api::high::HighDeserializer; use rkyv::Archive; use std::io::SeekFrom; @@ -72,7 +72,7 @@ pub fn map_data_pages_to_general( pub async fn persist_page<'a, T>( page: &'a mut GeneralPage, file: &'a mut File, -) -> eyre::Result<()> +) -> crate::error::Result<()> where T: Persistable + Send + Sync, { @@ -92,7 +92,7 @@ where async fn persist_page_in_place<'a, T>( page: &'a mut GeneralPage, file: &'a mut File, -) -> eyre::Result<()> +) -> crate::error::Result<()> where T: Persistable + Send + Sync, { @@ -101,11 +101,11 @@ where // An over-budget page must fail here, in its own persist, instead of // silently corrupting the neighboring page. if inner_length > INNER_PAGE_SIZE { - return Err(eyre::Report::new(PageOverflowError { - page_id: page.header.page_id, - data_length: inner_length, + return Err(Error::PageOverflow { + page: page.header.page_id, + needed: inner_length, capacity: INNER_PAGE_SIZE, - })); + }); } page.header.data_length = inner_length as u32; file.write_all(page.header.as_bytes().as_ref()).await?; @@ -113,7 +113,10 @@ where Ok(()) } -pub async fn persist_pages_batch(pages: Vec>, file: &mut File) -> eyre::Result<()> +pub async fn persist_pages_batch( + pages: Vec>, + file: &mut File, +) -> crate::error::Result<()> where T: Persistable + Send + Sync, { @@ -143,12 +146,12 @@ pub(crate) fn page_start_offset(index: u32) -> u64 { index as u64 * PAGE_SIZE as u64 } -pub async fn seek_to_page_start(file: &mut File, index: u32) -> eyre::Result<()> { +pub async fn seek_to_page_start(file: &mut File, index: u32) -> crate::error::Result<()> { file.seek(SeekFrom::Start(page_start_offset(index))).await?; Ok(()) } -async fn seek_to_page_start_relatively(file: &mut File, index: u32) -> eyre::Result<()> { +async fn seek_to_page_start_relatively(file: &mut File, index: u32) -> crate::error::Result<()> { let curr_position = file.stream_position().await?; file.seek(SeekFrom::Current( page_start_offset(index) as i64 - curr_position as i64, @@ -157,7 +160,7 @@ async fn seek_to_page_start_relatively(file: &mut File, index: u32) -> eyre::Res Ok(()) } -pub async fn seek_by_link(file: &mut File, link: Link) -> eyre::Result<()> { +pub async fn seek_by_link(file: &mut File, link: Link) -> crate::error::Result<()> { file.seek(SeekFrom::Start( link.page_id.0 as u64 * PAGE_SIZE as u64 + GENERAL_HEADER_SIZE as u64 + link.offset as u64, )) @@ -170,24 +173,22 @@ pub async fn update_at( file: &mut File, link: Link, new_data: &[u8], -) -> eyre::Result<()> { +) -> crate::error::Result<()> { if new_data.len() as u32 != link.length { - return Err(eyre!( - "New data length {} does not match link length {}", - new_data.len(), - link.length - )); + return Err(Error::LinkLengthMismatch { + expected: link.length, + found: new_data.len(), + }); } // Sum in u64: `offset + length` in u32 can wrap past 4 GiB and slip // under the bound, letting the write land outside the page. if link.offset as u64 + link.length as u64 > DATA_LENGTH as u64 { - return Err(eyre!( - "Link range (offset: {}, length: {}) exceeds data bounds ({})", - link.offset, - link.length, - DATA_LENGTH - )); + return Err(Error::LinkOutOfBounds { + offset: link.offset, + length: link.length, + capacity: DATA_LENGTH as usize, + }); } seek_by_link(file, link).await?; @@ -195,15 +196,19 @@ pub async fn update_at( Ok(()) } -pub async fn parse_general_header(file: &mut File) -> eyre::Result { +pub async fn parse_general_header(file: &mut File) -> crate::error::Result { let mut buffer = [0; GENERAL_HEADER_SIZE]; file.read_exact(&mut buffer).await?; // Validated: a header torn by a mid-write death must surface as an error // naming the page, not as undefined behavior in whatever reads it next. let archived = crate::access_archived::<::Archived>(&buffer[..]) - .map_err(|error| eyre::eyre!("torn or corrupt page header: {error}"))?; - let header = rkyv::deserialize::<_, rkyv::rancor::Error>(archived) - .map_err(|error| eyre::eyre!("page header failed to deserialize: {error}"))?; + .map_err(|_| Error::Corrupt { + what: "page header", + })?; + let header = + rkyv::deserialize::<_, rkyv::rancor::Error>(archived).map_err(|_| Error::Corrupt { + what: "page header", + })?; Ok(header) } @@ -211,7 +216,7 @@ pub async fn parse_general_header(file: &mut File) -> eyre::Result( file: &mut File, index: u32, -) -> eyre::Result> +) -> crate::error::Result> where Page: rkyv::Archive + Persistable, ::Archived: @@ -223,7 +228,7 @@ where async fn parse_page_in_place( file: &mut File, -) -> eyre::Result> +) -> crate::error::Result> where Page: rkyv::Archive + Persistable, ::Archived: @@ -249,7 +254,7 @@ where pub async fn parse_pages_batch( file: &mut File, indexes: Vec, -) -> eyre::Result>> +) -> crate::error::Result>> where Page: rkyv::Archive + Persistable, ::Archived: @@ -277,7 +282,7 @@ where pub async fn parse_general_header_by_index( file: &mut File, index: u32, -) -> eyre::Result { +) -> crate::error::Result { seek_to_page_start(file, index).await?; let header = parse_general_header(file).await?; @@ -287,14 +292,14 @@ pub async fn parse_general_header_by_index( pub async fn parse_data_page( file: &mut File, index: u32, -) -> eyre::Result>> { +) -> crate::error::Result>> { seek_to_page_start(file, index).await?; parse_data_page_in_place::(file).await } async fn parse_data_page_in_place( file: &mut File, -) -> eyre::Result>> { +) -> crate::error::Result>> { let header = parse_general_header(file).await?; let mut buffer = [0u8; INNER_PAGE_SIZE]; @@ -319,7 +324,7 @@ async fn parse_data_page_in_place( file: &mut File, indexes: Vec, -) -> eyre::Result>>> { +) -> crate::error::Result>>> { let mut iter = indexes.into_iter(); if let Some(index) = iter.next() { let mut pages = vec![]; @@ -345,7 +350,7 @@ pub async fn parse_data_pages_batch, -// ) -> eyre::Result> { +// ) -> crate::error::Result> { // seek_to_page_start(file, index)?; // let header = parse_general_header(file)?; // if header.page_type != PageType::Data { @@ -365,7 +370,7 @@ pub async fn parse_data_pages_batch( file: &mut File, -) -> eyre::Result { +) -> crate::error::Result { file.seek(SeekFrom::Start(0)).await?; let header = parse_general_header(file).await?; @@ -378,7 +383,7 @@ pub async fn parse_space_info( // pub fn read_index_pages( // file: &mut std::fs::File, // length: u32, -// ) -> eyre::Result>> +// ) -> crate::error::Result>> // where // T: Archive, // ::Archived: rkyv::Deserialize>, @@ -394,7 +399,7 @@ pub async fn parse_space_info( // fn read_links( // mut file: &mut std::fs::File, // space_info: &SpaceInfo, -// ) -> eyre::Result> { +// ) -> crate::error::Result> { // Ok( // read_index_pages::(&mut file, space_info.primary_key_length)? // .iter() @@ -405,14 +410,14 @@ pub async fn parse_space_info( // // pub fn read_rows_schema( // file: &mut std::fs::File, -// ) -> eyre::Result> { +// ) -> crate::error::Result> { // let space_info = parse_space_info::(file)?; // Ok(space_info.row_schema) // } // // pub fn read_data_pages( // mut file: &mut std::fs::File, -// ) -> eyre::Result>> { +// ) -> crate::error::Result>> { // let space_info = parse_space_info::(file)?; // let primary_key_fields = &space_info.primary_key_fields; // if primary_key_fields.len() != 1 { @@ -535,8 +540,8 @@ mod tests { let err = super::persist_page(&mut page, &mut file).await.unwrap_err(); assert!( - err.downcast_ref::().is_some(), - "expected PageOverflowError, got: {err}" + matches!(err, crate::error::Error::PageOverflow { .. }), + "expected a page overflow, got: {err}" ); // Nothing may have been written: the neighboring page is the one an @@ -576,7 +581,7 @@ mod tests { let err = super::update_at::<100>(&mut file, link, &[1, 2, 3, 4, 5, 6, 7, 8]) .await .unwrap_err(); - assert!(err.to_string().contains("exceeds data bounds")); + assert!(matches!(err, crate::error::Error::LinkOutOfBounds { .. })); drop(file); std::fs::remove_file(&path).unwrap();