Skip to content

Commit 4a0ea51

Browse files
committed
feat: improve type errors
1 parent 2415aa9 commit 4a0ea51

File tree

8 files changed

+155
-44
lines changed

8 files changed

+155
-44
lines changed

rust/catalyst-types/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ workspace = true
1616
name = "catalyst_types"
1717

1818
[dependencies]
19-
anyhow = "1.0.95"
2019
blake2b_simd = "1.0.2"
2120
coset = "0.3.8"
2221
ed25519-dalek = "2.1.1"

rust/catalyst-types/src/conversion.rs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
11
//! Conversion functions
22
3-
use anyhow::bail;
3+
use thiserror::Error;
4+
5+
/// Errors that can occur when converting bytes to an Ed25519 verifying key.
6+
#[derive(Debug, Error)]
7+
pub enum VKeyFromBytesError {
8+
/// Indicates the provided byte slice has an invalid length.
9+
#[error("Invalid byte length: expected {expected} bytes, got {actual}")]
10+
InvalidLength {
11+
/// The expected number of bytes (must be 32).
12+
expected: usize,
13+
/// The actual number of bytes in the provided input.
14+
actual: usize,
15+
},
16+
/// Indicates that the bytes could not be parsed into an Ed25519 public key.
17+
#[error("Failed to parse Ed25519 public key: {source}")]
18+
ParseError {
19+
/// The underlying error from `ed25519_dalek`.
20+
#[from]
21+
source: ed25519_dalek::SignatureError,
22+
},
23+
}
424

