Skip to content

Commit 72e1fb5

Browse files
committed
refactor: supports bytes and string
1 parent 47cafd4 commit 72e1fb5

File tree

5 files changed

+38
-43
lines changed

5 files changed

+38
-43
lines changed

src/errors/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ impl ErrorType {
573573
Self::UrlTooLong {..} => "URL should have at most {max_length} characters",
574574
Self::UrlScheme {..} => "URL scheme should be {expected_schemes}",
575575
Self::UuidExactType { .. } => "Input should be an instance of {class_name}",
576-
Self::UuidType => "UUID input should be a string, bytes, integer or UUID object",
576+
Self::UuidType => "UUID input should be a string, bytes or UUID object",
577577
Self::UuidParsing { .. } => "Input should be a valid UUID, {error}",
578578
Self::UuidVersionMismatch { .. } => "UUID version {version} does not match expected version: {schema_version}"
579579

src/validators/uuid.rs

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use pyo3::intern;
22
use pyo3::prelude::*;
33
use pyo3::sync::GILOnceCell;
4+
use pyo3::types::PyBytes;
45
use pyo3::types::{PyDict, PyType};
5-
use uuid::Builder;
66
use uuid::Uuid;
77

88
use crate::build_tools::is_strict;
@@ -32,20 +32,15 @@ fn get_uuid_type(py: Python) -> PyResult<&PyType> {
3232

3333
#[derive(Debug, Clone, Copy)]
3434
enum Version {
35-
UUIDv1,
36-
UUIDv3,
37-
UUIDv4,
38-
UUIDv5,
35+
UUIDv1 = 1,
36+
UUIDv3 = 3,
37+
UUIDv4 = 4,
38+
UUIDv5 = 5,
3939
}
4040

4141
impl From<Version> for usize {
4242
fn from(v: Version) -> Self {
43-
match v {
44-
Version::UUIDv1 => 1,
45-
Version::UUIDv3 => 3,
46-
Version::UUIDv4 => 4,
47-
Version::UUIDv5 => 5,
48-
}
43+
v as usize
4944
}
5045
}
5146

@@ -107,7 +102,7 @@ impl Validator for UuidValidator {
107102
} else {
108103
let dc = create_class(class)?;
109104
let uuid = self.get_uuid(py, input)?;
110-
self.set_dict_call(py, dc.as_ref(py), &uuid)?;
105+
self.create_py_uuid(py, dc.as_ref(py), &uuid)?;
111106
Ok(dc)
112107
}
113108
}
@@ -131,23 +126,20 @@ impl Validator for UuidValidator {
131126

132127
impl UuidValidator {
133128
fn get_uuid<'s, 'data>(&'s self, py: Python<'data>, input: &'data impl Input<'data>) -> ValResult<'data, Uuid> {
134-
let uuid = match input.validate_str(false) {
135-
Ok(either_string) => {
136-
let cow = either_string.as_cow()?;
137-
let uuid_str = cow.as_ref();
138-
match Uuid::parse_str(uuid_str) {
139-
Ok(uuid) => uuid,
140-
Err(e) => return Err(ValError::new(ErrorType::UuidParsing { error: e.to_string() }, input)),
141-
}
142-
}
143-
Err(_) => match input.exact_int() {
144-
Ok(either_int) => {
145-
let int = either_int.into_u128(py)?;
146-
Builder::from_u128(int).into_uuid()
147-
}
148-
Err(_) => return Err(ValError::new(ErrorType::UuidType, input)),
149-
},
129+
let uuid = if let Ok(either_string) = input.exact_str() {
130+
let cow = either_string.as_cow()?;
131+
let uuid_str = cow.as_ref();
132+
Uuid::parse_str(uuid_str)
133+
.map_err(|e| ValError::new(ErrorType::UuidParsing { error: e.to_string() }, input))?
134+
} else if let Ok(either_bytes) = input.validate_bytes(true) {
135+
let py_object = either_bytes.into_py(py);
136+
let py_bytes = py_object.downcast::<PyBytes>(py)?;
137+
Uuid::from_slice(py_bytes.as_bytes())
138+
.map_err(|e| ValError::new(ErrorType::UuidParsing { error: e.to_string() }, input))?
139+
} else {
140+
return Err(ValError::new(ErrorType::UuidType, input));
150141
};
142+
151143
if let Some(expected_version) = self.version {
152144
let v1 = uuid.get_version_num();
153145
let expected_version = usize::from(expected_version);
@@ -171,7 +163,7 @@ impl UuidValidator {
171163
///
172164
/// This implementation does not use the Python `__init__` function to speed up the process,
173165
/// as the `__init__` function in the Python `uuid` module performs extensive checks.
174-
fn set_dict_call<'data>(&self, py: Python<'data>, dc: &PyAny, uuid: &Uuid) -> ValResult<'data, ()> {
166+
fn create_py_uuid<'data>(&self, py: Python<'data>, dc: &PyAny, uuid: &Uuid) -> ValResult<'data, ()> {
175167
let int = uuid.as_u128();
176168
let safe = py
177169
.import(intern!(py, "uuid"))?

tests/serializers/test_uuid.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def test_uuid_key():
3838
@pytest.mark.parametrize(
3939
'value,expected',
4040
[
41-
(UUID('12345678-1234-1234-1234-567812345678'), '12345678-1234-1234-1234-567812345678'),
41+
(UUID('12345678-1234-5678-1234-567812345678'), '12345678-1234-5678-1234-567812345678'),
4242
(UUID('550e8400-e29b-41d4-a716-446655440000'), '550e8400-e29b-41d4-a716-446655440000'),
4343
(UUID('123e4567-e89b-12d3-a456-426655440000'), '123e4567-e89b-12d3-a456-426655440000'),
4444
(UUID('00000000-0000-0000-0000-000000000000'), '00000000-0000-0000-0000-000000000000'),
@@ -52,7 +52,7 @@ def test_uuid_json(value, expected):
5252

5353
def test_any_uuid_key():
5454
v = SchemaSerializer(core_schema.dict_schema())
55-
input_value = {UUID('12345678-1234-1234-1234-567812345678'): 1}
55+
input_value = {UUID('12345678-1234-5678-1234-567812345678'): 1}
5656

57-
assert v.to_python(input_value, mode='json') == {'12345678-1234-1234-1234-567812345678': 1}
58-
assert v.to_json(input_value) == b'{"12345678-1234-1234-1234-567812345678":1}'
57+
assert v.to_python(input_value, mode='json') == {'12345678-1234-5678-1234-567812345678': 1}
58+
assert v.to_json(input_value) == b'{"12345678-1234-5678-1234-567812345678":1}'

tests/test_errors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ def f(input_value, info):
283283
('url_too_long', 'URL should have at most 42 characters', {'max_length': 42}),
284284
('url_scheme', 'URL scheme should be "foo", "bar" or "spam"', {'expected_schemes': '"foo", "bar" or "spam"'}),
285285
('uuid_exact_type', 'Input should be an instance of Foobar', {'class_name': 'Foobar'}),
286-
('uuid_type', 'UUID input should be a string, bytes, integer or UUID object', None),
286+
('uuid_type', 'UUID input should be a string, bytes or UUID object', None),
287287
('uuid_parsing', 'Input should be a valid UUID, Foobar', {'error': 'Foobar'}),
288288
(
289289
'uuid_version_mismatch',

tests/validators/test_uuid.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
('6ba7b810-9dad-11d1-80b4-00c04fd430c8', UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8')),
2424
('886313e1-3b8a-5372-9b90-0c9aee199e5d', UUID('886313e1-3b8a-5372-9b90-0c9aee199e5d')),
2525
('c0a8f9a8-aa5e-482b-a067-9cb3a51f5c11', UUID('c0a8f9a8-aa5e-482b-a067-9cb3a51f5c11')),
26+
(b'\x12\x34\x56\x78' * 4, UUID('12345678-1234-5678-1234-567812345678')),
27+
(b'\x00\x00\x00\x00' * 4, UUID('00000000-0000-0000-0000-000000000000')),
2628
# Invalid UUIDs
2729
(
2830
'not-a-valid-uuid',
@@ -39,6 +41,7 @@
3941
'12345678-1234-1234-1234-1234567890123',
4042
Err('Input should be a valid UUID, invalid group length in group 4: expected 12, found 13'),
4143
),
44+
(b'\x00\x00\x00\x000' * 4, Err('Input should be a valid UUID, invalid length: expected 16 bytes, found 20')),
4245
('550e8400-e29b-41d4-a716', Err('Input should be a valid UUID, invalid group count: expected 5, found 4')),
4346
(
4447
'f47ac10b-58cc-4372-a567-0e02b2c3d47',
@@ -80,9 +83,9 @@ def test_uuid(input_value, expected):
8083
@pytest.mark.parametrize(
8184
'input_value,expected',
8285
[
83-
(UUID('12345678-1234-1234-1234-567812345678'), UUID('12345678-1234-1234-1234-567812345678')),
84-
('12345678-1234-1234-1234-567812345678', Err('Input should be an instance of uuid [type=uuid_exact_type,')),
85-
(b'12345678-1234-1234-1234-567812345678', Err('Input should be an instance of uuid [type=uuid_exact_type,')),
86+
(UUID('12345678-1234-5678-1234-567812345678'), UUID('12345678-1234-5678-1234-567812345678')),
87+
('12345678-1234-5678-1234-567812345678', Err('Input should be an instance of uuid [type=uuid_exact_type,')),
88+
(b'12345678-1234-5678-1234-567812345678', Err('Input should be an instance of uuid [type=uuid_exact_type,')),
8689
(1654646400, Err('Input should be an instance of uuid [type=uuid_exact_type')),
8790
],
8891
)
@@ -125,16 +128,16 @@ def test_uuid_version(input_value, version, expected):
125128
'input_value,expected',
126129
[
127130
('a6cc5730-2261-11ee-9c43-2eb5a363657c', UUID('a6cc5730-2261-11ee-9c43-2eb5a363657c')),
128-
(0xA1A2A3A4B1B2C1C2D1D2D3D4D5D6D7D8, UUID('a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8')),
129-
(0x12345678123456781234567812345678, UUID('12345678-1234-5678-1234-567812345678')),
130131
('12345678123456781234567812345678', UUID('12345678-1234-5678-1234-567812345678')),
131132
(
132133
'c0a8f9a8-aa5e-482b-a067-9cb3a51f5c1',
133134
Err('Input should be a valid UUID, invalid group length in group 4: expected 12, found 11'),
134135
),
135-
(1e1, Err('input should be a string, bytes, integer or UUID object')),
136-
(None, Err('input should be a string, bytes, integer or UUID object')),
137-
(True, Err('input should be a string, bytes, integer or UUID object')),
136+
(1e1, Err('input should be a string, bytes or UUID object')),
137+
(None, Err('input should be a string, bytes or UUID object')),
138+
(True, Err('input should be a string, bytes or UUID object')),
139+
(0xA1A2A3A4B1B2C1C2D1D2D3D4D5D6D7D8, Err('input should be a string, bytes or UUID object')),
140+
(0x12345678123456781234567812345678, Err('input should be a string, bytes or UUID object')),
138141
],
139142
)
140143
def test_uuid_json(py_and_json: PyAndJson, input_value, expected):

0 commit comments

Comments
 (0)