diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ae825be..f9474b2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,19 @@ - Bump MSRV to `1.70`. +### Fixed + +- `uuid` format validation. + ### Deprecated - `cli` feature in favor of a separate `jsonschema-cli` crate. - `draft201909` and `draft202012` features. The relevant functionality is now enabled by default. +### Performance + +- `uuid` validation via `uuid-simd`. + ## [0.18.3] - 2024-09-12 ### Fixed diff --git a/crates/jsonschema-py/CHANGELOG.md b/crates/jsonschema-py/CHANGELOG.md index 20c31c75..00a3d1c2 100644 --- a/crates/jsonschema-py/CHANGELOG.md +++ b/crates/jsonschema-py/CHANGELOG.md @@ -2,9 +2,17 @@ ## [Unreleased] +### Fixed + +- `uuid` format validation. + +### Performance + +- `uuid` validation. + ### Removed -- Support for Python 3.7 +- Support for Python 3.7. ## [0.18.3] - 2024-09-12 diff --git a/crates/jsonschema/Cargo.toml b/crates/jsonschema/Cargo.toml index cc006ea3..014f2060 100644 --- a/crates/jsonschema/Cargo.toml +++ b/crates/jsonschema/Cargo.toml @@ -47,7 +47,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json.workspace = true time = { version = "0.3", features = ["parsing", "macros"] } url = "2.5" -uuid = "1" +uuid-simd = "0.8" [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2", features = ["js"] } diff --git a/crates/jsonschema/src/keywords/format.rs b/crates/jsonschema/src/keywords/format.rs index 27f87af7..99700f5f 100644 --- a/crates/jsonschema/src/keywords/format.rs +++ b/crates/jsonschema/src/keywords/format.rs @@ -5,7 +5,7 @@ use fancy_regex::Regex; use once_cell::sync::Lazy; use serde_json::{Map, Value}; use url::Url; -use uuid::Uuid; +use uuid_simd::{parse_hyphenated, Out}; use crate::{ compilation::context::CompilationContext, @@ -348,7 +348,8 @@ impl Validate for UUIDValidator { validate!("uuid"); fn is_valid(&self, instance: &Value) -> bool { if let Value::String(item) = instance { - Uuid::from_str(item.as_str()).is_ok() + let mut out = [0; 16]; + parse_hyphenated(item.as_bytes(), Out::from_mut(&mut out)).is_ok() } else { true } @@ -450,60 +451,60 @@ pub(crate) fn compile<'a>( func.clone(), )); } - let draft_version = context.config.draft(); + let draft = context.config.draft(); match format.as_str() { "date-time" => Some(DateTimeValidator::compile(context)), "date" => Some(DateValidator::compile(context)), "email" => Some(EmailValidator::compile(context)), "hostname" => Some(HostnameValidator::compile(context)), "idn-email" => Some(IDNEmailValidator::compile(context)), - "idn-hostname" if draft_version == Draft::Draft7 => { + "idn-hostname" if draft == Draft::Draft7 => { Some(IDNHostnameValidator::compile(context)) } - "idn-hostname" if draft_version == Draft::Draft201909 => { + "idn-hostname" if draft == Draft::Draft201909 => { Some(IDNHostnameValidator::compile(context)) } "ipv4" => Some(IpV4Validator::compile(context)), "ipv6" => Some(IpV6Validator::compile(context)), - "iri-reference" if draft_version == Draft::Draft7 => { + "iri-reference" if draft == Draft::Draft7 => { Some(IRIReferenceValidator::compile(context)) } - "iri-reference" if draft_version == Draft::Draft201909 => { + "iri-reference" if draft == Draft::Draft201909 => { Some(IRIReferenceValidator::compile(context)) } - "iri" if draft_version == Draft::Draft7 => Some(IRIValidator::compile(context)), - "iri" if draft_version == Draft::Draft201909 => Some(IRIValidator::compile(context)), - "json-pointer" if draft_version == Draft::Draft6 || draft_version == Draft::Draft7 => { + "iri" if draft == Draft::Draft7 => Some(IRIValidator::compile(context)), + "iri" if draft == Draft::Draft201909 => Some(IRIValidator::compile(context)), + "json-pointer" if draft == Draft::Draft6 || draft == Draft::Draft7 => { Some(JSONPointerValidator::compile(context)) } - "json-pointer" if draft_version == Draft::Draft201909 => { + "json-pointer" if draft == Draft::Draft201909 => { Some(JSONPointerValidator::compile(context)) } "regex" => Some(RegexValidator::compile(context)), - "relative-json-pointer" if draft_version == Draft::Draft7 => { + "relative-json-pointer" if draft == Draft::Draft7 => { Some(RelativeJSONPointerValidator::compile(context)) } - "relative-json-pointer" if draft_version == Draft::Draft201909 => { + "relative-json-pointer" if draft == Draft::Draft201909 => { Some(RelativeJSONPointerValidator::compile(context)) } "time" => Some(TimeValidator::compile(context)), - "uri-reference" if draft_version == Draft::Draft6 || draft_version == Draft::Draft7 => { + "uri-reference" if draft == Draft::Draft6 || draft == Draft::Draft7 => { Some(URIReferenceValidator::compile(context)) } - "uri-reference" if draft_version == Draft::Draft201909 => { + "uri-reference" if draft == Draft::Draft201909 => { Some(URIReferenceValidator::compile(context)) } - "uri-template" if draft_version == Draft::Draft6 || draft_version == Draft::Draft7 => { + "uri-template" if draft == Draft::Draft6 || draft == Draft::Draft7 => { Some(URITemplateValidator::compile(context)) } - "uri-template" if draft_version == Draft::Draft201909 => { + "uri-template" if draft == Draft::Draft201909 => { Some(URITemplateValidator::compile(context)) } - "uuid" if draft_version == Draft::Draft201909 => Some(UUIDValidator::compile(context)), - "uri" => Some(URIValidator::compile(context)), - "duration" if draft_version == Draft::Draft201909 => { - Some(DurationValidator::compile(context)) + "uuid" if matches!(draft, Draft::Draft201909 | Draft::Draft202012) => { + Some(UUIDValidator::compile(context)) } + "uri" => Some(URIValidator::compile(context)), + "duration" if draft == Draft::Draft201909 => Some(DurationValidator::compile(context)), _ => { if context.config.are_unknown_formats_ignored() { None diff --git a/crates/jsonschema/tests/suite.rs b/crates/jsonschema/tests/suite.rs index 678c90dd..7387ed85 100644 --- a/crates/jsonschema/tests/suite.rs +++ b/crates/jsonschema/tests/suite.rs @@ -35,7 +35,6 @@ use testsuite::{suite, Test}; "draft2019-09::optional::format::duration::validation_of_duration_strings", "draft2019-09::optional::format::idn_hostname::validation_of_internationalized_host_names", "draft2019-09::optional::format::time::validation_of_time_strings", - "draft2019-09::optional::format::uuid::uuid_format::no_dashes", "draft2019-09::optional::ref_of_unknown_keyword::reference_internals_of_known_non_applicator", "draft2019-09::r#ref::order_of_evaluation_id_and_anchor_and_ref", "draft2019-09::r#ref::ref_with_recursive_anchor", @@ -72,6 +71,7 @@ use testsuite::{suite, Test}; "draft2020-12::format::time_format", "draft2020-12::format::uri_format", "draft2020-12::format::uri_format", + "draft2020-12::format::uuid_format::invalid_uuid", "draft2020-12::format::validation_of", "draft2020-12::id::invalid_use_of_fragments_in_location_independent_id", "draft2020-12::optional::anchor::anchor_inside_an_enum_is_not_a_real_identifier", @@ -90,11 +90,6 @@ use testsuite::{suite, Test}; "draft2020-12::optional::format::time::validation_of_time_strings", "draft2020-12::optional::format::uri_reference::validation_of_uri_references::an_invalid", "draft2020-12::optional::format::uri_template::format_uri_template::an_invalid_uri", - "draft2020-12::optional::format::uuid::uuid_format", - "draft2020-12::optional::format::uuid::uuid_format::bad_characters_not_hex", - "draft2020-12::optional::format::uuid::uuid_format::missing_section", - "draft2020-12::optional::format::uuid::uuid_format::no_dashes", - "draft2020-12::optional::format::uuid::uuid_format::wrong_length", "draft2020-12::optional::ref_of_unknown_keyword::reference_internals_of_known_non_applicator", "draft2020-12::r#ref::order_of_evaluation_id_and_anchor_and_ref", "draft2020-12::r#ref::urn_base_uri_with_urn_and_anchor_ref",