Skip to content

Commit b9d020f

Browse files
authored
fix(rust/catalyst-types): Add validation during deserialising and instantiation of the UuidV4, UuidV7 (#170)
* add validation during deserializing and instantioation of the UuidV4, UuidV7 types * remove anyhow usage * add tests * fix
1 parent 41c1446 commit b9d020f

File tree

3 files changed

+148
-55
lines changed

3 files changed

+148
-55
lines changed

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

+39-6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,19 @@ pub const INVALID_UUID: uuid::Uuid = uuid::Uuid::from_bytes([0x00; 16]);
1414
#[allow(dead_code)]
1515
const UUID_CBOR_TAG: u64 = 37;
1616

17+
/// Uuid validation errors, which could occur during decoding or converting to
18+
/// `UuidV4` or `UuidV7` types.
19+
#[derive(Debug, Clone, thiserror::Error)]
20+
#[allow(clippy::module_name_repetitions)]
21+
pub enum UuidError {
22+
/// `UUIDv4` invalid error
23+
#[error("'{0}' is not a valid UUIDv4")]
24+
InvalidUuidV4(uuid::Uuid),
25+
/// `UUIDv7` invalid error
26+
#[error("'{0}' is not a valid UUIDv7")]
27+
InvalidUuidV7(uuid::Uuid),
28+
}
29+
1730
/// Context for `CBOR` encoding and decoding
1831
pub enum CborContext {
1932
/// Untagged bytes
@@ -82,25 +95,45 @@ mod tests {
8295

8396
#[test]
8497
fn test_cbor_uuid_v4_roundtrip() {
85-
let uuid: V4 = uuid::Uuid::new_v4().into();
98+
let uuid = V4::new();
8699
let mut bytes = Vec::new();
87100
minicbor::encode_with(uuid, &mut bytes, &mut CborContext::Untagged).unwrap();
88101
let decoded = minicbor::decode_with(bytes.as_slice(), &mut CborContext::Untagged).unwrap();
89102
assert_eq!(uuid, decoded);
90103
}
91104

105+
#[test]
106+
fn test_cbor_uuid_v4_invalid_decoding() {
107+
let uuid_v7 = V7::new();
108+
let mut bytes = Vec::new();
109+
minicbor::encode_with(uuid_v7, &mut bytes, &mut CborContext::Untagged).unwrap();
110+
assert!(
111+
minicbor::decode_with::<_, V4>(bytes.as_slice(), &mut CborContext::Untagged).is_err()
112+
);
113+
}
114+
92115
#[test]
93116
fn test_cbor_uuid_v7_roundtrip() {
94-
let uuid: V7 = uuid::Uuid::now_v7().into();
117+
let uuid = V7::new();
95118
let mut bytes = Vec::new();
96119
minicbor::encode_with(uuid, &mut bytes, &mut CborContext::Untagged).unwrap();
97120
let decoded = minicbor::decode_with(bytes.as_slice(), &mut CborContext::Untagged).unwrap();
98121
assert_eq!(uuid, decoded);
99122
}
100123

124+
#[test]
125+
fn test_cbor_uuid_v7_invalid_decoding() {
126+
let uuid_v4 = V4::new();
127+
let mut bytes = Vec::new();
128+
minicbor::encode_with(uuid_v4, &mut bytes, &mut CborContext::Untagged).unwrap();
129+
assert!(
130+
minicbor::decode_with::<_, V7>(bytes.as_slice(), &mut CborContext::Untagged).is_err()
131+
);
132+
}
133+
101134
#[test]
102135
fn test_tagged_cbor_uuid_v4_roundtrip() {
103-
let uuid: V4 = uuid::Uuid::new_v4().into();
136+
let uuid = V4::new();
104137
let mut bytes = Vec::new();
105138
minicbor::encode_with(uuid, &mut bytes, &mut CborContext::Tagged).unwrap();
106139
let decoded = minicbor::decode_with(bytes.as_slice(), &mut CborContext::Tagged).unwrap();
@@ -109,7 +142,7 @@ mod tests {
109142

110143
#[test]
111144
fn test_tagged_cbor_uuid_v7_roundtrip() {
112-
let uuid: V7 = uuid::Uuid::now_v7().into();
145+
let uuid = V7::new();
113146
let mut bytes = Vec::new();
114147
minicbor::encode_with(uuid, &mut bytes, &mut CborContext::Tagged).unwrap();
115148
let decoded = minicbor::decode_with(bytes.as_slice(), &mut CborContext::Tagged).unwrap();
@@ -118,7 +151,7 @@ mod tests {
118151

119152
#[test]
120153
fn test_optional_cbor_uuid_v4_roundtrip() {
121-
let uuid: V4 = uuid::Uuid::new_v4().into();
154+
let uuid = V4::new();
122155

123156
let mut bytes = Vec::new();
124157
minicbor::encode_with(uuid, &mut bytes, &mut CborContext::Untagged).unwrap();
@@ -133,7 +166,7 @@ mod tests {
133166

134167
#[test]
135168
fn test_optional_cbor_uuid_v7_roundtrip() {
136-
let uuid: V7 = uuid::Uuid::now_v7().into();
169+
let uuid = V7::new();
137170

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

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

+56-26
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,65 @@
22
use std::fmt::{Display, Formatter};
33

44
use minicbor::{Decode, Decoder, Encode};
5+
use uuid::Uuid;
56

6-
use super::{decode_cbor_uuid, encode_cbor_uuid, CborContext, INVALID_UUID};
7+
use super::{decode_cbor_uuid, encode_cbor_uuid, CborContext, UuidError, INVALID_UUID};
78

89
/// 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-
}
10+
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, serde::Serialize)]
11+
pub struct UuidV4(Uuid);
1612

1713
impl UuidV4 {
1814
/// Version for `UUIDv4`.
1915
const UUID_VERSION_NUMBER: usize = 4;
2016

17+
/// Generates a random `UUIDv4`.
18+
#[must_use]
19+
#[allow(clippy::new_without_default)]
20+
pub fn new() -> Self {
21+
Self(Uuid::new_v4())
22+
}
23+
2124
/// Generates a zeroed out `UUIDv4` that can never be valid.
2225
#[must_use]
2326
pub fn invalid() -> Self {
24-
Self { uuid: INVALID_UUID }
27+
Self(INVALID_UUID)
2528
}
2629

2730
/// Check if this is a valid `UUIDv4`.
2831
#[must_use]
2932
pub fn is_valid(&self) -> bool {
30-
self.uuid != INVALID_UUID && self.uuid.get_version_num() == Self::UUID_VERSION_NUMBER
33+
is_valid(&self.uuid())
3134
}
3235

3336
/// Returns the `uuid::Uuid` type.
3437
#[must_use]
35-
pub fn uuid(&self) -> uuid::Uuid {
36-
self.uuid
38+
pub fn uuid(&self) -> Uuid {
39+
self.0
3740
}
3841
}
3942

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

4654
impl Decode<'_, CborContext> for UuidV4 {
4755
fn decode(d: &mut Decoder<'_>, ctx: &mut CborContext) -> Result<Self, minicbor::decode::Error> {
4856
let uuid = decode_cbor_uuid(d, ctx)?;
49-
Ok(Self { uuid })
57+
if is_valid(&uuid) {
58+
Ok(Self(uuid))
59+
} else {
60+
Err(minicbor::decode::Error::message(UuidError::InvalidUuidV4(
61+
uuid,
62+
)))
63+
}
5064
}
5165
}
5266

@@ -59,27 +73,41 @@ impl Encode<CborContext> for UuidV4 {
5973
}
6074

6175
/// 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 }
76+
impl TryFrom<Uuid> for UuidV4 {
77+
type Error = UuidError;
78+
79+
fn try_from(uuid: Uuid) -> Result<Self, Self::Error> {
80+
if is_valid(&uuid) {
81+
Ok(Self(uuid))
82+
} else {
83+
Err(UuidError::InvalidUuidV4(uuid))
84+
}
6785
}
6886
}
6987

7088
/// Returns a `uuid::Uuid` from `UUIDv4`.
7189
///
7290
/// NOTE: This does not guarantee that the `UUID` is valid.
73-
impl From<UuidV4> for uuid::Uuid {
91+
impl From<UuidV4> for Uuid {
7492
fn from(value: UuidV4) -> Self {
75-
value.uuid
93+
value.0
94+
}
95+
}
96+
97+
impl<'de> serde::Deserialize<'de> for UuidV4 {
98+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
99+
where D: serde::Deserializer<'de> {
100+
let uuid = Uuid::deserialize(deserializer)?;
101+
if is_valid(&uuid) {
102+
Ok(Self(uuid))
103+
} else {
104+
Err(serde::de::Error::custom(UuidError::InvalidUuidV4(uuid)))
105+
}
76106
}
77107
}
78108

79109
#[cfg(test)]
80110
mod tests {
81-
use uuid::Uuid;
82-
83111
use super::*;
84112

85113
#[test]
@@ -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

+53-23
Original file line numberDiff line numberDiff line change
@@ -3,50 +3,63 @@ use std::fmt::{Display, Formatter};
33

44
use minicbor::{Decode, Decoder, Encode};
55

6-
use super::{decode_cbor_uuid, encode_cbor_uuid, CborContext, INVALID_UUID};
6+
use super::{decode_cbor_uuid, encode_cbor_uuid, CborContext, UuidError, 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+
if is_valid(&uuid) {
57+
Ok(Self(uuid))
58+
} else {
59+
Err(minicbor::decode::Error::message(UuidError::InvalidUuidV7(
60+
uuid,
61+
)))
62+
}
5063
}
5164
}
5265

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

6174
/// 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 }
75+
impl TryFrom<uuid::Uuid> for UuidV7 {
76+
type Error = UuidError;
77+
78+
fn try_from(uuid: uuid::Uuid) -> Result<Self, Self::Error> {
79+
if is_valid(&uuid) {
80+
Ok(Self(uuid))
81+
} else {
82+
Err(UuidError::InvalidUuidV7(uuid))
83+
}
6784
}
6885
}
6986

@@ -72,7 +89,19 @@ impl From<uuid::Uuid> for UuidV7 {
7289
/// NOTE: This does not guarantee that the `UUID` is valid.
7390
impl From<UuidV7> for uuid::Uuid {
7491
fn from(value: UuidV7) -> Self {
75-
value.uuid
92+
value.0
93+
}
94+
}
95+
96+
impl<'de> serde::Deserialize<'de> for UuidV7 {
97+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
98+
where D: serde::Deserializer<'de> {
99+
let uuid = uuid::Uuid::deserialize(deserializer)?;
100+
if is_valid(&uuid) {
101+
Ok(Self(uuid))
102+
} else {
103+
Err(serde::de::Error::custom(UuidError::InvalidUuidV7(uuid)))
104+
}
76105
}
77106
}
78107

@@ -95,16 +124,17 @@ mod tests {
95124

96125
#[test]
97126
fn test_valid_uuid() {
98-
let valid_uuid =
99-
UuidV7::from(Uuid::try_parse("017f22e3-79b0-7cc7-98cf-e0bbf8a1c5f1").unwrap());
127+
let valid_uuid = UuidV7::try_from(Uuid::now_v7()).unwrap();
128+
assert!(valid_uuid.is_valid(), "Valid UUID should be valid");
129+
130+
let valid_uuid = UuidV7::new();
100131
assert!(valid_uuid.is_valid(), "Valid UUID should be valid");
101132
}
102133

103134
#[test]
104135
fn test_invalid_version_uuid() {
105-
let invalid_version_uuid = UuidV7::from(Uuid::from_u128(0));
106136
assert!(
107-
!invalid_version_uuid.is_valid(),
137+
UuidV7::try_from(INVALID_UUID).is_err(),
108138
"Zero UUID should not be valid"
109139
);
110140
}

0 commit comments

Comments
 (0)