Skip to content

Commit b8bd145

Browse files
committed
add validation during deserializing and instantioation of the UuidV4, UuidV7 types
1 parent 2859739 commit b8bd145

File tree

4 files changed

+103
-49
lines changed

4 files changed

+103
-49
lines changed

rust/catalyst-types/Cargo.toml

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

1818
[dependencies]
19+
anyhow = "1.0.95"
1920
blake2b_simd = "1.0.2"
2021
displaydoc = "0.2.5"
2122
ed25519-dalek = "2.1.1"

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ mod tests {
8282

8383
#[test]
8484
fn test_cbor_uuid_v4_roundtrip() {
85-
let uuid: V4 = uuid::Uuid::new_v4().into();
85+
let uuid = V4::new();
8686
let mut bytes = Vec::new();
8787
minicbor::encode_with(uuid, &mut bytes, &mut CborContext::Untagged).unwrap();
8888
let decoded = minicbor::decode_with(bytes.as_slice(), &mut CborContext::Untagged).unwrap();
@@ -91,7 +91,7 @@ mod tests {
9191

9292
#[test]
9393
fn test_cbor_uuid_v7_roundtrip() {
94-
let uuid: V7 = uuid::Uuid::now_v7().into();
94+
let uuid = V7::new();
9595
let mut bytes = Vec::new();
9696
minicbor::encode_with(uuid, &mut bytes, &mut CborContext::Untagged).unwrap();
9797
let decoded = minicbor::decode_with(bytes.as_slice(), &mut CborContext::Untagged).unwrap();
@@ -100,7 +100,7 @@ mod tests {
100100

101101
#[test]
102102
fn test_tagged_cbor_uuid_v4_roundtrip() {
103-
let uuid: V4 = uuid::Uuid::new_v4().into();
103+
let uuid = V4::new();
104104
let mut bytes = Vec::new();
105105
minicbor::encode_with(uuid, &mut bytes, &mut CborContext::Tagged).unwrap();
106106
let decoded = minicbor::decode_with(bytes.as_slice(), &mut CborContext::Tagged).unwrap();
@@ -109,7 +109,7 @@ mod tests {
109109

110110
#[test]
111111
fn test_tagged_cbor_uuid_v7_roundtrip() {
112-
let uuid: V7 = uuid::Uuid::now_v7().into();
112+
let uuid = V7::new();
113113
let mut bytes = Vec::new();
114114
minicbor::encode_with(uuid, &mut bytes, &mut CborContext::Tagged).unwrap();
115115
let decoded = minicbor::decode_with(bytes.as_slice(), &mut CborContext::Tagged).unwrap();
@@ -118,7 +118,7 @@ mod tests {
118118

119119
#[test]
120120
fn test_optional_cbor_uuid_v4_roundtrip() {
121-
let uuid: V4 = uuid::Uuid::new_v4().into();
121+
let uuid = V4::new();
122122

123123
let mut bytes = Vec::new();
124124
minicbor::encode_with(uuid, &mut bytes, &mut CborContext::Untagged).unwrap();
@@ -133,7 +133,7 @@ mod tests {
133133

134134
#[test]
135135
fn test_optional_cbor_uuid_v7_roundtrip() {
136-
let uuid: V7 = uuid::Uuid::now_v7().into();
136+
let uuid = V7::new();
137137

138138
let mut bytes = Vec::new();
139139
minicbor::encode_with(uuid, &mut bytes, &mut CborContext::Untagged).unwrap();

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

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,47 +6,60 @@ use minicbor::{Decode, Decoder, Encode};
66
use super::{decode_cbor_uuid, encode_cbor_uuid, CborContext, INVALID_UUID};
77

88
/// Type representing a `UUIDv4`.
9-
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)]
10-
#[serde(from = "uuid::Uuid")]
11-
#[serde(into = "uuid::Uuid")]
12-
pub struct UuidV4 {
13-
/// UUID
14-
uuid: uuid::Uuid,
15-
}
9+
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, serde::Serialize)]
10+
pub struct UuidV4(uuid::Uuid);
1611

1712
impl UuidV4 {
1813
/// Version for `UUIDv4`.
1914
const UUID_VERSION_NUMBER: usize = 4;
2015

16+
/// Generates a random `UUIDv4`.
17+
#[must_use]
18+
#[allow(clippy::new_without_default)]
19+
pub fn new() -> Self {
20+
Self(uuid::Uuid::new_v4())
21+
}
22+
2123
/// Generates a zeroed out `UUIDv4` that can never be valid.
2224
#[must_use]
2325
pub fn invalid() -> Self {
24-
Self { uuid: INVALID_UUID }
26+
Self(INVALID_UUID)
2527
}
2628

2729
/// Check if this is a valid `UUIDv4`.
2830
#[must_use]
2931
pub fn is_valid(&self) -> bool {
30-
self.uuid != INVALID_UUID && self.uuid.get_version_num() == Self::UUID_VERSION_NUMBER
32+
is_valid(&self.uuid())
3133
}
3234

3335
/// Returns the `uuid::Uuid` type.
3436
#[must_use]
3537
pub fn uuid(&self) -> uuid::Uuid {
36-
self.uuid
38+
self.0
3739
}
3840
}
3941

42+
/// Check if this is a valid `UUIDv4`.
43+
fn is_valid(uuid: &uuid::Uuid) -> bool {
44+
uuid != &INVALID_UUID && uuid.get_version_num() == UuidV4::UUID_VERSION_NUMBER
45+
}
46+
4047
impl Display for UuidV4 {
4148
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
42-
write!(f, "{}", self.uuid)
49+
write!(f, "{}", self.0)
4350
}
4451
}
4552

4653
impl Decode<'_, CborContext> for UuidV4 {
4754
fn decode(d: &mut Decoder<'_>, ctx: &mut CborContext) -> Result<Self, minicbor::decode::Error> {
4855
let uuid = decode_cbor_uuid(d, ctx)?;
49-
Ok(Self { uuid })
56+
if is_valid(&uuid) {
57+
Ok(Self(uuid))
58+
} else {
59+
Err(minicbor::decode::Error::message(format!(
60+
"'{uuid}' is not a valid UUIDv4"
61+
)))
62+
}
5063
}
5164
}
5265

@@ -59,11 +72,12 @@ impl Encode<CborContext> for UuidV4 {
5972
}
6073

6174
/// Returns a `UUIDv4` from `uuid::Uuid`.
62-
///
63-
/// NOTE: This does not guarantee that the `UUID` is valid.
64-
impl From<uuid::Uuid> for UuidV4 {
65-
fn from(uuid: uuid::Uuid) -> Self {
66-
Self { uuid }
75+
impl TryFrom<uuid::Uuid> for UuidV4 {
76+
type Error = anyhow::Error;
77+
78+
fn try_from(uuid: uuid::Uuid) -> Result<Self, Self::Error> {
79+
anyhow::ensure!(is_valid(&uuid), "'{uuid}' is not a valid UUIDv4");
80+
Ok(Self(uuid))
6781
}
6882
}
6983

@@ -72,7 +86,21 @@ impl From<uuid::Uuid> for UuidV4 {
7286
/// NOTE: This does not guarantee that the `UUID` is valid.
7387
impl From<UuidV4> for uuid::Uuid {
7488
fn from(value: UuidV4) -> Self {
75-
value.uuid
89+
value.0
90+
}
91+
}
92+
93+
impl<'de> serde::Deserialize<'de> for UuidV4 {
94+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
95+
where D: serde::Deserializer<'de> {
96+
let uuid = uuid::Uuid::deserialize(deserializer)?;
97+
if is_valid(&uuid) {
98+
Ok(Self(uuid))
99+
} else {
100+
Err(serde::de::Error::custom(format!(
101+
"'{uuid}' is not a valid UUIDv4"
102+
)))
103+
}
76104
}
77105
}
78106

@@ -95,15 +123,17 @@ mod tests {
95123

96124
#[test]
97125
fn test_valid_uuid() {
98-
let valid_uuid = UuidV4::from(Uuid::new_v4());
126+
let valid_uuid = UuidV4::try_from(Uuid::new_v4()).unwrap();
127+
assert!(valid_uuid.is_valid(), "Valid UUID should be valid");
128+
129+
let valid_uuid = UuidV4::new();
99130
assert!(valid_uuid.is_valid(), "Valid UUID should be valid");
100131
}
101132

102133
#[test]
103134
fn test_invalid_version_uuid() {
104-
let invalid_version_uuid = UuidV4::from(Uuid::from_u128(0));
105135
assert!(
106-
!invalid_version_uuid.is_valid(),
136+
UuidV4::try_from(INVALID_UUID).is_err(),
107137
"Zero UUID should not be valid"
108138
);
109139
}

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

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,47 +6,54 @@ use minicbor::{Decode, Decoder, Encode};
66
use super::{decode_cbor_uuid, encode_cbor_uuid, CborContext, INVALID_UUID};
77

88
/// Type representing a `UUIDv7`.
9-
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)]
10-
#[serde(from = "uuid::Uuid")]
11-
#[serde(into = "uuid::Uuid")]
12-
pub struct UuidV7 {
13-
/// UUID
14-
uuid: uuid::Uuid,
15-
}
9+
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, serde::Serialize)]
10+
pub struct UuidV7(uuid::Uuid);
1611

1712
impl UuidV7 {
1813
/// Version for `UUIDv7`.
1914
const UUID_VERSION_NUMBER: usize = 7;
2015

16+
/// Generates a random `UUIDv4`.
17+
#[must_use]
18+
#[allow(clippy::new_without_default)]
19+
pub fn new() -> Self {
20+
Self(uuid::Uuid::now_v7())
21+
}
22+
2123
/// Generates a zeroed out `UUIDv7` that can never be valid.
2224
#[must_use]
2325
pub fn invalid() -> Self {
24-
Self { uuid: INVALID_UUID }
26+
Self(INVALID_UUID)
2527
}
2628

2729
/// Check if this is a valid `UUIDv7`.
2830
#[must_use]
2931
pub fn is_valid(&self) -> bool {
30-
self.uuid != INVALID_UUID && self.uuid.get_version_num() == Self::UUID_VERSION_NUMBER
32+
is_valid(&self.0)
3133
}
3234

3335
/// Returns the `uuid::Uuid` type.
3436
#[must_use]
3537
pub fn uuid(&self) -> uuid::Uuid {
36-
self.uuid
38+
self.0
3739
}
3840
}
3941

42+
/// Check if this is a valid `UUIDv7`.
43+
fn is_valid(uuid: &uuid::Uuid) -> bool {
44+
uuid != &INVALID_UUID && uuid.get_version_num() == UuidV7::UUID_VERSION_NUMBER
45+
}
46+
4047
impl Display for UuidV7 {
4148
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
42-
write!(f, "{}", self.uuid)
49+
write!(f, "{}", self.0)
4350
}
4451
}
4552

4653
impl Decode<'_, CborContext> for UuidV7 {
4754
fn decode(d: &mut Decoder<'_>, ctx: &mut CborContext) -> Result<Self, minicbor::decode::Error> {
4855
let uuid = decode_cbor_uuid(d, ctx)?;
49-
Ok(Self { uuid })
56+
Ok(Self(uuid))
5057
}
5158
}
5259

@@ -59,11 +66,12 @@ impl Encode<CborContext> for UuidV7 {
5966
}
6067

6168
/// Returns a `UUIDv7` from `uuid::Uuid`.
62-
///
63-
/// NOTE: This does not guarantee that the `UUID` is valid.
64-
impl From<uuid::Uuid> for UuidV7 {
65-
fn from(uuid: uuid::Uuid) -> Self {
66-
Self { uuid }
69+
impl TryFrom<uuid::Uuid> for UuidV7 {
70+
type Error = anyhow::Error;
71+
72+
fn try_from(uuid: uuid::Uuid) -> Result<Self, Self::Error> {
73+
anyhow::ensure!(is_valid(&uuid), "'{uuid}' is not a valid UUIDv7");
74+
Ok(Self(uuid))
6775
}
6876
}
6977

@@ -72,7 +80,21 @@ impl From<uuid::Uuid> for UuidV7 {
7280
/// NOTE: This does not guarantee that the `UUID` is valid.
7381
impl From<UuidV7> for uuid::Uuid {
7482
fn from(value: UuidV7) -> Self {
75-
value.uuid
83+
value.0
84+
}
85+
}
86+
87+
impl<'de> serde::Deserialize<'de> for UuidV7 {
88+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
89+
where D: serde::Deserializer<'de> {
90+
let uuid = uuid::Uuid::deserialize(deserializer)?;
91+
if is_valid(&uuid) {
92+
Ok(Self(uuid))
93+
} else {
94+
Err(serde::de::Error::custom(format!(
95+
"'{uuid}' is not a valid UUIDv4"
96+
)))
97+
}
7698
}
7799
}
78100

@@ -95,16 +117,17 @@ mod tests {
95117

96118
#[test]
97119
fn test_valid_uuid() {
98-
let valid_uuid =
99-
UuidV7::from(Uuid::try_parse("017f22e3-79b0-7cc7-98cf-e0bbf8a1c5f1").unwrap());
120+
let valid_uuid = UuidV7::try_from(Uuid::now_v7()).unwrap();
121+
assert!(valid_uuid.is_valid(), "Valid UUID should be valid");
122+
123+
let valid_uuid = UuidV7::new();
100124
assert!(valid_uuid.is_valid(), "Valid UUID should be valid");
101125
}
102126

103127
#[test]
104128
fn test_invalid_version_uuid() {
105-
let invalid_version_uuid = UuidV7::from(Uuid::from_u128(0));
106129
assert!(
107-
!invalid_version_uuid.is_valid(),
130+
UuidV7::try_from(INVALID_UUID).is_err(),
108131
"Zero UUID should not be valid"
109132
);
110133
}

0 commit comments

Comments
 (0)