Better error management, Utf8Paths & other stuff.

This commit is contained in:
2022-08-08 11:40:45 +02:00
parent b1ceaaf636
commit 5d08b1ea57
33 changed files with 1483 additions and 1216 deletions

View File

@@ -2,12 +2,13 @@
name = "cas-core"
version = "0.1.0"
authors = ["Simon Boyé <sim.boye@gmail.com>"]
edition = "2018"
edition = "2021"
license = "AGPL-3.0-or-later"
[dependencies]
thiserror = "1.0.25"
digest = { version = "0.9.0", features = ["alloc"] }
camino = { version = "1.0.7" }
[dev-dependencies]
sha2 = "0.9.5"

View File

@@ -14,8 +14,9 @@
// along with cdb. If not, see <https://www.gnu.org/licenses/>.
use std::path::Path;
use camino::Utf8Path;
use super::err;
use super::error::{Error, Result};
use super::object_id::ObjectId;
use super::object_type::ObjectType;
@@ -40,21 +41,21 @@ pub trait Cas {
let mut data = Vec::new();
reader.read_to_end(&mut data).or_else(|err|
Err(Error::error(format!("failed to read object {}: {}", oid, err))))?;
err!("failed to read object {}: {}", oid, err))?;
let result_oid = reader.finalize()?;
if &result_oid == oid {
Ok((metadata, data))
}
else {
Err(Error::error(format!("object id mismatch: requested {}, read {}", oid, result_oid)))
err!("object id mismatch: requested {}, read {}", oid, result_oid)
}
}
fn write_object(&mut self, otype: &ObjectType, data: &[u8]) -> Result<ObjectId> {
let mut writer = self.new_writer(otype, data.len() as u64)?;
writer.write_all(data).or_else(|err|
Err(Error::error(format!("failed to write object: {}", err))))?;
err!("failed to write object: {}", err))?;
writer.finalize()
}
@@ -62,7 +63,7 @@ pub trait Cas {
}
pub trait RefStore {
fn get_ref<P: AsRef<Path>>(&self, key: P) -> Result<ObjectId>;
fn set_ref<P: AsRef<Path>>(&mut self, key: P, value: &ObjectId) -> Result<()>;
fn remove_ref<P: AsRef<Path>>(&mut self, key: P) -> Result<()>;
fn get_ref<P: AsRef<Utf8Path>>(&self, key: P) -> Result<ObjectId>;
fn set_ref<P: AsRef<Utf8Path>>(&mut self, key: P, value: &ObjectId) -> Result<()>;
fn remove_ref<P: AsRef<Utf8Path>>(&mut self, key: P) -> Result<()>;
}

View File

