Better error management, Utf8Paths & other stuff.
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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<()>;
|
||||
}
|
||||
@@ -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() }
|
||||
|
||||
@@ -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)
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user