525
/// Convert an `<T>` to `<R>` (saturate if out of range).
626
/// Note can convert any int to float, or f32 to f64 as well.
@@ -35,21 +55,20 @@ pub fn from_saturating<
3555
/// # Errors
3656
///
3757
/// Fails if the bytes are not a valid ED25519 Public Key
38-
pub fn vkey_from_bytes(bytes: &[u8]) -> anyhow::Result<ed25519_dalek::VerifyingKey> {
58+
pub fn vkey_from_bytes(bytes: &[u8]) -> Result<ed25519_dalek::VerifyingKey, VKeyFromBytesError> {
3959
if bytes.len() != ed25519_dalek::PUBLIC_KEY_LENGTH {
40-
bail!(
41-
"Failed to convert bytes to ED25519 public key. Expected {} bytes, got {}",
42-
ed25519_dalek::PUBLIC_KEY_LENGTH,
43-
bytes.len()
44-
);
60+
return Err(VKeyFromBytesError::InvalidLength {
61+
expected: ed25519_dalek::PUBLIC_KEY_LENGTH,
62+
actual: bytes.len(),
63+
});
4564
}
4665

4766
let mut ed25519 = [0u8; ed25519_dalek::PUBLIC_KEY_LENGTH];
4867
ed25519.copy_from_slice(bytes); // Can't panic because we already validated its size.
4968

5069
let pubkey = match ed25519_dalek::VerifyingKey::from_bytes(&ed25519) {
5170
Ok(key) => key,
52-
Err(err) => bail!("Failed to convert Ed25519 public key in SimplePublicKeyType {err}"),
71+
Err(err) => return Err(VKeyFromBytesError::ParseError { source: err }),
5372
};
5473
Ok(pubkey)
5574
}

rust/catalyst-types/src/hashes.rs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
33
use std::{fmt, str::FromStr};
44

5-
use anyhow::bail;
65
use blake2b_simd::Params;
76
use pallas_crypto::hash::Hash;
7+
use thiserror::Error;
88

99
/// Number of bytes in a blake2b 224 hash.
1010
pub const BLAKE_2B224_SIZE: usize = 224 / 8;
@@ -24,6 +24,20 @@ pub const BLAKE_2B128_SIZE: usize = 128 / 8;
2424
/// `Blake2B` 128bit Hash
2525
pub type Blake2b128Hash = Blake2bHash<BLAKE_2B128_SIZE>;
2626

27+
/// Errors that can occur when converting to a `Blake2bHash`.
28+
#[derive(Debug, Error)]
29+
pub enum Blake2bHashError {
30+
/// Indicates that the input byte slice has invalid length of bytes to create a valid
31+
/// hash.
32+
#[error("Invalid length: expected {expected} bytes, got {actual}")]
33+
InvalidLength {
34+
/// The expected number of bytes (must be 32 or 28).
35+
expected: usize,
36+
/// The actual number of bytes in the provided input.
37+
actual: usize,
38+
},
39+
}
40+
2741
/// data that is a blake2b [`struct@Hash`] of `BYTES` long.
2842
///
2943
/// Possible values with Cardano are 32 bytes long (block hash or transaction
@@ -73,11 +87,14 @@ impl<const BYTES: usize> From<Blake2bHash<BYTES>> for Vec<u8> {
7387

7488
/// Convert hash in a form of byte array into the `Blake2bHash` type.
7589
impl<const BYTES: usize> TryFrom<&[u8]> for Blake2bHash<BYTES> {
76-
type Error = anyhow::Error;
90+
type Error = Blake2bHashError;
7791

7892
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
7993
if value.len() < BYTES {
80-
bail!("Invalid input length");
94+
return Err(Blake2bHashError::InvalidLength {
95+
expected: BYTES,
96+
actual: value.len(),
97+
});
8198
}
8299

83100
let mut hash = [0; BYTES];
@@ -88,15 +105,15 @@ impl<const BYTES: usize> TryFrom<&[u8]> for Blake2bHash<BYTES> {
88105
}
89106

90107
impl<const BYTES: usize> TryFrom<&Vec<u8>> for Blake2bHash<BYTES> {
91-
type Error = anyhow::Error;
108+
type Error = Blake2bHashError;
92109

93110
fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
94111
value.as_slice().try_into()
95112
}
96113
}
97114

98115
impl<const BYTES: usize> TryFrom<Vec<u8>> for Blake2bHash<BYTES> {
99-
type Error = anyhow::Error;
116+
type Error = Blake2bHashError;
100117

101118
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
102119
value.as_slice().try_into()

rust/catalyst-types/src/kid_uri/authority.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ use std::{
55
str::FromStr,
66
};
77

8+
use thiserror::Error;
9+
10+
/// Errors that can occur when parsing an `Authority` from a string.
11+
#[allow(clippy::module_name_repetitions)]
12+
#[derive(Debug, Error)]
13+
pub enum AuthorityError {
14+
/// The input string does not match any known authority.
15+
#[error("Unknown Authority: {input}")]
16+
UnknownAuthority {
17+
/// The input string.
18+
input: String,
19+
},
20+
}
21+
822
/// URI Authority
923
#[derive(Debug, Clone)]
1024
pub enum Authority {
@@ -15,13 +29,17 @@ pub enum Authority {
1529
}
1630

1731
impl FromStr for Authority {
18-
type Err = anyhow::Error;
32+
type Err = AuthorityError;
1933

2034
fn from_str(s: &str) -> Result<Self, Self::Err> {
2135
match s {
2236
"cardano" => Ok(Authority::Cardano),
2337
"midnight" => Ok(Authority::Midnight),
24-
_ => Err(anyhow::anyhow!("Unknown Authority: {s}")),
38+
_ => {
39+
Err(AuthorityError::UnknownAuthority {
40+
input: s.to_string(),
41+
})
42+
},
2543
}
2644
}
2745
}

rust/catalyst-types/src/kid_uri/role0_pk.rs

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,64 @@ use std::{
55
str::FromStr,
66
};
77

8+
use thiserror::Error;
9+
10+
/// Errors that can occur when parsing a `Role0PublicKey` from a string.
11+
#[derive(Debug, Error)]
12+
pub enum Role0PublicKeyError {
13+
/// The input string does not start with the required "0x" prefix.
14+
#[error("Role0 Public Key hex string must start with '0x': {input}")]
15+
MissingPrefix {
16+
/// The input string.
17+
input: String,
18+
},
19+
/// The input string is not a valid hex-encoded string.
20+
#[error("Role0 Public Key is not a valid hex string: {source}")]
21+
InvalidHex {
22+
/// The underlying error from `hex`.
23+
#[from]
24+
source: hex::FromHexError,
25+
},
26+
/// The decoded key does not have the required length of 32 bytes.
27+
#[error("Role0 Public Key must have 32 bytes: {input}, len: {len}")]
28+
InvalidLength {
29+
/// The input string.
30+
input: String,
31+
/// The actual length of the input.
32+
len: usize
33+
},
34+
/// Unexpected error during key conversion.
35+
#[error("Unable to read Role0 Public Key, this should never happen")]
36+
ConversionError,
37+
}
38+
839
/// Role0 Public Key.
940
#[derive(Debug, Clone)]
1041
pub struct Role0PublicKey([u8; 32]);
1142

1243
impl FromStr for Role0PublicKey {
13-
type Err = anyhow::Error;
44+
type Err = Role0PublicKeyError;
1445

1546
fn from_str(s: &str) -> Result<Self, Self::Err> {
16-
let Some(role0_hex) = s.strip_prefix("0x") else {
17-
anyhow::bail!("Role0 Public Key hex string must start with '0x': {}", s);
18-
};
19-
let role0_key = hex::decode(role0_hex)
20-
.map_err(|e| anyhow::anyhow!("Role0 Public Key is not a valid hex string: {}", e))?;
47+
let role0_hex = s.strip_prefix("0x").ok_or_else(|| {
48+
Role0PublicKeyError::MissingPrefix {
49+
input: s.to_string(),
50+
}
51+
})?;
52+
53+
let role0_key = hex::decode(role0_hex).map_err(Role0PublicKeyError::from)?;
54+
2155
if role0_key.len() != 32 {
22-
anyhow::bail!(
23-
"Role0 Public Key must have 32 bytes: {role0_hex}, len: {}",
24-
role0_key.len()
25-
);
56+
return Err(Role0PublicKeyError::InvalidLength {
57+
input: role0_hex.to_string(),
58+
len: role0_key.len(),
59+
});
2660
}
27-
let role0 = role0_key.try_into().map_err(|e| {
28-
anyhow::anyhow!(
29-
"Unable to read Role0 Public Key, this should never happen. Eror: {e:?}"
30-
)
31-
})?;
61+
62+
let role0: [u8; 32] = role0_key
63+
.try_into()
64+
.map_err(|_| Role0PublicKeyError::ConversionError)?;
65+
3266
Ok(Role0PublicKey(role0))
3367
}
3468
}
Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
//! `UUID` types.
22
3+
use thiserror::Error;
4+
use uuid::Uuid;
5+
36
mod uuid_v4;
47
mod uuid_v7;
58

@@ -9,19 +12,38 @@ pub use uuid_v7::UuidV7 as V7;
912
/// Invalid Doc Type UUID
1013
pub const INVALID_UUID: uuid::Uuid = uuid::Uuid::from_bytes([0x00; 16]);
1114

15+
/// Errors that can occur when decoding CBOR-encoded UUIDs.
16+
#[derive(Debug, Error)]
17+
pub enum CborUuidError {
18+
/// Indicates that the CBOR value is not a valid UUID tag.
19+
#[error("Invalid CBOR encoded UUID type")]
20+
InvalidCborType,
21+
/// Indicates that the byte slice has an invalid size for a UUID.
22+
#[error("Invalid CBOR encoded UUID type: invalid bytes size")]
23+
InvalidByteSize,
24+
/// Indicates that the UUID version does not match the expected version.
25+
#[error("UUID {uuid} is not `v{expected_version}`")]
26+
InvalidVersion {
27+
/// The decoded UUID that was checked.
28+
uuid: Uuid,
29+
/// The expected version of the UUID, which did not match the decoded one.
30+
expected_version: usize,
31+
},
32+
}
33+
1234
/// CBOR tag for UUID content.
1335
pub const UUID_CBOR_TAG: u64 = 37;
1436

1537
/// Decode `CBOR` encoded `UUID`.
16-
pub(crate) fn decode_cbor_uuid(val: &coset::cbor::Value) -> anyhow::Result<uuid::Uuid> {
38+
pub(crate) fn decode_cbor_uuid(val: &coset::cbor::Value) -> Result<uuid::Uuid, CborUuidError> {
1739
let Some((UUID_CBOR_TAG, coset::cbor::Value::Bytes(bytes))) = val.as_tag() else {
18-
anyhow::bail!("Invalid CBOR encoded UUID type");
40+
return Err(CborUuidError::InvalidCborType);
1941
};
2042
let uuid = uuid::Uuid::from_bytes(
2143
bytes
2244
.clone()
2345
.try_into()
24-
.map_err(|_| anyhow::anyhow!("Invalid CBOR encoded UUID type, invalid bytes size"))?,
46+
.map_err(|_| CborUuidError::InvalidByteSize)?,
2547
);
2648
Ok(uuid)
2749
}

rust/catalyst-types/src/uuid/uuid_v4.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,21 @@ impl Display for UuidV4 {
4141
}
4242

4343
impl TryFrom<&coset::cbor::Value> for UuidV4 {
44-
type Error = anyhow::Error;
44+
type Error = super::CborUuidError;
4545

4646
fn try_from(cbor_value: &coset::cbor::Value) -> Result<Self, Self::Error> {
4747
match decode_cbor_uuid(cbor_value) {
4848
Ok(uuid) => {
4949
if uuid.get_version_num() == Self::UUID_VERSION_NUMBER {
5050
Ok(Self { uuid })
5151
} else {
52-
anyhow::bail!("UUID {uuid} is not `v{}`", Self::UUID_VERSION_NUMBER);
52+
Err(super::CborUuidError::InvalidVersion {
53+
uuid,
54+
expected_version: Self::UUID_VERSION_NUMBER,
55+
})
5356
}
5457
},
55-
Err(e) => {
56-
anyhow::bail!("Invalid UUID. Error: {e}");
57-
},
58+
Err(e) => Err(e),
5859
}
5960
}
6061
}

rust/catalyst-types/src/uuid/uuid_v7.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,21 @@ impl Display for UuidV7 {
4141
}
4242

4343
impl TryFrom<&coset::cbor::Value> for UuidV7 {
44-
type Error = anyhow::Error;
44+
type Error = super::CborUuidError;
4545

4646
fn try_from(cbor_value: &coset::cbor::Value) -> Result<Self, Self::Error> {
4747
match decode_cbor_uuid(cbor_value) {
4848
Ok(uuid) => {
4949
if uuid.get_version_num() == Self::UUID_VERSION_NUMBER {
5050
Ok(Self { uuid })
5151
} else {
52-
anyhow::bail!("UUID {uuid} is not `v{}`", Self::UUID_VERSION_NUMBER);
52+
Err(super::CborUuidError::InvalidVersion {
53+
uuid,
54+
expected_version: Self::UUID_VERSION_NUMBER,
55+
})
5356
}
5457
},
55-
Err(e) => {
56-
anyhow::bail!("Invalid UUID. Error: {e}");
57-
},
58+
Err(e) => Err(e),
5859
}
5960
}
6061
}

0 commit comments

Comments
 (0)