Skip to content

Commit

Permalink
add test for str | UUID unions in JSON mode
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Aug 21, 2023
1 parent 63cee1d commit 685f784
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 8 deletions.
14 changes: 12 additions & 2 deletions src/input/input_json.rs
Expand Up @@ -83,13 +83,23 @@ impl<'a> Input<'a> for JsonInput {
}
}

fn validate_str(&'a self, _strict: bool) -> ValResult<ValidationMatch<EitherString<'a>>> {
fn exact_str(&'a self) -> ValResult<EitherString<'a>> {
match self {
JsonInput::String(s) => Ok(ValidationMatch::exact(s.as_str().into())),
// Justification for `strict` instead of `exact` is that in JSON strings can also
// represent other datatypes such as UUID and date more exactly, so string is a
// converting input
JsonInput::String(s) => Ok(s.as_str().into()),
_ => Err(ValError::new(ErrorTypeDefaults::StringType, self)),
}
}

fn validate_str(&'a self, _strict: bool) -> ValResult<ValidationMatch<EitherString<'a>>> {
// Justification for `strict` instead of `exact` is that in JSON strings can also
// represent other datatypes such as UUID and date more exactly, so string is a
// converting input
self.exact_str().map(ValidationMatch::strict)
}

fn validate_bytes(&'a self, _strict: bool) -> ValResult<EitherBytes<'a>> {
match self {
JsonInput::String(s) => Ok(s.as_bytes().into()),
Expand Down
10 changes: 5 additions & 5 deletions src/validators/uuid.rs
Expand Up @@ -114,11 +114,11 @@ impl Validator for UuidValidator {
input,
))
} else {
state.set_exactness_ceiling(if input.is_python() {
Exactness::Lax
} else {
Exactness::Strict
});
// In python mode this is a coercion, in JSON mode we treat a UUID string as an
// exact match
if input.is_python() {
state.set_exactness_ceiling(Exactness::Lax);
}
let uuid = self.get_uuid(input)?;
self.create_py_uuid(py, class, &uuid)
}
Expand Down
56 changes: 55 additions & 1 deletion tests/validators/test_union.py
@@ -1,10 +1,13 @@
from datetime import date, datetime, time
from enum import Enum
from typing import Type, TypeVar
from uuid import UUID

import pytest
from dirty_equals import IsFloat, IsInt
from pydantic.type_adapter import TypeAdapter

from pydantic_core import SchemaError, SchemaValidator, ValidationError, core_schema
from pydantic_core import MultiHostUrl, SchemaError, SchemaValidator, Url, ValidationError, core_schema

from ..conftest import plain_repr

Expand Down Expand Up @@ -511,3 +514,54 @@ def remove_prefix(v: str):
'12345678-1234-5678-1234-567812345678'
)
assert validator_called_count == 1


T = TypeVar('T')


@pytest.mark.parametrize(
('ty', 'input_value', 'expected_value'),
(
(UUID, '12345678-1234-5678-1234-567812345678', UUID('12345678-1234-5678-1234-567812345678')),
pytest.param(
date,
'2020-01-01',
date(2020, 1, 1),
marks=pytest.mark.xfail(reason='remove set_exactness_unknown from date validator'),
),
pytest.param(
time,
'00:00:00',
time(0, 0, 0),
marks=pytest.mark.xfail(reason='remove set_exactness_unknown from time validator'),
),
pytest.param(
datetime,
'2020-01-01:00:00:00',
datetime(2020, 1, 1, 0, 0, 0),
marks=pytest.mark.xfail(reason='remove set_exactness_unknown from datetime validator'),
),
pytest.param(
Url,
'https://foo.com',
Url('https://foo.com'),
marks=pytest.mark.xfail(reason='remove set_exactness_unknown from url validator'),
),
pytest.param(
MultiHostUrl,
'https://bar.com,foo.com',
MultiHostUrl('https://bar.com,foo.com'),
marks=pytest.mark.xfail(reason='remove set_exactness_unknown from multihosturl validator'),
),
),
)
def test_smart_union_json_string_types(ty: Type[T], input_value: str, expected_value: T):
# Many types have to be represented in strings as JSON, we make sure that
# when parsing in JSON mode these types are preferred
assert TypeAdapter(ty | str).validate_json(f'"{input_value}"') == expected_value
# in Python mode the string will be preferred
assert TypeAdapter(ty | str).validate_python(input_value) == input_value

# Repeat with reversed order
assert TypeAdapter(str | ty).validate_json(f'"{input_value}"') == expected_value
assert TypeAdapter(str | ty).validate_python(input_value) == input_value

0 comments on commit 685f784

Please sign in to comment.