wip: get rid of anyhow in base, add wasm feature

feat/type-attributes
Tomáš Mládek 2023-06-27 21:11:10 +02:00
parent e5d645c7ee
commit 9f731d8ca0
7 changed files with 144 additions and 62 deletions

View File

@ -9,12 +9,11 @@ edition = "2018"
[features]
diesel = []
wasm = ["wasm-bindgen", "uuid/js"]
[dependencies]
log = "0.4"
anyhow = "1.0"
lazy_static = "1.4.0"
diesel = { version = "1.4", features = ["sqlite"] }
@ -30,10 +29,12 @@ multihash = { version = "*", default-features = false, features = [
"sha2",
"identity",
] }
uuid = { version = "0.8", features = ["v4", "serde"] }
uuid = { version = "1.4", features = ["v4", "serde"] }
url = { version = "2", features = ["serde"] }
nonempty = "0.6.0"
wasm-bindgen = { version = "0.2", optional = true }
[build-dependencies]
shadow-rs = "0.17"

View File

@ -1,5 +1,5 @@
use crate::hash::{b58_decode, b58_encode, AsDigest, Digest};
use anyhow::{anyhow, Result};
use crate::error::{AddressComponentsDecodeError, UpEndError};
use crate::hash::{b58_decode, b58_encode, AsDigest, AsDigestError, Digest};
use serde::de::Visitor;
use serde::{de, ser, Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
@ -8,6 +8,9 @@ use std::str::FromStr;
use url::Url;
use uuid::Uuid;
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;
#[derive(Clone, Eq, PartialEq, Hash)]
pub enum Address {
Hash(Digest),
@ -16,9 +19,10 @@ pub enum Address {
Url(Url),
}
#[cfg_attr(feature = "wasm", wasm_bindgen(getter_with_clone))]
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct AddressComponents<'a> {
pub t: &'a str,
pub struct AddressComponents {
pub t: String,
pub c: Option<String>,
}
@ -30,31 +34,32 @@ const IDENTITY: u64 = 0x00;
pub type LargeMultihash = multihash::MultihashGeneric<256>;
impl Address {
pub fn encode(&self) -> Result<Vec<u8>> {
pub fn encode(&self) -> Result<Vec<u8>, UpEndError> {
let hash = match self {
Self::Hash(hash) => {
LargeMultihash::wrap(SHA2_256, &hash.0).map_err(|err| anyhow!(err))?
LargeMultihash::wrap(SHA2_256, &hash.0).map_err(UpEndError::from_any)?
}
Self::Uuid(uuid) => {
LargeMultihash::wrap(IDENTITY, &[vec![b'U'], uuid.as_bytes().to_vec()].concat())
.map_err(|err| anyhow!(err))?
.map_err(UpEndError::from_any)?
}
Self::Attribute(attribute) => {
LargeMultihash::wrap(IDENTITY, &[&[b'A'], attribute.as_bytes()].concat())
.map_err(|err| anyhow!(err))?
.map_err(UpEndError::from_any)?
}
Self::Url(url) => {
LargeMultihash::wrap(IDENTITY, &[&[b'X'], url.to_string().as_bytes()].concat())
.map_err(|err| anyhow!(err))?
.map_err(UpEndError::from_any)?
}
};
Ok(hash.to_bytes())
}
pub fn decode(buffer: &[u8]) -> Result<Self> {
let multihash = LargeMultihash::from_bytes(buffer)
.map_err(|err| anyhow!("Error decoding address: {}", err))?;
pub fn decode(buffer: &[u8]) -> Result<Self, UpEndError> {
let multihash = LargeMultihash::from_bytes(buffer).map_err(|err| {
UpEndError::AddressParseError(format!("Error decoding address: {}", err))
})?;
match multihash.code() {
SHA2_256 => Ok(Self::Hash(Digest(multihash.digest().to_vec()))),
@ -62,21 +67,31 @@ impl Address {
let digest = multihash.digest().to_owned();
let digest_content: Vec<u8> = digest.clone().into_iter().skip(1).collect();
match digest[0] {
b'U' => Ok(Self::Uuid(uuid::Uuid::from_slice(
digest_content.as_slice(),
)?)),
b'A' => Ok(Self::Attribute(String::from_utf8(digest_content)?)),
b'X' => Ok(Self::Url(Url::parse(&String::from_utf8(digest_content)?)?)),
_ => Err(anyhow!("Error decoding address: Unknown identity marker.")),
b'U' => Ok(Self::Uuid(
uuid::Uuid::from_slice(digest_content.as_slice())
.map_err(UpEndError::from_any)?,
)),
b'A' => Ok(Self::Attribute(
String::from_utf8(digest_content).map_err(UpEndError::from_any)?,
)),
b'X' => Ok(Self::Url(
Url::parse(
&String::from_utf8(digest_content).map_err(UpEndError::from_any)?,
)
.map_err(|e| UpEndError::AddressParseError(e.to_string()))?,
)),
_ => Err(UpEndError::AddressParseError(
"Error decoding address: Unknown identity marker.".to_string(),
)),
}
}
_ => Err(anyhow!(
"Error decoding address: Unknown hash function type."
_ => Err(UpEndError::AddressParseError(
"Error decoding address: Unknown hash function type.".to_string(),
)),
}
}
pub fn as_components<'a>(&'a self) -> AddressComponents<'a> {
pub fn as_components<'a>(&'a self) -> AddressComponents {
// TODO: make this automatically derive from `Address` definition
let (entity_type, entity_content) = match self {
Address::Hash(digest) => ("Hash", Some(b58_encode(digest))),
@ -86,28 +101,38 @@ impl Address {
};
AddressComponents {
t: entity_type,
t: entity_type.to_string(),
c: entity_content,
}
}
pub fn from_components(components: AddressComponents) -> Result<Self> {
pub fn from_components(components: AddressComponents) -> Result<Self, UpEndError> {
// TODO: make this automatically derive from `Address` definition
let address = match components {
AddressComponents { t: "Attribute", c } => Address::Attribute(
AddressComponents { t, c } if t == "Attribute" => Address::Attribute(
c.map(|x| x.to_string())
.ok_or(anyhow!("Missing attribute."))?,
.ok_or(UpEndError::AddressComponentsDecodeError(
AddressComponentsDecodeError::MissingValue,
))?,
),
AddressComponents { t: "Url", c } => Address::Url(if let Some(string) = c {
Url::parse(&string)?
AddressComponents { t, c } if t == "Url" => Address::Url(if let Some(string) = c {
Url::parse(&string).map_err(|e| {
UpEndError::AddressComponentsDecodeError(
AddressComponentsDecodeError::UrlDecodeError(e.to_string()),
)
})?
} else {
Err(anyhow!("Missing URL."))?
Err(UpEndError::AddressComponentsDecodeError(
AddressComponentsDecodeError::MissingValue,
))?
}),
AddressComponents { t: "Uuid", c } => match c {
AddressComponents { t, c } if t == "Uuid" => match c {
Some(c) => c.parse()?,
None => Address::Uuid(Uuid::new_v4()),
},
AddressComponents { t, .. } => Err(anyhow!("Unknown attribute: \"{}\"", t))?,
AddressComponents { t, .. } => Err(UpEndError::AddressComponentsDecodeError(
AddressComponentsDecodeError::UnknownType(t),
))?,
};
Ok(address)
@ -153,12 +178,14 @@ impl<'de> Deserialize<'de> for Address {
}
impl FromStr for Address {
type Err = anyhow::Error;
type Err = UpEndError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Address::decode(
b58_decode(s)
.map_err(|e| anyhow!("Error deserializing address: {}", e))?
.map_err(|e| {
UpEndError::HashDecodeError(format!("Error deserializing address: {}", e))
})?
.as_ref(),
)
}
@ -191,22 +218,23 @@ impl std::fmt::Debug for Address {
}
pub trait Addressable: AsDigest {
fn address(&self) -> Result<Address> {
fn address(&self) -> Result<Address, AsDigestError> {
Ok(Address::Hash(self.as_digest()?))
}
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use url::Url;
use uuid::Uuid;
use crate::addressing::Address;
use crate::hash::Digest;
use super::UpEndError;
#[test]
fn test_hash_codec() -> Result<()> {
fn test_hash_codec() -> Result<(), UpEndError> {
let addr = Address::Hash(Digest(vec![1, 2, 3, 4, 5]));
let encoded = addr.encode()?;
let decoded = Address::decode(&encoded)?;
@ -215,7 +243,7 @@ mod tests {
}
#[test]
fn test_uuid_codec() -> Result<()> {
fn test_uuid_codec() -> Result<(), UpEndError> {
let addr = Address::Uuid(Uuid::new_v4());
let encoded = addr.encode()?;
let decoded = Address::decode(&encoded)?;
@ -224,7 +252,7 @@ mod tests {
}
#[test]
fn test_attribute_codec() -> Result<()> {
fn test_attribute_codec() -> Result<(), UpEndError> {
let addr = Address::Attribute(String::from("ATTRIBUTE"));
let encoded = addr.encode()?;
let decoded = Address::decode(&encoded)?;
@ -233,7 +261,7 @@ mod tests {
}
#[test]
fn test_url_codec() -> Result<()> {
fn test_url_codec() -> Result<(), UpEndError> {
let addr = Address::Url(Url::parse("https://upend.dev/an/url/that/is/particularly/long/because/multihash/used/to/have/a/small/limit").unwrap());
let encoded = addr.encode()?;
let decoded = Address::decode(&encoded)?;

View File

@ -1,6 +1,6 @@
use crate::addressing::{Address, Addressable};
use crate::error::UpEndError;
use crate::hash::{b58_decode, sha256hash, AsDigest, AsDigestError, Digest};
use anyhow::{anyhow, Result};
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
@ -36,7 +36,7 @@ pub enum EntryValue {
}
impl TryFrom<&InvariantEntry> for Entry {
type Error = anyhow::Error;
type Error = UpEndError;
fn try_from(invariant: &InvariantEntry) -> Result<Self, Self::Error> {
Ok(Entry {
@ -50,10 +50,14 @@ impl TryFrom<&InvariantEntry> for Entry {
}
impl InvariantEntry {
pub fn entity(&self) -> Result<Address> {
pub fn entity(&self) -> Result<Address, UpEndError> {
let mut entity = Cursor::new(vec![0u8; 0]);
entity.write_all(self.attribute.as_bytes())?;
entity.write_all(self.value.to_string()?.as_bytes())?;
entity
.write_all(self.attribute.as_bytes())
.map_err(UpEndError::from_any)?;
entity
.write_all(self.value.to_string()?.as_bytes())
.map_err(UpEndError::from_any)?;
Ok(Address::Hash(sha256hash(entity.into_inner())))
}
}
@ -112,13 +116,13 @@ impl Addressable for Entry {}
impl Addressable for InvariantEntry {}
impl EntryValue {
pub fn to_string(&self) -> Result<String> {
pub fn to_string(&self) -> Result<String, UpEndError> {
let (type_char, content) = match self {
EntryValue::String(value) => ('S', value.to_owned()),
EntryValue::Number(n) => ('N', n.to_string()),
EntryValue::Address(address) => ('O', address.to_string()),
EntryValue::Null => ('X', "".to_string()),
EntryValue::Invalid => return Err(anyhow!("Cannot serialize invalid value.")),
EntryValue::Invalid => return Err(UpEndError::CannotSerializeInvalid),
};
Ok(format!("{}{}", type_char, content))
@ -219,33 +223,32 @@ impl From<Address> for EntryValue {
#[cfg(test)]
mod tests {
use super::*;
use anyhow::Result;
#[test]
fn test_value_from_to_string() -> Result<()> {
fn test_value_from_to_string() -> Result<(), UpEndError> {
let entry = EntryValue::String("hello".to_string());
let encoded = entry.to_string()?;
let decoded = encoded.parse::<EntryValue>()?;
let decoded = encoded.parse::<EntryValue>().unwrap();
assert_eq!(entry, decoded);
let entry = EntryValue::Number(1337.93);
let encoded = entry.to_string()?;
let decoded = encoded.parse::<EntryValue>()?;
let decoded = encoded.parse::<EntryValue>().unwrap();
assert_eq!(entry, decoded);
let entry = EntryValue::Address(Address::Url(Url::parse("https://upend.dev").unwrap()));
let encoded = entry.to_string()?;
let decoded = encoded.parse::<EntryValue>()?;
let decoded = encoded.parse::<EntryValue>().unwrap();
assert_eq!(entry, decoded);
let entry = EntryValue::String("".to_string());
let encoded = entry.to_string()?;
let decoded = encoded.parse::<EntryValue>()?;
let decoded = encoded.parse::<EntryValue>().unwrap();
assert_eq!(entry, decoded);
let entry = EntryValue::Null;
let encoded = entry.to_string()?;
let decoded = encoded.parse::<EntryValue>()?;
let decoded = encoded.parse::<EntryValue>().unwrap();
assert_eq!(entry, decoded);
Ok(())

47
base/src/error.rs Normal file
View File

@ -0,0 +1,47 @@
#[derive(Debug, Clone)]
pub enum UpEndError {
HashDecodeError(String),
AddressParseError(String),
AddressComponentsDecodeError(AddressComponentsDecodeError),
CannotSerializeInvalid,
Other(String),
}
#[derive(Debug, Clone)]
pub enum AddressComponentsDecodeError {
UnknownType(String),
UrlDecodeError(String),
MissingValue,
}
impl std::fmt::Display for UpEndError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
UpEndError::HashDecodeError(err) => format!("Could not decode hash: {err}"),
UpEndError::AddressParseError(err) => format!("Error parsing address: {err}"),
UpEndError::AddressComponentsDecodeError(cde) => match cde {
AddressComponentsDecodeError::UnknownType(t) =>
format!("Unknown type: \"{t}\""),
AddressComponentsDecodeError::MissingValue =>
String::from("Address type requires a value."),
AddressComponentsDecodeError::UrlDecodeError(err) =>
format!("Couldn't decode URL: {err}"),
},
UpEndError::CannotSerializeInvalid =>
String::from("Invalid EntryValues cannot be serialized."),
UpEndError::Other(err) => format!("Unknown error: {err}"),
}
)
}
}
impl std::error::Error for UpEndError {}
impl UpEndError {
pub fn from_any<E: std::fmt::Display>(error: E) -> Self {
UpEndError::Other(error.to_string())
}
}

View File

@ -1,5 +1,4 @@
use crate::addressing::Address;
use anyhow::Result;
use crate::{addressing::Address, error::UpEndError};
use multihash::Hasher;
use serde::{ser, Serialize, Serializer};
@ -49,9 +48,10 @@ pub fn b58_encode<T: AsRef<[u8]>>(vec: T) -> String {
multibase::encode(multibase::Base::Base58Btc, vec.as_ref())
}
pub fn b58_decode<T: AsRef<str>>(input: T) -> Result<Vec<u8>> {
pub fn b58_decode<T: AsRef<str>>(input: T) -> Result<Vec<u8>, UpEndError> {
let input = input.as_ref();
let (_base, data) = multibase::decode(input)?;
let (_base, data) =
multibase::decode(input).map_err(|err| UpEndError::HashDecodeError(err.to_string()))?;
Ok(data)
}

View File

@ -1,5 +1,6 @@
use crate::addressing::Address;
use crate::entry::EntryValue;
use crate::error::UpEndError;
use nonempty::NonEmpty;
use std::borrow::Borrow;
use std::convert::TryFrom;
@ -41,7 +42,7 @@ impl TryFrom<lexpr::Value> for Address {
if let Some(address_str) = str.strip_prefix('@') {
address_str
.parse()
.map_err(|e: anyhow::Error| QueryParseError(e.to_string()))
.map_err(|e: UpEndError| QueryParseError(e.to_string()))
} else {
Err(QueryParseError(
"Incorrect address format (use @address).".into(),
@ -307,12 +308,13 @@ impl FromStr for Query {
#[cfg(test)]
mod test {
use crate::error::UpEndError;
use super::*;
use anyhow::Result;
use url::Url;
#[test]
fn test_matches() -> Result<()> {
fn test_matches() -> Result<(), UpEndError> {
let query = "(matches ? ? ?)".parse::<Query>()?;
assert_eq!(
query,

View File

@ -5,4 +5,5 @@ pub mod addressing;
pub mod constants;
pub mod entry;
pub mod hash;
pub mod error;
pub mod lang;