@@ -14,15 +14,15 @@
// along with cdb. If not, see <https://www.gnu.org/licenses/>.
// use std::path::PathBuf;
// use std::path::Utf8PathBuf;
/// Result type used through cas-core.
pub type Result<T> = std::result::Result<T, Box<Error>>;
pub type Result<T> = std::result::Result<T, Error>;
/// Error type used through cas-core.
#[non_exhaustive]
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[derive(Debug, thiserror::Error)]
pub enum Error {
// #[error("failed to create repository: {message}")]
// RepositoryCreationFailed {
@@ -44,7 +44,7 @@ pub enum Error {
// #[error("non-empty directory ({dir})")]
// NonEmptyDirectory {
// dir: PathBuf
// dir: Utf8PathBuf
// },
// #[error("invalid character(s) ({characters})")]
@@ -67,16 +67,25 @@ pub enum Error {
// UnsupportedFileType,
// #[error("invalid path ({path})")]
// InvalidPath { path: PathBuf },
// InvalidPath { path: Utf8PathBuf },
// #[error("io error{}", format_optional_path(path))]
// IoError {
// source: std::io::Error,
// path: Option<PathBuf>,
// path: Option<Utf8PathBuf>,
// },
#[error("item skipped")]
Skipped,
#[error("logic error: {0}")]
LogicError(String),
#[error("io error: {0}")]
IoError(#[from] std::io::Error),
#[error("{0}")]
Error(String),
UnknownError(String),
}
impl Error {
@@ -108,17 +117,17 @@ impl Error {
// })
// }
pub fn error<M: Into<String>>(message: M) -> Box<Error> {
Box::new(Error::Error(message.into()))
pub fn unknown<M: Into<String>>(message: M) -> Error {
Error::UnknownError(message.into())
}
pub fn err<M: Into<String>, T>(message: M) -> Result<T> {
Err(Self::error(message))
Err(Self::unknown(message))
}
}
// fn format_optional_path(maybe_path: &Option<PathBuf>) -> String {
// fn format_optional_path(maybe_path: &Option<Utf8PathBuf>) -> String {
// match maybe_path {
// Some(path) => { format!(" ({:?})", path) },
// None => { String::new() }

View File

@@ -18,11 +18,10 @@
//! `cas-core` provides traits and types to interface with content-addressable
//! storage.
#![feature(inherent_ascii_escape)]
extern crate thiserror;
extern crate digest;
extern crate camino;
mod error;
@@ -42,3 +41,11 @@ pub use crate::{
cas::{Cas, RefStore},
};
#[macro_export]
macro_rules! err {
($($arg:tt)*) => {{
let res = std::fmt::format(format_args!($($arg)*));
Error::err(res)
}}
}

View File

@@ -18,6 +18,7 @@ use std::fmt;
use std::str::{FromStr};
use std::sync::Arc;
use super::err;
use super::error::*;
@@ -43,7 +44,7 @@ impl ObjectId {
}
impl FromStr for ObjectId {
type Err = Box<Error>;
type Err = Error;
fn from_str(id_str: &str) -> Result<ObjectId> {
Ok(ObjectId {
@@ -138,7 +139,7 @@ impl<'a> Iterator for CharPairs<'a> {
Some(Ok(&self.string[start_index..self.index]))
}
else {
Some(Err(Error::error(&format!("invalid string: got {} characters, expected even number", self.char_count))))
Some(err!("invalid string: got {} characters, expected even number", self.char_count))
}
}
}
@@ -148,7 +149,7 @@ fn parse_byte(maybe_str_byte: Result<&str>) -> Result<u8> {
match maybe_str_byte {
Ok(str_byte) =>
u8::from_str_radix(str_byte, 16)
.map_err(|_| Error::error("invalid character")),
.map_err(|_| Error::unknown("invalid character")),
Err(err) => Err(err),
}
}
@@ -194,19 +195,23 @@ mod tests {
fn str_to_object_id_invalid_size() {
let result = ObjectId::from_str("000102030405060");
assert_eq!(
result.expect_err("object id parsing should have failed"),
Error::error("invalid string: got 15 characters, expected even number"),
);
assert!(result.is_err());
match result.expect_err("result is not an error") {
Error::UnknownError(ref msg) if msg == "invalid string: got 15 characters, expected even number"
=> {},
_ => panic!("result is not the expected error"),
}
}
#[test]
fn str_to_object_id_invalid_character() {
let result = ObjectId::from_str("00010203 4050607");
assert_eq!(
result.expect_err("object id parsing should have failed"),
Error::error("invalid character"),
);
match result.expect_err("result is not an error") {
Error::UnknownError(ref msg) if msg == "invalid character"
=> {},
_ => panic!("result is not the expected error"),
}
}
}

View File

@@ -13,6 +13,7 @@
// You should have received a copy of the Affero GNU General Public License
// along with cdb. If not, see <https://www.gnu.org/licenses/>.
use super::err;
use super::error::*;
@@ -24,7 +25,10 @@ pub struct ObjectType {
impl ObjectType {
pub fn new(id: &[u8]) -> Result<Self> {
if id.len() != 4 {
Err(Error::error("Invalid object type size."))
err!("invalid object type size")
}
else if let Err(err) = std::str::from_utf8(id) {
err!("invalid object type, contain non-utf8 sequence: {}", err)
}
else {
let mut buf = [0; 4];
@@ -48,6 +52,16 @@ impl std::fmt::Debug for ObjectType {
}
}
impl std::fmt::Display for ObjectType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
// Safe, because we check at ObjectType creation that it is valid utf8
let id = unsafe {
std::str::from_utf8_unchecked(&self.id)
};
write!(f, "{}", id)
}
}
#[cfg(test)]
mod tests {

View File

@@ -15,6 +15,7 @@
use digest::DynDigest;
use super::err;
use super::error::*;
use super::object_id::ObjectId;
@@ -53,7 +54,7 @@ impl<R: std::io::Read> std::io::Read for ReadWrapper<R> {
impl<R: std::io::Read> Reader for ReadWrapper<R> {
fn finalize(self: Box<Self>) -> Result<ObjectId> {
Err(Error::error("reader pipline has no digest step"))
err!("reader pipline has no digest step")
}
}
@@ -80,7 +81,7 @@ impl<W: std::io::Write> std::io::Write for WriteWrapper<W> {
impl<W: std::io::Write> Writer for WriteWrapper<W> {
fn finalize(self: Box<Self>) -> Result<ObjectId> {
Err(Error::error("reader pipline has no digest step"))
err!("reader pipline has no digest step")
}
fn _finalize(self: Box<Self>, oid: ObjectId) -> Result<ObjectId> {
@@ -97,7 +98,7 @@ pub struct DigestReader<'a> {
impl<'a> DigestReader<'a> {
pub fn new(reader: Box<dyn Reader + 'a>, digest: Box<dyn DynDigest + 'a>) -> Self {
return DigestReader {
DigestReader {
reader,
digest: Some(digest),
oid: None,
@@ -128,7 +129,7 @@ impl<'a> std::io::Read for DigestReader<'a> {
impl<'a> Reader for DigestReader<'a> {
fn finalize(self: Box<Self>) -> Result<ObjectId> {
self.oid.ok_or_else(|| Error::error("Reader not finalized"))
self.oid.ok_or_else(|| Error::unknown("Reader not finalized"))
}
}
@@ -140,7 +141,7 @@ pub struct DigestWriter<'a> {
impl<'a> DigestWriter<'a> {
pub fn new(writer: Box<dyn Writer + 'a>, digest: Box<dyn DynDigest + 'a>) -> Self {
return DigestWriter {
DigestWriter {
writer,
digest,
}
@@ -166,7 +167,7 @@ impl<'a> Writer for DigestWriter<'a> {
}
fn _finalize(self: Box<Self>, _oid: ObjectId) -> Result<ObjectId> {
Err(Error::error("writer pipeline has several digest steps"))
err!("writer pipeline has several digest steps")
}
}