From 5664beca579464e5900f85d9a4f6ae1370e5f093 Mon Sep 17 00:00:00 2001 From: Draklaw Date: Sun, 1 Aug 2021 17:06:50 +0200 Subject: [PATCH] Start implementing a cas in separate crates. --- Cargo.lock | 297 ++++++-------------------------- Cargo.toml | 6 +- cas-core/Cargo.toml | 13 ++ cas-core/src/cas.rs | 40 +++++ cas-core/src/error.rs | 122 +++++++++++++ cas-core/src/lib.rs | 44 +++++ cas-core/src/object_id.rs | 212 +++++++++++++++++++++++ cas-core/src/object_metadata.rs | 61 +++++++ cas-core/src/object_type.rs | 63 +++++++ cas-core/src/pipeline.rs | 252 +++++++++++++++++++++++++++ cas-simple/Cargo.toml | 15 ++ cas-simple/src/lib.rs | 245 ++++++++++++++++++++++++++ libbsv/Cargo.toml | 2 +- libbsv/src/core/error.rs | 137 ++++++++++----- libbsv/src/core/mod.rs | 18 +- libbsv/src/core/object.rs | 11 +- libbsv/src/core/object_id.rs | 6 +- libbsv/src/core/repository.rs | 6 +- libbsv/src/lib.rs | 20 +-- 19 files changed, 1248 insertions(+), 322 deletions(-) create mode 100644 cas-core/Cargo.toml create mode 100644 cas-core/src/cas.rs create mode 100644 cas-core/src/error.rs create mode 100644 cas-core/src/lib.rs create mode 100644 cas-core/src/object_id.rs create mode 100644 cas-core/src/object_metadata.rs create mode 100644 cas-core/src/object_type.rs create mode 100644 cas-core/src/pipeline.rs create mode 100644 cas-simple/Cargo.toml create mode 100644 cas-simple/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 94f6cc6..39207ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,62 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03345e98af8f3d786b6d9f656ccfa6ac316d954e92bc4841f0bba20789d5fb5a" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" - -[[package]] -name = "backtrace" -version = "0.3.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4717cfcbfaa661a0fd48f8453951837ae7e8f81e481fbb136e3202d72805a744" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "bitflags" version = "1.2.1" @@ -74,20 +18,24 @@ dependencies = [ ] [[package]] -name = "bsv" +name = "cas-core" version = "0.1.0" dependencies = [ - "clap", - "error-chain", - "hostname", - "libbsv", + "digest", + "sha2", + "thiserror", ] [[package]] -name = "cc" -version = "1.0.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" +name = "cas-simple" +version = "0.1.0" +dependencies = [ + "cas-core", + "digest", + "sha2", + "tempfile", + "toml", +] [[package]] name = "cfg-if" @@ -95,39 +43,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "clap" -version = "2.33.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" -dependencies = [ - "ansi_term", - "atty", - "bitflags", - "strsim", - "textwrap", - "unicode-width", - "vec_map", -] - [[package]] name = "cpufeatures" -version = "0.1.1" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec1028182c380cc45a2e2c5ec841134f2dfd0f8f5f0a5bcd68004f81b5efdf4" +checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" dependencies = [ "libc", ] -[[package]] -name = "crc32fast" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" -dependencies = [ - "cfg-if", -] - [[package]] name = "digest" version = "0.9.0" @@ -137,28 +61,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "error-chain" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" -dependencies = [ - "backtrace", - "version_check", -] - -[[package]] -name = "flate2" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" -dependencies = [ - "cfg-if", - "crc32fast", - "libc", - "miniz_oxide", -] - [[package]] name = "generic-array" version = "0.14.4" @@ -171,82 +73,20 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if", "libc", "wasi", ] -[[package]] -name = "gimli" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" - -[[package]] -name = "hermit-abi" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" -dependencies = [ - "libc", -] - -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - -[[package]] -name = "libbsv" -version = "0.1.0" -dependencies = [ - "digest", - "error-chain", - "flate2", - "serde", - "sha2", - "tempfile", - "toml", - "uuid", -] - [[package]] name = "libc" -version = "0.2.94" +version = "0.2.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" - -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - -[[package]] -name = "object" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170" +checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" [[package]] name = "opaque-debug" @@ -262,9 +102,9 @@ checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "proc-macro2" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" dependencies = [ "unicode-xid", ] @@ -280,9 +120,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" dependencies = [ "libc", "rand_chacha", @@ -292,9 +132,9 @@ dependencies = [ [[package]] name = "rand_chacha" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", @@ -302,27 +142,27 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ "getrandom", ] [[package]] name = "rand_hc" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" dependencies = [ "rand_core", ] [[package]] name = "redox_syscall" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" +checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" dependencies = [ "bitflags", ] @@ -336,31 +176,11 @@ dependencies = [ "winapi", ] -[[package]] -name = "rustc-demangle" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "410f7acf3cb3a44527c5d9546bad4bf4e6c460915d5f9f2fc524498bfe8f70ce" - [[package]] name = "serde" -version = "1.0.126" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8" [[package]] name = "sha2" @@ -375,17 +195,11 @@ dependencies = [ "opaque-debug", ] -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - [[package]] name = "syn" -version = "1.0.72" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" +checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" dependencies = [ "proc-macro2", "quote", @@ -407,12 +221,23 @@ dependencies = [ ] [[package]] -name = "textwrap" -version = "0.11.0" +name = "thiserror" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" dependencies = [ - "unicode-width", + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -430,34 +255,12 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" -[[package]] -name = "unicode-width" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" - [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -dependencies = [ - "getrandom", - "serde", -] - -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.9.3" diff --git a/Cargo.toml b/Cargo.toml index 168cf3e..b56b9b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,8 @@ [workspace] members = [ - "libbsv", - "bsv", + "cas-core", + "cas-simple", + # "libbsv", + # "bsv", ] diff --git a/cas-core/Cargo.toml b/cas-core/Cargo.toml new file mode 100644 index 0000000..59adf6f --- /dev/null +++ b/cas-core/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "cas-core" +version = "0.1.0" +authors = ["Simon Boyé "] +edition = "2018" +license = "AGPL-3.0-or-later" + +[dependencies] +thiserror = "1.0.25" +digest = { version = "0.9.0", features = ["alloc"] } + +[dev-dependencies] +sha2 = "0.9.5" diff --git a/cas-core/src/cas.rs b/cas-core/src/cas.rs new file mode 100644 index 0000000..089e7de --- /dev/null +++ b/cas-core/src/cas.rs @@ -0,0 +1,40 @@ +// This file is part of bsv. +// +// bsv is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// cdb is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for +// more details. +// +// You should have received a copy of the Affero GNU General Public License +// along with cdb. If not, see . + + +use super::error::Result; +use super::object_id::ObjectId; +use super::object_type::ObjectType; +use super::object_metadata::ObjectMetadata; + + +// pub struct ObjectIdIterator; + + +pub trait Cas { + fn object_id_from_string(&self, hex: &str) -> Result; + fn object_id_from_partial(&self, hex: &str) -> Result; + + fn has_object_id(&self, oid: &ObjectId) -> Result; + // fn iter_object_id(&self) -> Result; + + fn read_object(&self, oid: &ObjectId) -> Result<(ObjectMetadata, &dyn std::io::Read)>; + fn write_object(&mut self, otype: ObjectType, data: &mut dyn std::io::Read) -> Result; + fn remove_object(&mut self, oid: &ObjectId) -> Result<()>; + + fn read_ref(&self, key: &str) -> Result; + fn write_ref(&mut self, key: &str, value: &ObjectId) -> Result<()>; + fn remove_ref(&mut self, key: &str) -> Result<()>; +} \ No newline at end of file diff --git a/cas-core/src/error.rs b/cas-core/src/error.rs new file mode 100644 index 0000000..6f36aa2 --- /dev/null +++ b/cas-core/src/error.rs @@ -0,0 +1,122 @@ +// This file is part of bsv. +// +// bsv is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// cdb is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for +// more details. +// +// You should have received a copy of the Affero GNU General Public License +// along with cdb. If not, see . + + +// use std::path::PathBuf; + + +/// Result type used through cas-core. +pub type Result = std::result::Result>; + +/// Error type used through cas-core. +#[non_exhaustive] +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +pub enum Error { + // #[error("failed to create repository: {message}")] + // RepositoryCreationFailed { + // message: String, + // source: Option>, + // }, + + // #[error("invalid object id: {message}")] + // InvalidObjectId { + // message: String, + // source: Option>, + // }, + + // #[error("invalid size (got: {size}, expected: {expected})")] + // InvalidSize { + // size: usize, + // expected: usize, + // }, + + // #[error("non-empty directory ({dir})")] + // NonEmptyDirectory { + // dir: PathBuf + // }, + + // #[error("invalid character(s) ({characters})")] + // InvalidCharacters { + // characters: String, + // }, + + // #[error("invalid object type ({otype:?})")] + // InvalidObjectType { + // otype: [u8; 4], + // }, + + // #[error("invalid object size (expected {expected}, got {size})")] + // InvalidObjectSize { + // size: u64, + // expected: u64, + // }, + + // #[error("unsupported file type")] + // UnsupportedFileType, + + // #[error("invalid path ({path})")] + // InvalidPath { path: PathBuf }, + + // #[error("io error{}", format_optional_path(path))] + // IoError { + // source: std::io::Error, + // path: Option, + // }, + + #[error("{0}")] + Error(String), +} + +impl Error { + // pub fn repository_creation_failed>(message: M) -> Box { + // Box::new(Error::RepositoryCreationFailed { + // message: message.into(), + // source: None, + // }) + // } + + // pub fn repository_creation_failed_from>(source: Box, message: M) -> Box { + // Box::new(Error::RepositoryCreationFailed { + // message: message.into(), + // source: Some(source), + // }) + // } + + // pub fn invalid_object_id>(message: M) -> Box { + // Box::new(Error::InvalidObjectId { + // message: message.into(), + // source: None, + // }) + // } + + // pub fn invalid_object_id_from>(source: Box, message: M) -> Box { + // Box::new(Error::InvalidObjectId { + // message: message.into(), + // source: Some(source), + // }) + // } + + pub fn error>(message: M) -> Box { + Box::new(Error::Error(message.into())) + } +} + + +// fn format_optional_path(maybe_path: &Option) -> String { +// match maybe_path { +// Some(path) => { format!(" ({:?})", path) }, +// None => { String::new() } +// } +// } diff --git a/cas-core/src/lib.rs b/cas-core/src/lib.rs new file mode 100644 index 0000000..1f2e681 --- /dev/null +++ b/cas-core/src/lib.rs @@ -0,0 +1,44 @@ +// This file is part of bsv. +// +// bsv is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// cdb is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for +// more details. +// +// You should have received a copy of the Affero GNU General Public License +// along with cdb. If not, see . + +//! # cas-core +//! +//! `cas-core` provides traits and types to interface with content-addressable +//! storage. + +#![feature(inherent_ascii_escape)] + + +extern crate thiserror; +extern crate digest; + + +mod error; +mod object_id; +mod object_type; +mod object_metadata; +mod pipeline; +mod cas; + + +pub use crate::{ + error::{Error, Result}, + object_id::{ObjectId, hex, write_hex}, + object_type::{ObjectType}, + object_metadata::{ObjectMetadata}, + pipeline::{Pipeline, DefaultPipeline}, + cas::{Cas}, +}; + diff --git a/cas-core/src/object_id.rs b/cas-core/src/object_id.rs new file mode 100644 index 0000000..2ce5c88 --- /dev/null +++ b/cas-core/src/object_id.rs @@ -0,0 +1,212 @@ +// This file is part of bsv. +// +// bsv is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// cdb is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for +// more details. +// +// You should have received a copy of the Affero GNU General Public License +// along with cdb. If not, see . + + +use std::fmt; +use std::str::{FromStr}; +use std::sync::Arc; + +use super::error::*; + + +/// A unique identifier for an object. +/// +/// This is the handle used to reference an Object. This is an opaque type that uniquely identify an +/// object. It can be compared to another ObjectId, be hashed and that's about it. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ObjectId { + id: Arc>, +} + +impl ObjectId { + pub fn new(id: &[u8]) -> ObjectId { + ObjectId { + id: Arc::new(id.into()), + } + } + + pub fn id(&self) -> &[u8] { + self.id.as_slice() + } +} + +impl FromStr for ObjectId { + type Err = Box; + + fn from_str(id_str: &str) -> Result { + Ok(ObjectId { + id: Arc::new( + CharPairs::new(id_str, false) + .map(parse_byte) + .collect::>>()? + ), + }) + } +} + +impl fmt::Display for ObjectId { + fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> { + write_hex(f, self.id.as_slice()) + } +} + +impl fmt::Debug for ObjectId { + fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> { + write!(f, "ObjectId::new(\"{}\")", self) + } +} + + +pub fn hex(bytes: &[u8]) -> String { + let mut hex = String::with_capacity(bytes.len() * 2); + write_hex(&mut hex, bytes).expect("can't format bytes ?"); + hex +} + +pub fn write_hex(write: &mut W, bytes: &[u8]) + -> std::result::Result<(), std::fmt::Error> +{ + for byte in bytes.iter() { + write!(write, "{:02x}", byte)?; + } + Ok(()) +} + + +struct CharPairs<'a> { + string: &'a str, + chars: std::str::CharIndices<'a>, + char_count: usize, + index: usize, + allow_partial: bool, +} + +impl<'a> CharPairs<'a> { + fn new(string: &'a str, allow_partial: bool) -> Self { + let mut char_pairs = Self { + string, + chars: string.char_indices(), + char_count: 0, + index: 0, + allow_partial, + }; + char_pairs.next_char(); + char_pairs + } + + fn next_char(&mut self) -> bool { + if self.index == self.string.len() { + false + } + else { + match self.chars.next() { + Some((i, _)) => { + self.char_count += 1; + self.index = i; + }, + None => { + self.index = self.string.len(); + } + } + true + } + } +} + +impl<'a> Iterator for CharPairs<'a> { + type Item = Result<&'a str>; + + fn next(&mut self) -> Option { + let start_index = self.index; + if !self.next_char() { + return None; + } + + if self.next_char() || self.allow_partial { + Some(Ok(&self.string[start_index..self.index])) + } + else { + Some(Err(Error::error(&format!("invalid string: got {} characters, expected even number", self.char_count)))) + } + } +} + + +fn parse_byte(maybe_str_byte: Result<&str>) -> Result { + match maybe_str_byte { + Ok(str_byte) => + u8::from_str_radix(str_byte, 16) + .map_err(|_| Error::error("invalid character")), + Err(err) => Err(err), + } +} + + +#[cfg(test)] +mod tests { + use std::str::{FromStr}; + + use crate::error::*; + use super::ObjectId; + + #[test] + fn object_id_display() { + let obj = ObjectId::new(&[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + ]); + + assert_eq!(format!("{}", obj), "0001020304050607"); + } + + #[test] + fn object_id_debug() { + let obj = ObjectId::new(&[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + ]); + + assert_eq!(format!("{:?}", obj), "ObjectId::new(\"0001020304050607\")"); + } + + #[test] + fn str_to_object_id() { + let obj = ObjectId::from_str("0001020304050607") + .expect("object id parsing failed"); + let obj_ref = ObjectId::new(&[ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + ]); + + assert_eq!(obj, obj_ref); + } + + #[test] + 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"), + ); + } + + #[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"), + ); + } +} diff --git a/cas-core/src/object_metadata.rs b/cas-core/src/object_metadata.rs new file mode 100644 index 0000000..aeae1ea --- /dev/null +++ b/cas-core/src/object_metadata.rs @@ -0,0 +1,61 @@ +// This file is part of bsv. +// +// bsv is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// cdb is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for +// more details. +// +// You should have received a copy of the Affero GNU General Public License +// along with cdb. If not, see . + + +use super::object_type::{ObjectType}; + + +#[derive(Clone, Eq, PartialEq)] +pub struct ObjectMetadata { + otype: ObjectType, + size: usize, +} + +impl ObjectMetadata { + pub fn new(otype: ObjectType, size: usize) -> Self { + Self { + otype, + size, + } + } + + pub fn otype(&self) -> &ObjectType { + &self.otype + } + + pub fn size(&self) -> usize { + self.size + } +} + +impl std::fmt::Debug for ObjectMetadata { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.write_fmt(format_args!("", self.otype().id().escape_ascii().to_string(), self.size)) + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn object_metadata_debug() { + let meta = ObjectMetadata::new(ObjectType::new(b"tree").unwrap(), 1234); + + assert_eq!(meta, meta); + assert_eq!(format!("{:?}", meta), ""); + } +} diff --git a/cas-core/src/object_type.rs b/cas-core/src/object_type.rs new file mode 100644 index 0000000..3063d8c --- /dev/null +++ b/cas-core/src/object_type.rs @@ -0,0 +1,63 @@ +// This file is part of bsv. +// +// bsv is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// cdb is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for +// more details. +// +// You should have received a copy of the Affero GNU General Public License +// along with cdb. If not, see . + +use super::error::*; + + +#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct ObjectType { + id: [u8; 4], +} + +impl ObjectType { + pub fn new(id: &[u8]) -> Result { + if id.len() != 4 { + Err(Error::error("Invalid object type size.")) + } + else { + let mut buf = [0; 4]; + for (i, b) in id.iter().enumerate() { + buf[i] = *b; + } + Ok(Self { + id: buf, + }) + } + } + + pub fn id(&self) -> &[u8; 4] { + &self.id + } +} + +impl std::fmt::Debug for ObjectType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.write_fmt(format_args!("", self.id.escape_ascii().to_string())) + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn object_type_debug() { + let otype = ObjectType::new(b"tree").unwrap(); + + assert_eq!(otype, otype); + assert_eq!(format!("{:?}", otype), ""); + } +} diff --git a/cas-core/src/pipeline.rs b/cas-core/src/pipeline.rs new file mode 100644 index 0000000..487720c --- /dev/null +++ b/cas-core/src/pipeline.rs @@ -0,0 +1,252 @@ +// This file is part of bsv. +// +// bsv is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// cdb is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for +// more details. +// +// You should have received a copy of the Affero GNU General Public License +// along with cdb. If not, see . + +use digest::DynDigest; + +use super::error::*; +use super::object_id::ObjectId; + + +pub trait Reader { + fn read(&mut self, buf: &mut [u8]) -> Result; + fn finalize(self: Box) -> Result; + + fn read_all(&mut self) -> Result> { + let mut buffer = [0u8; 1 << 13]; + let mut data = Vec::new(); + let mut count = usize::MAX; + + while count != 0 { + count = self.read(&mut buffer)?; + if count != 0 { + data.extend_from_slice(&buffer[..count]); + } + } + + Ok(data) + } +} + +pub trait Writer { + fn write(&mut self, buf: &[u8]) -> Result<()>; + fn finalize(self: Box) -> Result; +} + + +pub trait Pipeline { + fn new_reader(&self, reader: Box) -> Box; + fn new_writer(&self, writer: Box) -> Box; +} + + +impl Reader for R { + fn read(&mut self, buf: &mut [u8]) -> Result { + ::read(self, buf).or_else(|err| + Err(Error::error(format!("Read error: {}", err))) + ) + } + + fn finalize(self: Box) -> Result { + Err(Error::error("reader pipline has no digest step")) + } +} + + +impl Writer for W { + fn write(&mut self, buf: &[u8]) -> Result<()> { + self.write_all(buf).or_else(|err| + Err(Error::error(format!("Write error: {}", err))) + ) + } + + fn finalize(self: Box) -> Result { + Err(Error::error("reader pipline has no digest step")) + } +} + + +pub struct DigestReader<'a> { + reader: Box, + digest: Option>, + oid: Option, +} + +impl<'a> DigestReader<'a> { + pub fn new(reader: Box, digest: Box) -> Self { + return DigestReader { + reader, + digest: Some(digest), + oid: None, + } + } +} + +impl<'a> Reader for DigestReader<'a> { + fn read(&mut self, buf: &mut [u8]) -> Result { + let count = if let Some(dig) = &mut self.digest { + let count = self.reader.read(buf)?; + dig.update(&buf[..count]); + count + } + else { + 0 + }; + + if count == 0 { + if let Some(digest) = self.digest.take() { + self.oid = Some(ObjectId::new(&digest.finalize())); + } + } + + Ok(count) + } + + fn finalize(self: Box) -> Result { + self.oid.ok_or_else(|| Error::error("Reader not finalized")) + } +} + + +pub struct DigestWriter<'a> { + writer: Box, + digest: Box, +} + +impl<'a> DigestWriter<'a> { + pub fn new(writer: Box, digest: Box) -> Self { + return DigestWriter { + writer, + digest, + } + } +} + +impl<'a> Writer for DigestWriter<'a> { + fn write(&mut self, buf: &[u8]) -> Result<()> { + self.digest.update(buf); + self.writer.write(buf)?; + Ok(()) + } + + fn finalize(self: Box) -> Result { + Ok(ObjectId::new(&self.digest.finalize())) + } +} + + +pub struct DefaultPipeline { + digest: Box, +} + +impl DefaultPipeline { + pub fn new(digest: Box) -> Self { + Self { + digest, + } + } +} + +impl Pipeline for DefaultPipeline { + fn new_reader(&self, reader: Box) -> Box { + Box::new(DigestReader::new(reader, self.digest.box_clone())) + } + + fn new_writer(&self, writer: Box) -> Box { + Box::new(DigestWriter::new(writer, self.digest.box_clone())) + } +} + + +#[cfg(test)] +mod tests { + extern crate sha2; + + use std::str::FromStr; + + use sha2::Sha256; + + use super::*; + + #[test] + fn std_read_as_reader() { + let data = b"Hello World!".to_vec(); + let mut reader: Box = Box::new(data.as_slice()); + + let data2 = reader.read_all().expect("read failed"); + let maybe_oid = reader.finalize(); + + assert_eq!(data2, data); + assert!(maybe_oid.is_err()); + } + + #[test] + fn std_write_as_writer() { + let data = b"Hello World!".to_vec(); + let mut buffer = [0u8; 32]; + + let maybe_oid = { + let mut writer: Box = Box::new(&mut buffer[..]); + writer.write(&data).expect("write failed"); + writer.finalize() + }; + + assert_eq!(buffer[..data.len()], data); + // Check that the rest of the buffer is unchanged + assert!(buffer[data.len()..].iter().all(|&v| v == 0)); + assert!(maybe_oid.is_err()); + } + + #[test] + fn digest_reader() { + let data = b"Hello World!".to_vec(); + let oid = ObjectId::from_str( + "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069" + ).expect("invalid object id"); + + let mut reader = Box::new(DigestReader::new( + Box::new(data.as_slice()), + Box::new(Sha256::default()), + )); + + let data2 = reader.read_all().expect("read failed"); + let oid2 = reader.finalize().expect("finalize failed"); + + assert_eq!(data2, data); + assert_eq!(oid2, oid); + } + + #[test] + fn digest_writer() { + let data = b"Hello World!".to_vec(); + let oid = ObjectId::from_str( + "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069" + ).expect("invalid object id"); + + let mut buffer = [0u8; 32]; + let oid2 = { + let mut writer = Box::new(DigestWriter::new( + Box::new(&mut buffer[..]), + Box::new(Sha256::default()), + )); + writer.write(&data).expect("write failed"); + writer.finalize().expect("finalize failed") + }; + + assert_eq!(buffer[..data.len()], data); + // Check that the rest of the buffer is unchanged + assert!(buffer[data.len()..].iter().all(|&v| v == 0)); + assert_eq!(oid2, oid); + } +} diff --git a/cas-simple/Cargo.toml b/cas-simple/Cargo.toml new file mode 100644 index 0000000..749d252 --- /dev/null +++ b/cas-simple/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "cas-simple" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +digest = { version = "0.9.0", features = ["alloc"] } +sha2 = "0.9.5" +toml = "0.5.8" +cas-core = { path = "../cas-core" } +tempfile = "3.2.0" + +[dev-dependencies] diff --git a/cas-simple/src/lib.rs b/cas-simple/src/lib.rs new file mode 100644 index 0000000..d65b5d0 --- /dev/null +++ b/cas-simple/src/lib.rs @@ -0,0 +1,245 @@ +// This file is part of bsv. +// +// bsv is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// cdb is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for +// more details. +// +// You should have received a copy of the Affero GNU General Public License +// along with cdb. If not, see . + +//! # cas-simple +//! +//! `cas-simple` implements a simple content addressable storage using +//! cas-core. + + +extern crate digest; +extern crate sha2; +extern crate toml; +extern crate cas_core; + + +use std::str::FromStr; +use std::path::{Path, PathBuf}; + +use digest::DynDigest; +use toml::Value; +use cas_core::{Cas, Error, hex, ObjectId, ObjectMetadata, ObjectType, Result}; + + +fn new_digest(id: &str) -> Result> { + match id { + "sha256" => { Ok(Box::new(sha2::Sha256::default())) }, + _ => { Err(Error::error(format!("unknown digest '{}'", id))) } + } +} + + +pub struct SimpleCas { + db_path: PathBuf, + digest: Box, + config: Value, +} + + +impl SimpleCas { + pub fn create(db_path: PathBuf, config: Value) -> Result { + let digest_id = config["cas"]["digest"].as_str() + .ok_or_else(|| Error::error( + "mandatory cas.digest value is invalid or missing from config" + ))?; + let digest = new_digest(digest_id)?; + + if db_path.exists() { + return Err(Error::error(format!( + "failed to create SimpleCas: target directory already exists ({})", + db_path.to_string_lossy(), + ))); + } + + for path in [ + &db_path, + &obj_dir(&db_path), + &ref_dir(&db_path), + &tmp_dir(&db_path), + ] { + std::fs::create_dir(path).or_else(|e| + Err(Error::error(format!( + "failed to create directory ({}): {}", + path.to_string_lossy(), e, + ))) + )?; + } + + write_config(&config, &db_path)?; + + Ok(SimpleCas { + db_path, + digest, + config, + }) + } + + pub fn open(db_path: PathBuf) -> Result { + let config = read_config(&db_path)?; + + let digest_id = config["cas"]["digest"].as_str() + .ok_or_else(|| Error::error( + "mandatory cas.digest value is invalid or missing from config" + ))?; + let digest = new_digest(digest_id)?; + + Ok(SimpleCas { + db_path, + digest, + config, + }) + } + + pub fn save_config(&self) -> Result<()> { + write_config(&self.config, &self.db_path) + } +} + + +impl Cas for SimpleCas { + fn object_id_from_string(&self, hex: &str) -> Result { + if hex.len() == self.digest.output_size() * 2 { + ObjectId::from_str(hex) + } + else { + Err(Error::error(format!( + "invalid object id size: got {}, expected {}", + hex.len(), self.digest.output_size() * 2, + ))) + } + } + + fn object_id_from_partial(&self, _hex: &str) -> Result { + Err(Error::error("Not implemented")) + } + + fn has_object_id(&self, oid: &ObjectId) -> Result { + let opath = obj_path(&self.db_path, oid); + Ok(opath.is_file()) + } + + fn read_object(&self, _oid: &ObjectId) -> Result<(ObjectMetadata, &dyn std::io::Read)> { + Err(Error::error("Not implemented")) + } + + fn write_object(&mut self, _otype: ObjectType, _data: &mut dyn std::io::Read) -> Result { + Err(Error::error("Not implemented")) + } + + fn remove_object(&mut self, _oid: &ObjectId) -> Result<()> { + Err(Error::error("Not implemented")) + } + + fn read_ref(&self, _key: &str) -> Result { + Err(Error::error("Not implemented")) + } + + fn write_ref(&mut self, _key: &str, _value: &ObjectId) -> Result<()> { + Err(Error::error("Not implemented")) + } + + fn remove_ref(&mut self, _key: &str) -> Result<()> { + Err(Error::error("Not implemented")) + } +} + + +fn obj_dir(cas_path: &Path) -> PathBuf { + cas_path.join("obj") +} + +fn obj_path(cas_path: &Path, oid: &ObjectId) -> PathBuf { + let mut path = cas_path.to_path_buf(); + path.push(hex(&oid.id()[0..1])); + path.push(hex(&oid.id()[1..2])); + path.push(hex(&oid.id()[2..])); + path +} + +fn ref_dir(cas_path: &Path) -> PathBuf { + cas_path.join("ref") +} + +fn tmp_dir(cas_path: &Path) -> PathBuf { + cas_path.join("tmp") +} + +fn config_path(cas_path: &Path) -> PathBuf { + cas_path.join("config") +} + +fn read_config(db_path: &Path) -> Result { + use std::io::Read; + + let mut file = std::fs::File::open(config_path(db_path)).or_else(|err| + Err(Error::error(format!("invalid repository: no config file: {}", err))) + )?; + let mut config_str = String::new(); + file.read_to_string(&mut config_str).or_else(|err| + Err(Error::error(format!("cannot read config file: {}", err))) + )?; + let config = toml::from_str(&config_str).or_else(|err| + Err(Error::error(format!("error while reading config file: {}", err))) + )?; + Ok(config) +} + +fn write_config(config: &Value, db_path: &Path) -> Result<()> { + use std::io::Write; + + let config_str = toml::to_string_pretty(config).or_else(|err| + Err(Error::error(format!("failed to serialize config: {}", err))) + )?; + let mut file = tempfile::NamedTempFile::new_in(tmp_dir(db_path)).or_else(|err| + Err(Error::error(format!("cannot create temp config file: {}", err))) + )?; + file.write_all(config_str.as_bytes()).or_else(|err| + Err(Error::error(format!("failed to write to temp config: {}", err))) + )?; + file.persist(config_path(db_path)).or_else(|err| + Err(Error::error(format!("failed to (over)write config: {}", err))) + )?; + Ok(()) +} + + +#[cfg(test)] +mod tests { + extern crate tempfile; + + use super::*; + + #[test] + fn create_simple_cas() { + let dir = tempfile::tempdir() + .expect("failed to create temp test dir"); + let cas_path = { + let mut cas_path = dir.path().to_path_buf(); + cas_path.push(".bsv"); + cas_path + }; + let config = toml::toml!( + [cas] + digest = "sha256" + ); + + let cas = SimpleCas::create(cas_path, config) + .expect("failed to create SimpleCas object"); + let oid = cas.object_id_from_string("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + .expect("failed to create object id"); + + assert!(!cas.has_object_id(&oid).expect("has_object_id failed")); + } +} diff --git a/libbsv/Cargo.toml b/libbsv/Cargo.toml index 6762412..fe6d4bb 100644 --- a/libbsv/Cargo.toml +++ b/libbsv/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" license = "AGPL-3.0-or-later" [dependencies] -error-chain = "0.12.2" +thiserror = "1.0.25" serde = { version = "1.0.106", features = ["derive"] } toml = "0.5.6" uuid = { version = "0.8.1", features = ["serde", "v4"] } diff --git a/libbsv/src/core/error.rs b/libbsv/src/core/error.rs index 3e3d639..7332121 100644 --- a/libbsv/src/core/error.rs +++ b/libbsv/src/core/error.rs @@ -17,50 +17,99 @@ use std::path::PathBuf; -error_chain! { - foreign_links { - Io(::std::io::Error); - } +pub type Result = std::result::Result>; + +#[non_exhaustive] +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("failed to create repository: {message}")] + RepositoryCreationFailed { + message: String, + source: Option>, + }, + + #[error("invalid object id: {message}")] + InvalidObjectId { + message: String, + source: Option>, + }, + + #[error("invalid size (got: {size}, expected: {expected})")] + InvalidSize { + size: usize, + expected: usize, + }, + + #[error("non-empty directory ({dir})")] + NonEmptyDirectory { + dir: PathBuf + }, + + #[error("invalid character(s) ({characters})")] + InvalidObjectIdCharacter { + characters: String, + }, + + #[error("invalid object type ({otype:?})")] + InvalidObjectType { + otype: [u8; 4], + }, + + #[error("invalid object size (expected {expected}, got {size})")] + InvalidObjectSize { + size: u64, + expected: u64, + }, + + #[error("unsupported file type")] + UnsupportedFileType, + + #[error("invalid path ({path})")] + InvalidPath { path: PathBuf }, + + #[error("io error{}", format_optional_path(path))] + IoError { + source: std::io::Error, + path: Option, + }, + + #[error("{0}")] + Other(String), +} + + +pub fn repository_creation_failed>(message: M) -> Box { + Box::new(Error::RepositoryCreationFailed { + message: message.into(), + source: None, + }) +} + +pub fn repository_creation_failed_from>(source: Box, message: M) -> Box { + Box::new(Error::RepositoryCreationFailed { + message: message.into(), + source: Some(source), + }) +} + +pub fn invalid_object_id>(message: M) -> Box { + Box::new(Error::InvalidObjectId { + message: message.into(), + source: None, + }) +} + +pub fn invalid_object_id_from>(source: Box, message: M) -> Box { + Box::new(Error::InvalidObjectId { + message: message.into(), + source: Some(source), + }) +} + - errors { - NoRepositorySpecifiedError { - description("No repository specifed") - display("No repository specifed. Use -r or set BSV_REPOSITORY environment variable.") - } - - NonEmptyDirectory(dir: PathBuf) { - description("Non-empty directory") - display("Target directory {:?} must be empty.", dir) - } - - InvalidObjectIdSize { - description("Object id has an invalid size") - display("Object id has an invalid size.") - } - - InvalidObjectIdCharacter { - description("Object id contains invalid character") - display("Object id contains invalid character.") - } - - InvalidObjectType(otype: [u8; 4]) { - description("Object has an invalid object type") - display("Object has an invalid object type {:?}.", otype) - } - - MismatchingObjectSize(actual: u64, expected: u64) { - description("Mismatching object size") - display("Mismatching object size: expected {}, got {}.", actual, expected) - } - - UnsupportedFileType { - description("Unsupported file type") - display("Unsupported file type.") - } - - InvalidPath(path: PathBuf) { - description("Invalid path") - display("Invalid path: {:?}.", path) - } +fn format_optional_path(maybe_path: &Option) -> String { + match maybe_path { + Some(path) => { format!(" ({:?})", path) }, + None => { String::new() } } } diff --git a/libbsv/src/core/mod.rs b/libbsv/src/core/mod.rs index e4069ac..06b2689 100644 --- a/libbsv/src/core/mod.rs +++ b/libbsv/src/core/mod.rs @@ -15,18 +15,18 @@ pub mod error; -pub mod config; -pub mod object_id; -pub mod object; -pub mod repository; +// pub mod config; +// pub mod object_id; +// pub mod object; +// pub mod repository; pub const NAME: &str = env!("CARGO_PKG_NAME"); pub const VERSION: &str = env!("CARGO_PKG_VERSION"); -pub use error::{Error, Result, ErrorKind}; -pub use config::{Config, RepositoryConfig}; -pub use object_id::ObjectId; -pub use object::{ObjectType, OTYPE_BLOB, OTYPE_TREE}; -pub use repository::Repository; +pub use error::{Error, Result}; +// pub use config::{Config, RepositoryConfig}; +// pub use object_id::ObjectId; +// pub use object::{ObjectType, OTYPE_BLOB, OTYPE_TREE}; +// pub use repository::Repository; diff --git a/libbsv/src/core/object.rs b/libbsv/src/core/object.rs index 0c637be..4959820 100644 --- a/libbsv/src/core/object.rs +++ b/libbsv/src/core/object.rs @@ -35,7 +35,7 @@ pub fn object_type_from_metadata(md: &Metadata) -> Result { Ok(*OTYPE_TREE) } else { - Err(ErrorKind::UnsupportedFileType.into()) + Err(Error::UnsupportedFileType) } } @@ -137,7 +137,12 @@ impl TreeItem { } pub fn from_file_path_and_id(file_path: &Path, oid: &ObjectId) -> Result { - let md = file_path.symlink_metadata()?; + let md = file_path.symlink_metadata() + .map_err(|err| Error::IoError { + source: err, + path: Some(file_path.to_path_buf()) + })?; + Ok(TreeItem { otype: object_type_from_metadata(&md)?, oid: oid.clone(), @@ -145,7 +150,7 @@ impl TreeItem { permissions: Permissions::from_unix_mode(md.mode()), name: file_path.file_name() .and_then(|n| n.to_str()) - .ok_or_else(|| ErrorKind::InvalidPath(file_path.into()))? + .ok_or_else(|| Error::InvalidPath { path: file_path.into() })? .into(), }) } diff --git a/libbsv/src/core/object_id.rs b/libbsv/src/core/object_id.rs index 78facb5..5ac00d6 100644 --- a/libbsv/src/core/object_id.rs +++ b/libbsv/src/core/object_id.rs @@ -44,7 +44,7 @@ impl FromStr for ObjectId { fn from_str(id_str: &str) -> Result { if id_str.len() % 2 != 0 { - return Err(ErrorKind::InvalidObjectIdSize.into()); + return Err(Error::InvalidObjectIdSize); } let byte_count = id_str.len() / 2; @@ -53,9 +53,9 @@ impl FromStr for ObjectId { for byte_index in 0..byte_count { let str_index = byte_index * 2; let byte_str = id_str.get(str_index..(str_index + 2)) - .ok_or(ErrorKind::InvalidObjectIdCharacter)?; + .ok_or(Error::InvalidObjectIdCharacter)?; id.push(u8::from_str_radix(byte_str, 16) - .or(Err(ErrorKind::InvalidObjectIdCharacter))?); + .or(Err(Error::InvalidObjectIdCharacter))?); } Ok(ObjectId { diff --git a/libbsv/src/core/repository.rs b/libbsv/src/core/repository.rs index ca8f94b..b46f842 100644 --- a/libbsv/src/core/repository.rs +++ b/libbsv/src/core/repository.rs @@ -39,14 +39,14 @@ pub struct Repository { impl Repository { pub fn create(path: &Path, device_name: &str) -> Result { if path.exists() { - bail!(ErrorKind::NonEmptyDirectory(path.into())) + return Err(Error::NonEmptyDirectory { dir: path.to_path_buf() }); } if device_name.is_empty() { - bail!("Device name must not be empty.") + return Err(Error::RepositoryCreationFailed("Device name must not be empty.".to_string())); } - create_dir(&path).chain_err(|| "Failed to create repository.")?; + create_dir(&path).map_err(|err| Error::IoError("Failed to create repository.")?; let config = Rc::new( Config { diff --git a/libbsv/src/lib.rs b/libbsv/src/lib.rs index 87c5e8e..d68827c 100644 --- a/libbsv/src/lib.rs +++ b/libbsv/src/lib.rs @@ -14,20 +14,20 @@ // along with cdb. If not, see . -#[macro_use] -extern crate error_chain; +// #[macro_use] +extern crate thiserror; extern crate serde; pub mod core; -pub mod simple_db; +// pub mod simple_db; -pub use crate::core::{ - Error, Result, ErrorKind, - Config, RepositoryConfig, - ObjectId, ObjectType, - Repository, +// pub use crate::core::{ +// Error, Result, +// Config, RepositoryConfig, +// ObjectId, ObjectType, +// Repository, - OTYPE_BLOB, OTYPE_TREE, -}; +// OTYPE_BLOB, OTYPE_TREE, +// };