openapi-spec-validator-rs 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/Cargo.lock +3 -3
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/Cargo.toml +3 -3
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/PKG-INFO +25 -15
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/README.md +24 -14
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/README.md +24 -14
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/document.rs +110 -62
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/formats.rs +1 -1
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/keywords.rs +3 -5
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/lib.rs +14 -2
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/loader/dump.rs +104 -9
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/loader/libyaml.rs +33 -26
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/loader/scalars.rs +8 -4
- openapi_spec_validator_rs-0.2.2/crates/openapi-spec-validator-rs/src/loader/stream.rs +242 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/loader.rs +12 -3
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/meta.rs +167 -35
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/pyjsonschema.rs +15 -15
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/pyobj.rs +52 -4
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/pyrepr.rs +109 -4
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/retrieve.rs +18 -11
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/values.rs +19 -16
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/version.rs +9 -2
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/README.md +24 -14
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/src/lib.rs +17 -12
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-py/src/convert.rs +111 -15
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-py/src/errors.rs +20 -9
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-py/src/lib.rs +32 -17
- openapi_spec_validator_rs-0.2.2/crates/openapi-spec-validator-rs-py/src/stack.rs +62 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/python/openapi_spec_validator_rs/__main__.py +3 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/python/openapi_spec_validator_rs/handlers.py +42 -6
- openapi_spec_validator_rs-0.2.2/python/openapi_spec_validator_rs/readers.py +53 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/python/openapi_spec_validator_rs/shortcuts.py +2 -2
- openapi_spec_validator_rs-0.2.2/python/openapi_spec_validator_rs/validation/__init__.py +37 -0
- openapi_spec_validator_rs-0.2.2/python/openapi_spec_validator_rs/validation/exceptions.py +21 -0
- openapi_spec_validator_rs-0.2.2/python/openapi_spec_validator_rs/validation/proxies.py +23 -0
- openapi_spec_validator_rs-0.2.2/python/openapi_spec_validator_rs/validation/validators.py +19 -0
- openapi_spec_validator_rs-0.2.2/python/openapi_spec_validator_rs/versions/consts.py +11 -0
- openapi_spec_validator_rs-0.2.2/python/openapi_spec_validator_rs/versions/datatypes.py +5 -0
- openapi_spec_validator_rs-0.2.2/python/openapi_spec_validator_rs/versions/exceptions.py +5 -0
- openapi_spec_validator_rs-0.2.2/python/openapi_spec_validator_rs/versions/shortcuts.py +5 -0
- openapi_spec_validator_rs-0.2.0/crates/openapi-spec-validator-rs-py/src/stack.rs +0 -33
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/LICENSE +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/NOTICE +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/Cargo.toml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/LICENSE +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/NOTICE +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/dialects/oas3.1/metaschema.json +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/dialects/oas3.1/vocabularies/base.json +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/dialects/oas3.2/dialect/2025-09-17.json +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/dialects/oas3.2/meta/2025-09-17.json +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/metaschemas/draft-04.json +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/metaschemas/draft-07.json +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/schemas/v2.0/schema.json +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/schemas/v3.0/schema.json +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/schemas/v3.1/schema.json +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/schemas/v3.2/schema.json +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/error.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/loader/bigint.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/loader/compose.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/loader/construct.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/loader/fast_json.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/loader/pyfmt.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/loader/resolve.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/loader/text_input.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/pycompat_tests.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/pyformat.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/pyregex/ast.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/pyregex/parser.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/pyregex/translate.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/pyregex/unicode.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/pyregex.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/pyregex_tests.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/pyurl.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/schema_path.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/Cargo.toml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/LICENSE +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/NOTICE +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/src/main.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/fixtures/bad-regex.yaml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/fixtures/broken.yaml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/fixtures/crash-path-template.yaml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/fixtures/crash-version.yaml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/fixtures/many-errors.yaml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/fixtures/missing-response-description.yaml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/fixtures/oas31-defaults.yaml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/fixtures/oas31-malformed-ref-target.yaml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/fixtures/oas31-nested-schema-error.yaml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/fixtures/split/openapi.yaml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/fixtures/split/schemas/base.yaml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/fixtures/split/schemas/pet.yaml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/fixtures/swagger-parameter.yaml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/fixtures/swagger.yaml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/fixtures/valid.yaml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/output.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__all_validation_errors.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__best_match_hint_in_python_order.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__deprecated_errors_flag_warns.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__exception_from_a_malformed_keyword.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__exception_from_path_template.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__exception_from_unquoted_version.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__explicit_schema_version.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__first_error_before_an_exception.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__first_error_only.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__meta_schema_error_with_all_subschema_errors.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__meta_schema_error_with_best_match.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__meta_schema_path_through_dynamic_ref.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__missing_file.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__oas31_default_values.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__python_regex_rules.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__refs_across_files.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__remote_ref_invalid_default.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__remote_ref_unresolvable.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__remote_ref_valid.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__stops_at_first_failing_file.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__valid_openapi_3.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__valid_swagger_2.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/tests/snapshots/output__yaml_syntax_error.snap +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-py/Cargo.toml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-py/src/retrieve.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/pyproject.toml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/python/openapi_spec_validator_rs/__init__.py +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/python/openapi_spec_validator_rs/_core.pyi +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/python/openapi_spec_validator_rs/exceptions.py +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/python/openapi_spec_validator_rs/proxies.py +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/python/openapi_spec_validator_rs/py.typed +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.2}/python/openapi_spec_validator_rs/validators.py +0 -0
- /openapi_spec_validator_rs-0.2.0/python/openapi_spec_validator_rs/versions.py → /openapi_spec_validator_rs-0.2.2/python/openapi_spec_validator_rs/versions/__init__.py +0 -0
|
@@ -848,7 +848,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
|
|
848
848
|
|
|
849
849
|
[[package]]
|
|
850
850
|
name = "openapi-spec-validator-rs"
|
|
851
|
-
version = "0.2.
|
|
851
|
+
version = "0.2.2"
|
|
852
852
|
dependencies = [
|
|
853
853
|
"base64",
|
|
854
854
|
"fancy-regex",
|
|
@@ -864,7 +864,7 @@ dependencies = [
|
|
|
864
864
|
|
|
865
865
|
[[package]]
|
|
866
866
|
name = "openapi-spec-validator-rs-cli"
|
|
867
|
-
version = "0.2.
|
|
867
|
+
version = "0.2.2"
|
|
868
868
|
dependencies = [
|
|
869
869
|
"clap",
|
|
870
870
|
"insta",
|
|
@@ -875,7 +875,7 @@ dependencies = [
|
|
|
875
875
|
|
|
876
876
|
[[package]]
|
|
877
877
|
name = "openapi-spec-validator-rs-py"
|
|
878
|
-
version = "0.2.
|
|
878
|
+
version = "0.2.2"
|
|
879
879
|
dependencies = [
|
|
880
880
|
"clap",
|
|
881
881
|
"mimalloc",
|
|
@@ -3,7 +3,7 @@ resolver = "3"
|
|
|
3
3
|
members = ["crates/*"]
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
|
-
version = "0.2.
|
|
6
|
+
version = "0.2.2"
|
|
7
7
|
edition = "2024"
|
|
8
8
|
rust-version = "1.85"
|
|
9
9
|
license = "Apache-2.0"
|
|
@@ -14,8 +14,8 @@ keywords = ["openapi", "swagger", "validation", "jsonschema"]
|
|
|
14
14
|
categories = ["command-line-utilities", "development-tools", "web-programming"]
|
|
15
15
|
|
|
16
16
|
[workspace.dependencies]
|
|
17
|
-
openapi-spec-validator-rs = { path = "crates/openapi-spec-validator-rs", version = "0.2.
|
|
18
|
-
openapi-spec-validator-rs-cli = { path = "crates/openapi-spec-validator-rs-cli", version = "0.2.
|
|
17
|
+
openapi-spec-validator-rs = { path = "crates/openapi-spec-validator-rs", version = "0.2.2", default-features = false }
|
|
18
|
+
openapi-spec-validator-rs-cli = { path = "crates/openapi-spec-validator-rs-cli", version = "0.2.2", default-features = false }
|
|
19
19
|
|
|
20
20
|
anyhow = "1.0.104"
|
|
21
21
|
base64 = "0.23.1"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openapi-spec-validator-rs
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: Operating System :: OS Independent
|
|
@@ -28,7 +28,7 @@ Project-URL: Repository, https://github.com/Arjun-Aravind/openapi-spec-validator
|
|
|
28
28
|
**A fast, drop-in Rust companion to [openapi-spec-validator](https://github.com/python-openapi/openapi-spec-validator).**
|
|
29
29
|
|
|
30
30
|
It validates Swagger 2.0 and OpenAPI 3.0, 3.1 and 3.2 documents with the same rules as
|
|
31
|
-
openapi-spec-validator, returns the same verdicts, and runs
|
|
31
|
+
openapi-spec-validator, returns the same verdicts, and runs 37 to 143 times faster on large
|
|
32
32
|
specs. It ships as a command-line tool, a Python package with the same API, and a pre-commit
|
|
33
33
|
hook. It is an independent project, not affiliated with or endorsed by the maintainers of
|
|
34
34
|
openapi-spec-validator.
|
|
@@ -39,10 +39,10 @@ with its default backend):
|
|
|
39
39
|
|
|
40
40
|
| Spec | openapi-spec-validator-rs | openapi-spec-validator | Speed-up |
|
|
41
41
|
|---|---:|---:|---:|
|
|
42
|
-
| GitHub (OpenAPI 3.0, JSON, 13.5 MB) | **57 ms** | 6.
|
|
43
|
-
| GitHub (OpenAPI 3.0, YAML, 10.3 MB) | **
|
|
44
|
-
| Stripe (OpenAPI 3.0, JSON, 8.3 MB) | **31 ms** | 1.
|
|
45
|
-
| Kubernetes (Swagger 2.0, JSON, 4.3 MB) | **20 ms** |
|
|
42
|
+
| GitHub (OpenAPI 3.0, JSON, 13.5 MB) | **57 ms** | 6.45 s – 8.21 s | **112–143×** |
|
|
43
|
+
| GitHub (OpenAPI 3.0, YAML, 10.3 MB) | **150 ms** | 6.46 s – 8.23 s | **43–55×** |
|
|
44
|
+
| Stripe (OpenAPI 3.0, JSON, 8.3 MB) | **31 ms** | 1.81 s – 2.85 s | **59–93×** |
|
|
45
|
+
| Kubernetes (Swagger 2.0, JSON, 4.3 MB) | **20 ms** | 766 ms – 2.14 s | **37–104×** |
|
|
46
46
|
|
|
47
47
|
The `pip`-installed command runs the same validator behind a Python entry point, which adds
|
|
48
48
|
about 25 ms of interpreter start-up per run: 45 ms for Kubernetes and 80 ms for GitHub's
|
|
@@ -113,14 +113,20 @@ The package mirrors openapi-spec-validator's API:
|
|
|
113
113
|
```python
|
|
114
114
|
from openapi_spec_validator_rs import validate, validate_path, validate_url
|
|
115
115
|
from openapi_spec_validator_rs import OpenAPIV31SpecValidator, OpenAPIValidationError
|
|
116
|
+
from openapi_spec_validator_rs.readers import read_from_filename
|
|
116
117
|
|
|
117
118
|
validate(spec_dict) # raises the first error
|
|
118
119
|
validate_path("openapi.yaml") # reads the file like the command line
|
|
119
120
|
validate_url("https://example.com/openapi.json")
|
|
120
121
|
|
|
121
|
-
|
|
122
|
+
spec_dict, base_uri = read_from_filename("openapi.yaml")
|
|
123
|
+
errors = list(OpenAPIV31SpecValidator(spec_dict, base_uri=base_uri).iter_errors())
|
|
122
124
|
```
|
|
123
125
|
|
|
126
|
+
`spec_dict` can be anything `json.load` or `yaml.safe_load` returns, dates and all: a
|
|
127
|
+
`datetime.date` from an unquoted `2020-01-01` is treated as jsonschema treats it (of no
|
|
128
|
+
type, shown as `datetime.date(2020, 1, 1)` in messages).
|
|
129
|
+
|
|
124
130
|
Errors are `OpenAPIValidationError` (or one of its subclasses: `DuplicateOperationIDError`,
|
|
125
131
|
`ParameterDuplicateError`, `UnresolvableParameterError`, `ExtraParametersError`) with the
|
|
126
132
|
same attributes and messages as upstream's. Moving over is usually a matter of changing the
|
|
@@ -165,7 +171,7 @@ repos:
|
|
|
165
171
|
name: openapi-spec-validator-rs
|
|
166
172
|
entry: openapi-spec-validator-rs
|
|
167
173
|
language: python
|
|
168
|
-
additional_dependencies: [openapi-spec-validator-rs==0.2.
|
|
174
|
+
additional_dependencies: [openapi-spec-validator-rs==0.2.2]
|
|
169
175
|
files: .*(openapi|swagger).*\.(json|yaml|yml)$
|
|
170
176
|
```
|
|
171
177
|
|
|
@@ -184,12 +190,13 @@ validates FastAPI's `app.openapi()` does), median of 5 runs:
|
|
|
184
190
|
|
|
185
191
|
| Spec | openapi-spec-validator-rs | openapi-spec-validator, jsonschema-rs backend | openapi-spec-validator, default backend |
|
|
186
192
|
|---|---:|---:|---:|
|
|
187
|
-
| GitHub (13.5 MB) | **
|
|
188
|
-
| Stripe (8.3 MB) | **
|
|
189
|
-
| Kubernetes (4.3 MB) | **11 ms** | 0.33 s (
|
|
193
|
+
| GitHub (13.5 MB) | **30 ms** | 4.66 s (158× slower) | 6.40 s (217× slower) |
|
|
194
|
+
| Stripe (8.3 MB) | **15 ms** | 0.89 s (58× slower) | 1.92 s (126× slower) |
|
|
195
|
+
| Kubernetes (4.3 MB) | **11 ms** | 0.33 s (30× slower) | 1.70 s (154× slower) |
|
|
190
196
|
|
|
191
|
-
|
|
192
|
-
GitHub's (98 MiB through the `pip`-installed command)
|
|
197
|
+
On the same machine it also needs about a quarter of the memory on JSON specs: 84 MiB
|
|
198
|
+
instead of 289 MiB for GitHub's (98 MiB through the `pip`-installed command); the ratio
|
|
199
|
+
varies with the platform's allocator and Python build.
|
|
193
200
|
|
|
194
201
|
Validating every spec in the APIs.guru directory that openapi-spec-validator accepts (3,646
|
|
195
202
|
files, 535 MB of YAML) takes 1.6 s instead of 202–332 s.
|
|
@@ -203,8 +210,10 @@ The validator is a Rust library (`crates/openapi-spec-validator-rs`) with a CLI
|
|
|
203
210
|
PyO3 bindings on top. It follows the reference implementation step by step:
|
|
204
211
|
|
|
205
212
|
1. **Load**: JSON goes through serde_json when libyaml would read it the same way; anything
|
|
206
|
-
else goes through libyaml (the parser inside PyYAML
|
|
207
|
-
|
|
213
|
+
else goes through libyaml (the parser inside PyYAML, as the [unsafe-libyaml] crate's
|
|
214
|
+
mechanical translation of its C code: archived upstream, which is also what keeps it
|
|
215
|
+
identical to the parser PyYAML bundles) with PyYAML's YAML 1.1 rules, then through
|
|
216
|
+
the same JSON round trip. Streams are read only as far as that parser would get.
|
|
208
217
|
2. **Meta-schema**: the whole document is validated against the official schema for its
|
|
209
218
|
version with the [`jsonschema`](https://crates.io/crates/jsonschema) crate.
|
|
210
219
|
3. **Walk**: the document is walked like `keywords.py`, following `$ref`s (into other
|
|
@@ -232,4 +241,5 @@ and [NOTICE][notice].
|
|
|
232
241
|
[compatibility]: https://github.com/Arjun-Aravind/openapi-spec-validator-rs/blob/main/COMPATIBILITY.md
|
|
233
242
|
[license]: https://github.com/Arjun-Aravind/openapi-spec-validator-rs/blob/main/LICENSE
|
|
234
243
|
[notice]: https://github.com/Arjun-Aravind/openapi-spec-validator-rs/blob/main/NOTICE
|
|
244
|
+
[unsafe-libyaml]: https://crates.io/crates/unsafe-libyaml
|
|
235
245
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
**A fast, drop-in Rust companion to [openapi-spec-validator](https://github.com/python-openapi/openapi-spec-validator).**
|
|
4
4
|
|
|
5
5
|
It validates Swagger 2.0 and OpenAPI 3.0, 3.1 and 3.2 documents with the same rules as
|
|
6
|
-
openapi-spec-validator, returns the same verdicts, and runs
|
|
6
|
+
openapi-spec-validator, returns the same verdicts, and runs 37 to 143 times faster on large
|
|
7
7
|
specs. It ships as a command-line tool, a Python package with the same API, and a pre-commit
|
|
8
8
|
hook. It is an independent project, not affiliated with or endorsed by the maintainers of
|
|
9
9
|
openapi-spec-validator.
|
|
@@ -14,10 +14,10 @@ with its default backend):
|
|
|
14
14
|
|
|
15
15
|
| Spec | openapi-spec-validator-rs | openapi-spec-validator | Speed-up |
|
|
16
16
|
|---|---:|---:|---:|
|
|
17
|
-
| GitHub (OpenAPI 3.0, JSON, 13.5 MB) | **57 ms** | 6.
|
|
18
|
-
| GitHub (OpenAPI 3.0, YAML, 10.3 MB) | **
|
|
19
|
-
| Stripe (OpenAPI 3.0, JSON, 8.3 MB) | **31 ms** | 1.
|
|
20
|
-
| Kubernetes (Swagger 2.0, JSON, 4.3 MB) | **20 ms** |
|
|
17
|
+
| GitHub (OpenAPI 3.0, JSON, 13.5 MB) | **57 ms** | 6.45 s – 8.21 s | **112–143×** |
|
|
18
|
+
| GitHub (OpenAPI 3.0, YAML, 10.3 MB) | **150 ms** | 6.46 s – 8.23 s | **43–55×** |
|
|
19
|
+
| Stripe (OpenAPI 3.0, JSON, 8.3 MB) | **31 ms** | 1.81 s – 2.85 s | **59–93×** |
|
|
20
|
+
| Kubernetes (Swagger 2.0, JSON, 4.3 MB) | **20 ms** | 766 ms – 2.14 s | **37–104×** |
|
|
21
21
|
|
|
22
22
|
The `pip`-installed command runs the same validator behind a Python entry point, which adds
|
|
23
23
|
about 25 ms of interpreter start-up per run: 45 ms for Kubernetes and 80 ms for GitHub's
|
|
@@ -88,14 +88,20 @@ The package mirrors openapi-spec-validator's API:
|
|
|
88
88
|
```python
|
|
89
89
|
from openapi_spec_validator_rs import validate, validate_path, validate_url
|
|
90
90
|
from openapi_spec_validator_rs import OpenAPIV31SpecValidator, OpenAPIValidationError
|
|
91
|
+
from openapi_spec_validator_rs.readers import read_from_filename
|
|
91
92
|
|
|
92
93
|
validate(spec_dict) # raises the first error
|
|
93
94
|
validate_path("openapi.yaml") # reads the file like the command line
|
|
94
95
|
validate_url("https://example.com/openapi.json")
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
spec_dict, base_uri = read_from_filename("openapi.yaml")
|
|
98
|
+
errors = list(OpenAPIV31SpecValidator(spec_dict, base_uri=base_uri).iter_errors())
|
|
97
99
|
```
|
|
98
100
|
|
|
101
|
+
`spec_dict` can be anything `json.load` or `yaml.safe_load` returns, dates and all: a
|
|
102
|
+
`datetime.date` from an unquoted `2020-01-01` is treated as jsonschema treats it (of no
|
|
103
|
+
type, shown as `datetime.date(2020, 1, 1)` in messages).
|
|
104
|
+
|
|
99
105
|
Errors are `OpenAPIValidationError` (or one of its subclasses: `DuplicateOperationIDError`,
|
|
100
106
|
`ParameterDuplicateError`, `UnresolvableParameterError`, `ExtraParametersError`) with the
|
|
101
107
|
same attributes and messages as upstream's. Moving over is usually a matter of changing the
|
|
@@ -140,7 +146,7 @@ repos:
|
|
|
140
146
|
name: openapi-spec-validator-rs
|
|
141
147
|
entry: openapi-spec-validator-rs
|
|
142
148
|
language: python
|
|
143
|
-
additional_dependencies: [openapi-spec-validator-rs==0.2.
|
|
149
|
+
additional_dependencies: [openapi-spec-validator-rs==0.2.2]
|
|
144
150
|
files: .*(openapi|swagger).*\.(json|yaml|yml)$
|
|
145
151
|
```
|
|
146
152
|
|
|
@@ -159,12 +165,13 @@ validates FastAPI's `app.openapi()` does), median of 5 runs:
|
|
|
159
165
|
|
|
160
166
|
| Spec | openapi-spec-validator-rs | openapi-spec-validator, jsonschema-rs backend | openapi-spec-validator, default backend |
|
|
161
167
|
|---|---:|---:|---:|
|
|
162
|
-
| GitHub (13.5 MB) | **
|
|
163
|
-
| Stripe (8.3 MB) | **
|
|
164
|
-
| Kubernetes (4.3 MB) | **11 ms** | 0.33 s (
|
|
168
|
+
| GitHub (13.5 MB) | **30 ms** | 4.66 s (158× slower) | 6.40 s (217× slower) |
|
|
169
|
+
| Stripe (8.3 MB) | **15 ms** | 0.89 s (58× slower) | 1.92 s (126× slower) |
|
|
170
|
+
| Kubernetes (4.3 MB) | **11 ms** | 0.33 s (30× slower) | 1.70 s (154× slower) |
|
|
165
171
|
|
|
166
|
-
|
|
167
|
-
GitHub's (98 MiB through the `pip`-installed command)
|
|
172
|
+
On the same machine it also needs about a quarter of the memory on JSON specs: 84 MiB
|
|
173
|
+
instead of 289 MiB for GitHub's (98 MiB through the `pip`-installed command); the ratio
|
|
174
|
+
varies with the platform's allocator and Python build.
|
|
168
175
|
|
|
169
176
|
Validating every spec in the APIs.guru directory that openapi-spec-validator accepts (3,646
|
|
170
177
|
files, 535 MB of YAML) takes 1.6 s instead of 202–332 s.
|
|
@@ -178,8 +185,10 @@ The validator is a Rust library (`crates/openapi-spec-validator-rs`) with a CLI
|
|
|
178
185
|
PyO3 bindings on top. It follows the reference implementation step by step:
|
|
179
186
|
|
|
180
187
|
1. **Load**: JSON goes through serde_json when libyaml would read it the same way; anything
|
|
181
|
-
else goes through libyaml (the parser inside PyYAML
|
|
182
|
-
|
|
188
|
+
else goes through libyaml (the parser inside PyYAML, as the [unsafe-libyaml] crate's
|
|
189
|
+
mechanical translation of its C code: archived upstream, which is also what keeps it
|
|
190
|
+
identical to the parser PyYAML bundles) with PyYAML's YAML 1.1 rules, then through
|
|
191
|
+
the same JSON round trip. Streams are read only as far as that parser would get.
|
|
183
192
|
2. **Meta-schema**: the whole document is validated against the official schema for its
|
|
184
193
|
version with the [`jsonschema`](https://crates.io/crates/jsonschema) crate.
|
|
185
194
|
3. **Walk**: the document is walked like `keywords.py`, following `$ref`s (into other
|
|
@@ -207,3 +216,4 @@ and [NOTICE][notice].
|
|
|
207
216
|
[compatibility]: https://github.com/Arjun-Aravind/openapi-spec-validator-rs/blob/main/COMPATIBILITY.md
|
|
208
217
|
[license]: https://github.com/Arjun-Aravind/openapi-spec-validator-rs/blob/main/LICENSE
|
|
209
218
|
[notice]: https://github.com/Arjun-Aravind/openapi-spec-validator-rs/blob/main/NOTICE
|
|
219
|
+
[unsafe-libyaml]: https://crates.io/crates/unsafe-libyaml
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
**A fast, drop-in Rust companion to [openapi-spec-validator](https://github.com/python-openapi/openapi-spec-validator).**
|
|
4
4
|
|
|
5
5
|
It validates Swagger 2.0 and OpenAPI 3.0, 3.1 and 3.2 documents with the same rules as
|
|
6
|
-
openapi-spec-validator, returns the same verdicts, and runs
|
|
6
|
+
openapi-spec-validator, returns the same verdicts, and runs 37 to 143 times faster on large
|
|
7
7
|
specs. It ships as a command-line tool, a Python package with the same API, and a pre-commit
|
|
8
8
|
hook. It is an independent project, not affiliated with or endorsed by the maintainers of
|
|
9
9
|
openapi-spec-validator.
|
|
@@ -14,10 +14,10 @@ with its default backend):
|
|
|
14
14
|
|
|
15
15
|
| Spec | openapi-spec-validator-rs | openapi-spec-validator | Speed-up |
|
|
16
16
|
|---|---:|---:|---:|
|
|
17
|
-
| GitHub (OpenAPI 3.0, JSON, 13.5 MB) | **57 ms** | 6.
|
|
18
|
-
| GitHub (OpenAPI 3.0, YAML, 10.3 MB) | **
|
|
19
|
-
| Stripe (OpenAPI 3.0, JSON, 8.3 MB) | **31 ms** | 1.
|
|
20
|
-
| Kubernetes (Swagger 2.0, JSON, 4.3 MB) | **20 ms** |
|
|
17
|
+
| GitHub (OpenAPI 3.0, JSON, 13.5 MB) | **57 ms** | 6.45 s – 8.21 s | **112–143×** |
|
|
18
|
+
| GitHub (OpenAPI 3.0, YAML, 10.3 MB) | **150 ms** | 6.46 s – 8.23 s | **43–55×** |
|
|
19
|
+
| Stripe (OpenAPI 3.0, JSON, 8.3 MB) | **31 ms** | 1.81 s – 2.85 s | **59–93×** |
|
|
20
|
+
| Kubernetes (Swagger 2.0, JSON, 4.3 MB) | **20 ms** | 766 ms – 2.14 s | **37–104×** |
|
|
21
21
|
|
|
22
22
|
The `pip`-installed command runs the same validator behind a Python entry point, which adds
|
|
23
23
|
about 25 ms of interpreter start-up per run: 45 ms for Kubernetes and 80 ms for GitHub's
|
|
@@ -88,14 +88,20 @@ The package mirrors openapi-spec-validator's API:
|
|
|
88
88
|
```python
|
|
89
89
|
from openapi_spec_validator_rs import validate, validate_path, validate_url
|
|
90
90
|
from openapi_spec_validator_rs import OpenAPIV31SpecValidator, OpenAPIValidationError
|
|
91
|
+
from openapi_spec_validator_rs.readers import read_from_filename
|
|
91
92
|
|
|
92
93
|
validate(spec_dict) # raises the first error
|
|
93
94
|
validate_path("openapi.yaml") # reads the file like the command line
|
|
94
95
|
validate_url("https://example.com/openapi.json")
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
spec_dict, base_uri = read_from_filename("openapi.yaml")
|
|
98
|
+
errors = list(OpenAPIV31SpecValidator(spec_dict, base_uri=base_uri).iter_errors())
|
|
97
99
|
```
|
|
98
100
|
|
|
101
|
+
`spec_dict` can be anything `json.load` or `yaml.safe_load` returns, dates and all: a
|
|
102
|
+
`datetime.date` from an unquoted `2020-01-01` is treated as jsonschema treats it (of no
|
|
103
|
+
type, shown as `datetime.date(2020, 1, 1)` in messages).
|
|
104
|
+
|
|
99
105
|
Errors are `OpenAPIValidationError` (or one of its subclasses: `DuplicateOperationIDError`,
|
|
100
106
|
`ParameterDuplicateError`, `UnresolvableParameterError`, `ExtraParametersError`) with the
|
|
101
107
|
same attributes and messages as upstream's. Moving over is usually a matter of changing the
|
|
@@ -140,7 +146,7 @@ repos:
|
|
|
140
146
|
name: openapi-spec-validator-rs
|
|
141
147
|
entry: openapi-spec-validator-rs
|
|
142
148
|
language: python
|
|
143
|
-
additional_dependencies: [openapi-spec-validator-rs==0.2.
|
|
149
|
+
additional_dependencies: [openapi-spec-validator-rs==0.2.2]
|
|
144
150
|
files: .*(openapi|swagger).*\.(json|yaml|yml)$
|
|
145
151
|
```
|
|
146
152
|
|
|
@@ -159,12 +165,13 @@ validates FastAPI's `app.openapi()` does), median of 5 runs:
|
|
|
159
165
|
|
|
160
166
|
| Spec | openapi-spec-validator-rs | openapi-spec-validator, jsonschema-rs backend | openapi-spec-validator, default backend |
|
|
161
167
|
|---|---:|---:|---:|
|
|
162
|
-
| GitHub (13.5 MB) | **
|
|
163
|
-
| Stripe (8.3 MB) | **
|
|
164
|
-
| Kubernetes (4.3 MB) | **11 ms** | 0.33 s (
|
|
168
|
+
| GitHub (13.5 MB) | **30 ms** | 4.66 s (158× slower) | 6.40 s (217× slower) |
|
|
169
|
+
| Stripe (8.3 MB) | **15 ms** | 0.89 s (58× slower) | 1.92 s (126× slower) |
|
|
170
|
+
| Kubernetes (4.3 MB) | **11 ms** | 0.33 s (30× slower) | 1.70 s (154× slower) |
|
|
165
171
|
|
|
166
|
-
|
|
167
|
-
GitHub's (98 MiB through the `pip`-installed command)
|
|
172
|
+
On the same machine it also needs about a quarter of the memory on JSON specs: 84 MiB
|
|
173
|
+
instead of 289 MiB for GitHub's (98 MiB through the `pip`-installed command); the ratio
|
|
174
|
+
varies with the platform's allocator and Python build.
|
|
168
175
|
|
|
169
176
|
Validating every spec in the APIs.guru directory that openapi-spec-validator accepts (3,646
|
|
170
177
|
files, 535 MB of YAML) takes 1.6 s instead of 202–332 s.
|
|
@@ -178,8 +185,10 @@ The validator is a Rust library (`crates/openapi-spec-validator-rs`) with a CLI
|
|
|
178
185
|
PyO3 bindings on top. It follows the reference implementation step by step:
|
|
179
186
|
|
|
180
187
|
1. **Load**: JSON goes through serde_json when libyaml would read it the same way; anything
|
|
181
|
-
else goes through libyaml (the parser inside PyYAML
|
|
182
|
-
|
|
188
|
+
else goes through libyaml (the parser inside PyYAML, as the [unsafe-libyaml] crate's
|
|
189
|
+
mechanical translation of its C code: archived upstream, which is also what keeps it
|
|
190
|
+
identical to the parser PyYAML bundles) with PyYAML's YAML 1.1 rules, then through
|
|
191
|
+
the same JSON round trip. Streams are read only as far as that parser would get.
|
|
183
192
|
2. **Meta-schema**: the whole document is validated against the official schema for its
|
|
184
193
|
version with the [`jsonschema`](https://crates.io/crates/jsonschema) crate.
|
|
185
194
|
3. **Walk**: the document is walked like `keywords.py`, following `$ref`s (into other
|
|
@@ -207,3 +216,4 @@ and [NOTICE][notice].
|
|
|
207
216
|
[compatibility]: https://github.com/Arjun-Aravind/openapi-spec-validator-rs/blob/main/COMPATIBILITY.md
|
|
208
217
|
[license]: https://github.com/Arjun-Aravind/openapi-spec-validator-rs/blob/main/LICENSE
|
|
209
218
|
[notice]: https://github.com/Arjun-Aravind/openapi-spec-validator-rs/blob/main/NOTICE
|
|
219
|
+
[unsafe-libyaml]: https://crates.io/crates/unsafe-libyaml
|
|
@@ -19,6 +19,7 @@ use serde_json::Value;
|
|
|
19
19
|
use url::Url;
|
|
20
20
|
|
|
21
21
|
use crate::loader;
|
|
22
|
+
use crate::pyobj;
|
|
22
23
|
use crate::pyrepr;
|
|
23
24
|
use crate::pyurl;
|
|
24
25
|
use crate::retrieve::Retrieve;
|
|
@@ -123,6 +124,9 @@ pub(crate) struct Documents<'a> {
|
|
|
123
124
|
externals: Arena<External>,
|
|
124
125
|
/// Document index by URI (without fragment): 0 is the root, `n` is `externals[n - 1]`.
|
|
125
126
|
by_uri: RefCell<HashMap<String, usize>>,
|
|
127
|
+
/// The one-character strings that pointers into strings resolve to: Python indexes
|
|
128
|
+
/// a `str` like a sequence and yields a new `str` each time.
|
|
129
|
+
characters: Arena<Value>,
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
impl<'a> Documents<'a> {
|
|
@@ -138,6 +142,7 @@ impl<'a> Documents<'a> {
|
|
|
138
142
|
retriever,
|
|
139
143
|
externals: Arena::new(),
|
|
140
144
|
by_uri: RefCell::new(by_uri),
|
|
145
|
+
characters: Arena::new(),
|
|
141
146
|
}
|
|
142
147
|
}
|
|
143
148
|
|
|
@@ -223,7 +228,7 @@ impl<'a> Documents<'a> {
|
|
|
223
228
|
let path = url
|
|
224
229
|
.to_file_path()
|
|
225
230
|
.map_err(|()| format!("not a file path: {uri}"))?;
|
|
226
|
-
let bytes =
|
|
231
|
+
let bytes = crate::read_file(&path).map_err(|error| error.to_string())?;
|
|
227
232
|
// Upstream reads files through `urlopen`, which is the stream name
|
|
228
233
|
// PyYAML reports.
|
|
229
234
|
loader::load(&bytes, "<urllib response>").map_err(|error| error.message)
|
|
@@ -296,14 +301,78 @@ impl<'a> Documents<'a> {
|
|
|
296
301
|
),
|
|
297
302
|
));
|
|
298
303
|
}
|
|
299
|
-
let value = pointer(contents, fragment)?;
|
|
304
|
+
let value = self.pointer(contents, fragment)?;
|
|
300
305
|
Ok(Node { value, doc })
|
|
301
306
|
}
|
|
302
307
|
|
|
308
|
+
/// `Resource.pointer` from `referencing`: each segment indexes a mapping by key or a
|
|
309
|
+
/// sequence by position. A Python `str` is a sequence too, so a pointer can reach
|
|
310
|
+
/// into a string and come out with one of its characters.
|
|
311
|
+
fn pointer<'d>(&'d self, document: &'d Value, pointer: &str) -> Result<&'d Value, LookupError> {
|
|
312
|
+
let nowhere = || {
|
|
313
|
+
let mut message = format!(
|
|
314
|
+
"{} does not exist within {}",
|
|
315
|
+
pyrepr::repr_str(pointer),
|
|
316
|
+
pyrepr::repr(document)
|
|
317
|
+
);
|
|
318
|
+
if pointer == "/" {
|
|
319
|
+
message.push_str(
|
|
320
|
+
". The pointer '/' is a valid JSON Pointer but it points to an empty string \
|
|
321
|
+
property ''. If you intended to point to the entire resource, you should use '#'.",
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
LookupError::referencing("PointerToNowhere", message)
|
|
325
|
+
};
|
|
326
|
+
let invalid_index = |segment: &str| {
|
|
327
|
+
LookupError::from(Crash(format!(
|
|
328
|
+
"invalid literal for int() with base 10: {}",
|
|
329
|
+
pyrepr::repr_str(segment)
|
|
330
|
+
)))
|
|
331
|
+
};
|
|
332
|
+
// `int(segment)`, then Python's negative indexing.
|
|
333
|
+
let position = |segment: &str, len: usize| -> Result<Option<usize>, LookupError> {
|
|
334
|
+
let index = python_int(segment).ok_or_else(|| invalid_index(segment))?;
|
|
335
|
+
let index = if index < 0 {
|
|
336
|
+
i128::try_from(len).unwrap_or(i128::MAX) + index
|
|
337
|
+
} else {
|
|
338
|
+
index
|
|
339
|
+
};
|
|
340
|
+
Ok(usize::try_from(index).ok().filter(|&i| i < len))
|
|
341
|
+
};
|
|
342
|
+
let decoded = percent_decode_str(&pointer[1..]).decode_utf8_lossy();
|
|
343
|
+
let mut contents = document;
|
|
344
|
+
for segment in decoded.split('/') {
|
|
345
|
+
contents = match contents {
|
|
346
|
+
Value::Array(items) => position(segment, items.len())?
|
|
347
|
+
.map(|i| &items[i])
|
|
348
|
+
.ok_or_else(nowhere)?,
|
|
349
|
+
Value::Object(map) if pyrepr::foreign(contents).is_none() => {
|
|
350
|
+
let key = segment.replace("~1", "/").replace("~0", "~");
|
|
351
|
+
map.get(&key).ok_or_else(nowhere)?
|
|
352
|
+
}
|
|
353
|
+
Value::String(text) => {
|
|
354
|
+
let character = position(segment, text.chars().count())?
|
|
355
|
+
.and_then(|i| text.chars().nth(i))
|
|
356
|
+
.ok_or_else(nowhere)?;
|
|
357
|
+
let index = self.characters.push(Value::String(character.to_string()));
|
|
358
|
+
self.characters.get(index)
|
|
359
|
+
}
|
|
360
|
+
other => {
|
|
361
|
+
return Err(Crash(format!(
|
|
362
|
+
"'{}' object is not subscriptable",
|
|
363
|
+
python_type_name(other)
|
|
364
|
+
))
|
|
365
|
+
.into());
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
Ok(contents)
|
|
370
|
+
}
|
|
371
|
+
|
|
303
372
|
/// The child `key` of a mapping node, resolved (`path / key`).
|
|
304
373
|
pub fn get<'d>(&'d self, node: Node<'d>, key: &str) -> Result<Option<Node<'d>>, Crash> {
|
|
305
|
-
match node.value {
|
|
306
|
-
|
|
374
|
+
match pyobj::as_dict(node.value) {
|
|
375
|
+
Some(map) => match map.get(key) {
|
|
307
376
|
Some(value) => self
|
|
308
377
|
.resolve(Node {
|
|
309
378
|
value,
|
|
@@ -342,7 +411,7 @@ impl<'a> Documents<'a> {
|
|
|
342
411
|
) -> Result<Vec<(Key<'d>, Node<'d>)>, Crash> {
|
|
343
412
|
let doc = node.doc;
|
|
344
413
|
match node.value {
|
|
345
|
-
Value::Object(map) => Ok(map
|
|
414
|
+
Value::Object(map) if pyrepr::foreign(node.value).is_none() => Ok(map
|
|
346
415
|
.iter()
|
|
347
416
|
.map(|(key, value)| (Key::Str(key), Node { value, doc }))
|
|
348
417
|
.collect()),
|
|
@@ -365,67 +434,12 @@ pub(crate) enum Key<'d> {
|
|
|
365
434
|
|
|
366
435
|
/// `is_ref` from `jsonschema-path`: a mapping whose `$ref` is a string.
|
|
367
436
|
pub(crate) fn as_ref(value: &Value) -> Option<&str> {
|
|
368
|
-
value
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/// `Resource.pointer` from `referencing`.
|
|
372
|
-
fn pointer<'d>(document: &'d Value, pointer: &str) -> Result<&'d Value, LookupError> {
|
|
373
|
-
let nowhere = || {
|
|
374
|
-
let mut message = format!(
|
|
375
|
-
"{} does not exist within {}",
|
|
376
|
-
pyrepr::repr_str(pointer),
|
|
377
|
-
pyrepr::repr(document)
|
|
378
|
-
);
|
|
379
|
-
if pointer == "/" {
|
|
380
|
-
message.push_str(
|
|
381
|
-
". The pointer '/' is a valid JSON Pointer but it points to an empty string \
|
|
382
|
-
property ''. If you intended to point to the entire resource, you should use '#'.",
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
LookupError::referencing("PointerToNowhere", message)
|
|
386
|
-
};
|
|
387
|
-
let decoded = percent_decode_str(&pointer[1..]).decode_utf8_lossy();
|
|
388
|
-
let mut contents = document;
|
|
389
|
-
for segment in decoded.split('/') {
|
|
390
|
-
contents = match contents {
|
|
391
|
-
Value::Array(items) => {
|
|
392
|
-
let index = python_int(segment).ok_or_else(|| {
|
|
393
|
-
Crash(format!(
|
|
394
|
-
"invalid literal for int() with base 10: {}",
|
|
395
|
-
pyrepr::repr_str(segment)
|
|
396
|
-
))
|
|
397
|
-
})?;
|
|
398
|
-
let index = if index < 0 {
|
|
399
|
-
i128::try_from(items.len()).unwrap_or(i128::MAX) + index
|
|
400
|
-
} else {
|
|
401
|
-
index
|
|
402
|
-
};
|
|
403
|
-
usize::try_from(index)
|
|
404
|
-
.ok()
|
|
405
|
-
.and_then(|i| items.get(i))
|
|
406
|
-
.ok_or_else(nowhere)?
|
|
407
|
-
}
|
|
408
|
-
Value::Object(map) => {
|
|
409
|
-
let key = segment.replace("~1", "/").replace("~0", "~");
|
|
410
|
-
map.get(&key).ok_or_else(nowhere)?
|
|
411
|
-
}
|
|
412
|
-
// Strings are sequences in Python, so indexing them yields a new
|
|
413
|
-
// one-character string that has no address in the document.
|
|
414
|
-
Value::String(_) => return Err(nowhere()),
|
|
415
|
-
other => {
|
|
416
|
-
return Err(Crash(format!(
|
|
417
|
-
"'{}' object is not subscriptable",
|
|
418
|
-
python_type_name(other)
|
|
419
|
-
))
|
|
420
|
-
.into());
|
|
421
|
-
}
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
Ok(contents)
|
|
437
|
+
pyobj::as_dict(value)?.get("$ref")?.as_str()
|
|
425
438
|
}
|
|
426
439
|
|
|
427
440
|
/// Python `int(s)` for base-10 literals (ASCII digits, sign, underscores, whitespace).
|
|
428
441
|
fn python_int(s: &str) -> Option<i128> {
|
|
442
|
+
let s = pyobj::decimal_to_ascii(s)?;
|
|
429
443
|
let s = s.trim();
|
|
430
444
|
let (negative, digits) = match s.as_bytes().first()? {
|
|
431
445
|
b'-' => (true, &s[1..]),
|
|
@@ -526,6 +540,35 @@ mod tests {
|
|
|
526
540
|
);
|
|
527
541
|
}
|
|
528
542
|
|
|
543
|
+
#[test]
|
|
544
|
+
fn pointers_index_strings_like_python() {
|
|
545
|
+
let spec = json!({
|
|
546
|
+
"info": {"title": "tx"},
|
|
547
|
+
"a": {"$ref": "#/info/title/0"},
|
|
548
|
+
"b": {"$ref": "#/info/title/-1"},
|
|
549
|
+
"c": {"$ref": "#/info/title/x"},
|
|
550
|
+
"d": {"$ref": "#/info/title/2"},
|
|
551
|
+
"e": {"$ref": "#/info/title/1/0/0"},
|
|
552
|
+
});
|
|
553
|
+
let docs = Documents::new(&spec, "", None);
|
|
554
|
+
assert_eq!(docs.child(docs.root(), "a").unwrap().value, &json!("t"));
|
|
555
|
+
assert_eq!(docs.child(docs.root(), "b").unwrap().value, &json!("x"));
|
|
556
|
+
assert_eq!(docs.child(docs.root(), "e").unwrap().value, &json!("x"));
|
|
557
|
+
assert_eq!(
|
|
558
|
+
docs.child(docs.root(), "c").unwrap_err(),
|
|
559
|
+
Crash("invalid literal for int() with base 10: 'x'".to_owned())
|
|
560
|
+
);
|
|
561
|
+
assert!(
|
|
562
|
+
docs.child(docs.root(), "d")
|
|
563
|
+
.unwrap_err()
|
|
564
|
+
.0
|
|
565
|
+
.starts_with("'/info/title/2' does not exist within")
|
|
566
|
+
);
|
|
567
|
+
// Each resolution yields a new string, as in Python.
|
|
568
|
+
let first = docs.child(docs.root(), "a").unwrap().id();
|
|
569
|
+
assert_ne!(first, docs.child(docs.root(), "a").unwrap().id());
|
|
570
|
+
}
|
|
571
|
+
|
|
529
572
|
#[test]
|
|
530
573
|
fn ref_cycles_hit_the_recursion_limit() {
|
|
531
574
|
let spec = json!({"a": {"$ref": "#/b"}, "b": {"$ref": "#/a"}});
|
|
@@ -553,5 +596,10 @@ mod tests {
|
|
|
553
596
|
assert_eq!(python_int("1_0"), Some(10));
|
|
554
597
|
assert_eq!(python_int("1__0"), None);
|
|
555
598
|
assert_eq!(python_int("x"), None);
|
|
599
|
+
// Python's `int()` reads every script's decimal digits and whitespace.
|
|
600
|
+
assert_eq!(python_int("\u{660}\u{661}"), Some(1));
|
|
601
|
+
assert_eq!(python_int("\u{3000}-\u{ff11}\u{ff12} "), Some(-12));
|
|
602
|
+
assert_eq!(python_int("\u{660}x"), None);
|
|
603
|
+
assert_eq!(python_int("\u{2160}"), None);
|
|
556
604
|
}
|
|
557
605
|
}
|
|
@@ -682,7 +682,7 @@ fn py_int_base16(s: &str) -> Option<bool> {
|
|
|
682
682
|
}
|
|
683
683
|
|
|
684
684
|
/// Value of a Unicode decimal digit (`unicodedata.decimal`), Unicode 15.1.
|
|
685
|
-
fn decimal_value(c: char) -> Option<u32> {
|
|
685
|
+
pub(crate) fn decimal_value(c: char) -> Option<u32> {
|
|
686
686
|
let cp = c as u32;
|
|
687
687
|
let i = DECIMAL_ZEROS.partition_point(|&z| z <= cp);
|
|
688
688
|
let zero = DECIMAL_ZEROS[..i].last()?;
|
|
@@ -347,7 +347,7 @@ impl<'d, 'a> Walker<'d, 'a> {
|
|
|
347
347
|
let location = self.item(parameter, "in")?;
|
|
348
348
|
// jsonschema-path hands out lists and mappings as `SchemaPath`s, which are
|
|
349
349
|
// hashable and equal only to the same path: such a key is never a duplicate.
|
|
350
|
-
let container = |v: &Value| v.is_array() || v.
|
|
350
|
+
let container = |v: &Value| v.is_array() || pyobj::as_dict(v).is_some();
|
|
351
351
|
if container(name) || container(location) {
|
|
352
352
|
continue;
|
|
353
353
|
}
|
|
@@ -451,7 +451,7 @@ impl<'d, 'a> Walker<'d, 'a> {
|
|
|
451
451
|
/// as a nested subschema never gets the `required` check.
|
|
452
452
|
fn schema(&mut self, schema: Node<'d>, require_properties: bool, meta_checked: bool) -> Step {
|
|
453
453
|
let value = schema.value;
|
|
454
|
-
if
|
|
454
|
+
if pyobj::as_dict(value).is_none() && !value.is_boolean() {
|
|
455
455
|
return self.emit_new(
|
|
456
456
|
ErrorKind::OpenApiValidation,
|
|
457
457
|
format!("{} is not of type 'object', 'boolean'", repr(value)),
|
|
@@ -675,9 +675,7 @@ impl<'d, 'a> Walker<'d, 'a> {
|
|
|
675
675
|
|
|
676
676
|
/// `"key" in path` on a resolved node.
|
|
677
677
|
fn contains(node: Node<'_>, key: &str) -> bool {
|
|
678
|
-
node.value
|
|
679
|
-
.as_object()
|
|
680
|
-
.is_some_and(|map| map.contains_key(key))
|
|
678
|
+
pyobj::as_dict(node.value).is_some_and(|map| map.contains_key(key))
|
|
681
679
|
}
|
|
682
680
|
|
|
683
681
|
/// `list(set(required) - set(properties) - set(nested_properties))`.
|
|
@@ -62,7 +62,7 @@ use crate::document::Documents;
|
|
|
62
62
|
use crate::keywords::{Stop, Walker};
|
|
63
63
|
|
|
64
64
|
pub use crate::error::{ErrorKind, Outcome, SchemaErrorDetails, ValidationError, best_match};
|
|
65
|
-
pub use crate::loader::{LoadError, TextErrors, load, load_text};
|
|
65
|
+
pub use crate::loader::{LoadError, TextErrors, load, load_text, read_until_unreadable};
|
|
66
66
|
pub use crate::pyrepr::PathItem;
|
|
67
67
|
#[cfg(feature = "http")]
|
|
68
68
|
pub use crate::retrieve::HttpRetriever;
|
|
@@ -188,6 +188,18 @@ pub fn validate(spec: &Value, options: &Options) -> Outcome {
|
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
/// Reads a document file as PyYAML reads the stream `urlopen` gives upstream: only as far
|
|
192
|
+
/// as the parser could get (see [`read_until_unreadable`]).
|
|
193
|
+
pub(crate) fn read_file(path: &Path) -> std::io::Result<Vec<u8>> {
|
|
194
|
+
let mut file = std::fs::File::open(path)?;
|
|
195
|
+
let mut bytes = Vec::new();
|
|
196
|
+
if let Ok(metadata) = file.metadata() {
|
|
197
|
+
bytes.reserve_exact(usize::try_from(metadata.len()).unwrap_or(0));
|
|
198
|
+
}
|
|
199
|
+
read_until_unreadable(&mut file, &mut bytes)?;
|
|
200
|
+
Ok(bytes)
|
|
201
|
+
}
|
|
202
|
+
|
|
191
203
|
/// Stack size for validation threads: enough for documents nested as deeply as the
|
|
192
204
|
/// loader allows (pages are only committed as they are used).
|
|
193
205
|
const STACK_SIZE: usize = 256 << 20;
|
|
@@ -291,7 +303,7 @@ fn read_and_validate(path: &Path, options: &Options) -> Result<Outcome, LoadErro
|
|
|
291
303
|
return Err(not_found());
|
|
292
304
|
}
|
|
293
305
|
let absolute = std::path::absolute(path).map_err(|_| not_found())?;
|
|
294
|
-
let bytes =
|
|
306
|
+
let bytes = read_file(&absolute).map_err(|error| LoadError {
|
|
295
307
|
message: error.to_string(),
|
|
296
308
|
})?;
|
|
297
309
|
// Upstream reads files through `urlopen`, which is the stream name PyYAML reports.
|