openapi-spec-validator-rs 0.2.0__tar.gz → 0.2.1__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.1}/Cargo.lock +3 -3
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/Cargo.toml +3 -3
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/PKG-INFO +13 -12
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/README.md +12 -11
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/README.md +12 -11
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/document.rs +98 -57
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/loader/dump.rs +104 -9
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/loader.rs +9 -1
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/retrieve.rs +10 -1
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs-cli/README.md +12 -11
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/LICENSE +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/NOTICE +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/Cargo.toml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/LICENSE +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/NOTICE +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/crates/openapi-spec-validator-rs/src/error.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/formats.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/keywords.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/lib.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/loader/bigint.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/loader/compose.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/loader/construct.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/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.1}/crates/openapi-spec-validator-rs/src/loader/libyaml.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/loader/pyfmt.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/loader/resolve.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/loader/scalars.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/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.1}/crates/openapi-spec-validator-rs/src/meta.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/pycompat_tests.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/pyformat.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/pyjsonschema.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/pyobj.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/pyregex/ast.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/pyregex/parser.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/pyregex/translate.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/pyregex/unicode.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/pyregex.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/pyregex_tests.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/pyrepr.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/pyurl.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/schema_path.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/values.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/version.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs-cli/Cargo.toml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs-cli/LICENSE +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs-cli/NOTICE +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs-cli/src/lib.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs-cli/src/main.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/crates/openapi-spec-validator-rs-cli/tests/output.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/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.1}/crates/openapi-spec-validator-rs-py/Cargo.toml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs-py/src/convert.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs-py/src/errors.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs-py/src/lib.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs-py/src/retrieve.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs-py/src/stack.rs +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/pyproject.toml +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/python/openapi_spec_validator_rs/__init__.py +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/python/openapi_spec_validator_rs/__main__.py +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/python/openapi_spec_validator_rs/_core.pyi +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/python/openapi_spec_validator_rs/exceptions.py +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/python/openapi_spec_validator_rs/handlers.py +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/python/openapi_spec_validator_rs/proxies.py +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/python/openapi_spec_validator_rs/py.typed +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/python/openapi_spec_validator_rs/shortcuts.py +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/python/openapi_spec_validator_rs/validators.py +0 -0
- {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/python/openapi_spec_validator_rs/versions.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.1"
|
|
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.1"
|
|
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.1"
|
|
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.1"
|
|
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.1", default-features = false }
|
|
18
|
+
openapi-spec-validator-rs-cli = { path = "crates/openapi-spec-validator-rs-cli", version = "0.2.1", 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.1
|
|
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
|
|
@@ -165,7 +165,7 @@ repos:
|
|
|
165
165
|
name: openapi-spec-validator-rs
|
|
166
166
|
entry: openapi-spec-validator-rs
|
|
167
167
|
language: python
|
|
168
|
-
additional_dependencies: [openapi-spec-validator-rs==0.2.
|
|
168
|
+
additional_dependencies: [openapi-spec-validator-rs==0.2.1]
|
|
169
169
|
files: .*(openapi|swagger).*\.(json|yaml|yml)$
|
|
170
170
|
```
|
|
171
171
|
|
|
@@ -184,12 +184,13 @@ validates FastAPI's `app.openapi()` does), median of 5 runs:
|
|
|
184
184
|
|
|
185
185
|
| Spec | openapi-spec-validator-rs | openapi-spec-validator, jsonschema-rs backend | openapi-spec-validator, default backend |
|
|
186
186
|
|---|---:|---:|---:|
|
|
187
|
-
| GitHub (13.5 MB) | **
|
|
188
|
-
| Stripe (8.3 MB) | **
|
|
189
|
-
| Kubernetes (4.3 MB) | **11 ms** | 0.33 s (
|
|
187
|
+
| GitHub (13.5 MB) | **30 ms** | 4.66 s (158× slower) | 6.40 s (217× slower) |
|
|
188
|
+
| Stripe (8.3 MB) | **15 ms** | 0.89 s (58× slower) | 1.92 s (126× slower) |
|
|
189
|
+
| Kubernetes (4.3 MB) | **11 ms** | 0.33 s (30× slower) | 1.70 s (154× slower) |
|
|
190
190
|
|
|
191
|
-
|
|
192
|
-
GitHub's (98 MiB through the `pip`-installed command)
|
|
191
|
+
On the same machine it also needs about a quarter of the memory on JSON specs: 84 MiB
|
|
192
|
+
instead of 289 MiB for GitHub's (98 MiB through the `pip`-installed command); the ratio
|
|
193
|
+
varies with the platform's allocator and Python build.
|
|
193
194
|
|
|
194
195
|
Validating every spec in the APIs.guru directory that openapi-spec-validator accepts (3,646
|
|
195
196
|
files, 535 MB of YAML) takes 1.6 s instead of 202–332 s.
|
|
@@ -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
|
|
@@ -140,7 +140,7 @@ repos:
|
|
|
140
140
|
name: openapi-spec-validator-rs
|
|
141
141
|
entry: openapi-spec-validator-rs
|
|
142
142
|
language: python
|
|
143
|
-
additional_dependencies: [openapi-spec-validator-rs==0.2.
|
|
143
|
+
additional_dependencies: [openapi-spec-validator-rs==0.2.1]
|
|
144
144
|
files: .*(openapi|swagger).*\.(json|yaml|yml)$
|
|
145
145
|
```
|
|
146
146
|
|
|
@@ -159,12 +159,13 @@ validates FastAPI's `app.openapi()` does), median of 5 runs:
|
|
|
159
159
|
|
|
160
160
|
| Spec | openapi-spec-validator-rs | openapi-spec-validator, jsonschema-rs backend | openapi-spec-validator, default backend |
|
|
161
161
|
|---|---:|---:|---:|
|
|
162
|
-
| GitHub (13.5 MB) | **
|
|
163
|
-
| Stripe (8.3 MB) | **
|
|
164
|
-
| Kubernetes (4.3 MB) | **11 ms** | 0.33 s (
|
|
162
|
+
| GitHub (13.5 MB) | **30 ms** | 4.66 s (158× slower) | 6.40 s (217× slower) |
|
|
163
|
+
| Stripe (8.3 MB) | **15 ms** | 0.89 s (58× slower) | 1.92 s (126× slower) |
|
|
164
|
+
| Kubernetes (4.3 MB) | **11 ms** | 0.33 s (30× slower) | 1.70 s (154× slower) |
|
|
165
165
|
|
|
166
|
-
|
|
167
|
-
GitHub's (98 MiB through the `pip`-installed command)
|
|
166
|
+
On the same machine it also needs about a quarter of the memory on JSON specs: 84 MiB
|
|
167
|
+
instead of 289 MiB for GitHub's (98 MiB through the `pip`-installed command); the ratio
|
|
168
|
+
varies with the platform's allocator and Python build.
|
|
168
169
|
|
|
169
170
|
Validating every spec in the APIs.guru directory that openapi-spec-validator accepts (3,646
|
|
170
171
|
files, 535 MB of YAML) takes 1.6 s instead of 202–332 s.
|
|
@@ -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
|
|
@@ -140,7 +140,7 @@ repos:
|
|
|
140
140
|
name: openapi-spec-validator-rs
|
|
141
141
|
entry: openapi-spec-validator-rs
|
|
142
142
|
language: python
|
|
143
|
-
additional_dependencies: [openapi-spec-validator-rs==0.2.
|
|
143
|
+
additional_dependencies: [openapi-spec-validator-rs==0.2.1]
|
|
144
144
|
files: .*(openapi|swagger).*\.(json|yaml|yml)$
|
|
145
145
|
```
|
|
146
146
|
|
|
@@ -159,12 +159,13 @@ validates FastAPI's `app.openapi()` does), median of 5 runs:
|
|
|
159
159
|
|
|
160
160
|
| Spec | openapi-spec-validator-rs | openapi-spec-validator, jsonschema-rs backend | openapi-spec-validator, default backend |
|
|
161
161
|
|---|---:|---:|---:|
|
|
162
|
-
| GitHub (13.5 MB) | **
|
|
163
|
-
| Stripe (8.3 MB) | **
|
|
164
|
-
| Kubernetes (4.3 MB) | **11 ms** | 0.33 s (
|
|
162
|
+
| GitHub (13.5 MB) | **30 ms** | 4.66 s (158× slower) | 6.40 s (217× slower) |
|
|
163
|
+
| Stripe (8.3 MB) | **15 ms** | 0.89 s (58× slower) | 1.92 s (126× slower) |
|
|
164
|
+
| Kubernetes (4.3 MB) | **11 ms** | 0.33 s (30× slower) | 1.70 s (154× slower) |
|
|
165
165
|
|
|
166
|
-
|
|
167
|
-
GitHub's (98 MiB through the `pip`-installed command)
|
|
166
|
+
On the same machine it also needs about a quarter of the memory on JSON specs: 84 MiB
|
|
167
|
+
instead of 289 MiB for GitHub's (98 MiB through the `pip`-installed command); the ratio
|
|
168
|
+
varies with the platform's allocator and Python build.
|
|
168
169
|
|
|
169
170
|
Validating every spec in the APIs.guru directory that openapi-spec-validator accepts (3,646
|
|
170
171
|
files, 535 MB of YAML) takes 1.6 s instead of 202–332 s.
|
|
@@ -123,6 +123,9 @@ pub(crate) struct Documents<'a> {
|
|
|
123
123
|
externals: Arena<External>,
|
|
124
124
|
/// Document index by URI (without fragment): 0 is the root, `n` is `externals[n - 1]`.
|
|
125
125
|
by_uri: RefCell<HashMap<String, usize>>,
|
|
126
|
+
/// The one-character strings that pointers into strings resolve to: Python indexes
|
|
127
|
+
/// a `str` like a sequence and yields a new `str` each time.
|
|
128
|
+
characters: Arena<Value>,
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
impl<'a> Documents<'a> {
|
|
@@ -138,6 +141,7 @@ impl<'a> Documents<'a> {
|
|
|
138
141
|
retriever,
|
|
139
142
|
externals: Arena::new(),
|
|
140
143
|
by_uri: RefCell::new(by_uri),
|
|
144
|
+
characters: Arena::new(),
|
|
141
145
|
}
|
|
142
146
|
}
|
|
143
147
|
|
|
@@ -296,10 +300,74 @@ impl<'a> Documents<'a> {
|
|
|
296
300
|
),
|
|
297
301
|
));
|
|
298
302
|
}
|
|
299
|
-
let value = pointer(contents, fragment)?;
|
|
303
|
+
let value = self.pointer(contents, fragment)?;
|
|
300
304
|
Ok(Node { value, doc })
|
|
301
305
|
}
|
|
302
306
|
|
|
307
|
+
/// `Resource.pointer` from `referencing`: each segment indexes a mapping by key or a
|
|
308
|
+
/// sequence by position. A Python `str` is a sequence too, so a pointer can reach
|
|
309
|
+
/// into a string and come out with one of its characters.
|
|
310
|
+
fn pointer<'d>(&'d self, document: &'d Value, pointer: &str) -> Result<&'d Value, LookupError> {
|
|
311
|
+
let nowhere = || {
|
|
312
|
+
let mut message = format!(
|
|
313
|
+
"{} does not exist within {}",
|
|
314
|
+
pyrepr::repr_str(pointer),
|
|
315
|
+
pyrepr::repr(document)
|
|
316
|
+
);
|
|
317
|
+
if pointer == "/" {
|
|
318
|
+
message.push_str(
|
|
319
|
+
". The pointer '/' is a valid JSON Pointer but it points to an empty string \
|
|
320
|
+
property ''. If you intended to point to the entire resource, you should use '#'.",
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
LookupError::referencing("PointerToNowhere", message)
|
|
324
|
+
};
|
|
325
|
+
let invalid_index = |segment: &str| {
|
|
326
|
+
LookupError::from(Crash(format!(
|
|
327
|
+
"invalid literal for int() with base 10: {}",
|
|
328
|
+
pyrepr::repr_str(segment)
|
|
329
|
+
)))
|
|
330
|
+
};
|
|
331
|
+
// `int(segment)`, then Python's negative indexing.
|
|
332
|
+
let position = |segment: &str, len: usize| -> Result<Option<usize>, LookupError> {
|
|
333
|
+
let index = python_int(segment).ok_or_else(|| invalid_index(segment))?;
|
|
334
|
+
let index = if index < 0 {
|
|
335
|
+
i128::try_from(len).unwrap_or(i128::MAX) + index
|
|
336
|
+
} else {
|
|
337
|
+
index
|
|
338
|
+
};
|
|
339
|
+
Ok(usize::try_from(index).ok().filter(|&i| i < len))
|
|
340
|
+
};
|
|
341
|
+
let decoded = percent_decode_str(&pointer[1..]).decode_utf8_lossy();
|
|
342
|
+
let mut contents = document;
|
|
343
|
+
for segment in decoded.split('/') {
|
|
344
|
+
contents = match contents {
|
|
345
|
+
Value::Array(items) => position(segment, items.len())?
|
|
346
|
+
.map(|i| &items[i])
|
|
347
|
+
.ok_or_else(nowhere)?,
|
|
348
|
+
Value::Object(map) => {
|
|
349
|
+
let key = segment.replace("~1", "/").replace("~0", "~");
|
|
350
|
+
map.get(&key).ok_or_else(nowhere)?
|
|
351
|
+
}
|
|
352
|
+
Value::String(text) => {
|
|
353
|
+
let character = position(segment, text.chars().count())?
|
|
354
|
+
.and_then(|i| text.chars().nth(i))
|
|
355
|
+
.ok_or_else(nowhere)?;
|
|
356
|
+
let index = self.characters.push(Value::String(character.to_string()));
|
|
357
|
+
self.characters.get(index)
|
|
358
|
+
}
|
|
359
|
+
other => {
|
|
360
|
+
return Err(Crash(format!(
|
|
361
|
+
"'{}' object is not subscriptable",
|
|
362
|
+
python_type_name(other)
|
|
363
|
+
))
|
|
364
|
+
.into());
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
Ok(contents)
|
|
369
|
+
}
|
|
370
|
+
|
|
303
371
|
/// The child `key` of a mapping node, resolved (`path / key`).
|
|
304
372
|
pub fn get<'d>(&'d self, node: Node<'d>, key: &str) -> Result<Option<Node<'d>>, Crash> {
|
|
305
373
|
match node.value {
|
|
@@ -368,62 +436,6 @@ pub(crate) fn as_ref(value: &Value) -> Option<&str> {
|
|
|
368
436
|
value.as_object()?.get("$ref")?.as_str()
|
|
369
437
|
}
|
|
370
438
|
|
|
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)
|
|
425
|
-
}
|
|
426
|
-
|
|
427
439
|
/// Python `int(s)` for base-10 literals (ASCII digits, sign, underscores, whitespace).
|
|
428
440
|
fn python_int(s: &str) -> Option<i128> {
|
|
429
441
|
let s = s.trim();
|
|
@@ -526,6 +538,35 @@ mod tests {
|
|
|
526
538
|
);
|
|
527
539
|
}
|
|
528
540
|
|
|
541
|
+
#[test]
|
|
542
|
+
fn pointers_index_strings_like_python() {
|
|
543
|
+
let spec = json!({
|
|
544
|
+
"info": {"title": "tx"},
|
|
545
|
+
"a": {"$ref": "#/info/title/0"},
|
|
546
|
+
"b": {"$ref": "#/info/title/-1"},
|
|
547
|
+
"c": {"$ref": "#/info/title/x"},
|
|
548
|
+
"d": {"$ref": "#/info/title/2"},
|
|
549
|
+
"e": {"$ref": "#/info/title/1/0/0"},
|
|
550
|
+
});
|
|
551
|
+
let docs = Documents::new(&spec, "", None);
|
|
552
|
+
assert_eq!(docs.child(docs.root(), "a").unwrap().value, &json!("t"));
|
|
553
|
+
assert_eq!(docs.child(docs.root(), "b").unwrap().value, &json!("x"));
|
|
554
|
+
assert_eq!(docs.child(docs.root(), "e").unwrap().value, &json!("x"));
|
|
555
|
+
assert_eq!(
|
|
556
|
+
docs.child(docs.root(), "c").unwrap_err(),
|
|
557
|
+
Crash("invalid literal for int() with base 10: 'x'".to_owned())
|
|
558
|
+
);
|
|
559
|
+
assert!(
|
|
560
|
+
docs.child(docs.root(), "d")
|
|
561
|
+
.unwrap_err()
|
|
562
|
+
.0
|
|
563
|
+
.starts_with("'/info/title/2' does not exist within")
|
|
564
|
+
);
|
|
565
|
+
// Each resolution yields a new string, as in Python.
|
|
566
|
+
let first = docs.child(docs.root(), "a").unwrap().id();
|
|
567
|
+
assert_ne!(first, docs.child(docs.root(), "a").unwrap().id());
|
|
568
|
+
}
|
|
569
|
+
|
|
529
570
|
#[test]
|
|
530
571
|
fn ref_cycles_hit_the_recursion_limit() {
|
|
531
572
|
let spec = json!({"a": {"$ref": "#/b"}, "b": {"$ref": "#/a"}});
|
|
@@ -20,15 +20,33 @@ enum Frame {
|
|
|
20
20
|
items: ListRef,
|
|
21
21
|
next: usize,
|
|
22
22
|
out: Vec<Value>,
|
|
23
|
+
/// Dry run: how many containers deep the children reach (see `Encoder::depths`).
|
|
24
|
+
below: usize,
|
|
23
25
|
},
|
|
24
26
|
Dict {
|
|
25
27
|
id: u32,
|
|
26
28
|
next: usize,
|
|
27
29
|
out: Map<String, Value>,
|
|
28
30
|
key: String,
|
|
31
|
+
below: usize,
|
|
29
32
|
},
|
|
30
33
|
}
|
|
31
34
|
|
|
35
|
+
impl Frame {
|
|
36
|
+
fn note_child(&mut self, reaches: usize) {
|
|
37
|
+
let below = match self {
|
|
38
|
+
Self::List { below, .. } | Self::Dict { below, .. } => below,
|
|
39
|
+
};
|
|
40
|
+
*below = (*below).max(reaches);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fn below(&self) -> usize {
|
|
44
|
+
match self {
|
|
45
|
+
Self::List { below, .. } | Self::Dict { below, .. } => *below,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
32
50
|
#[derive(Clone, Copy)]
|
|
33
51
|
enum ListRef {
|
|
34
52
|
List(u32),
|
|
@@ -38,6 +56,9 @@ enum ListRef {
|
|
|
38
56
|
enum Opened {
|
|
39
57
|
Leaf(Value),
|
|
40
58
|
Frame(Frame),
|
|
59
|
+
/// Dry run: a shared container already traversed without error, whose contents
|
|
60
|
+
/// reach this many containers below it.
|
|
61
|
+
Known(usize),
|
|
41
62
|
}
|
|
42
63
|
|
|
43
64
|
struct Encoder<'a> {
|
|
@@ -48,14 +69,41 @@ struct Encoder<'a> {
|
|
|
48
69
|
depth_limit: usize,
|
|
49
70
|
/// Strings can be moved out of the nodes when no node is reachable twice.
|
|
50
71
|
move_strings: bool,
|
|
72
|
+
/// Only look for the error the encoder would raise, building nothing.
|
|
73
|
+
dry: bool,
|
|
74
|
+
/// Dry run: for each list and dict traversed without error, how many containers deep
|
|
75
|
+
/// its contents reach (an empty container counts one, a scalar none). A node shared
|
|
76
|
+
/// through aliases is then not walked again: at a new depth it either fits, or its
|
|
77
|
+
/// first error in traversal order is the recursion limit (it holds no other error).
|
|
78
|
+
list_depths: Vec<Option<usize>>,
|
|
79
|
+
dict_depths: Vec<Option<usize>>,
|
|
51
80
|
}
|
|
52
81
|
|
|
82
|
+
/// `check_first` runs the traversal once without building anything, so that an error
|
|
83
|
+
/// (the recursion limit above all) is found before any value is materialised: when
|
|
84
|
+
/// nodes are shared through aliases, the value can be far larger than the node graph,
|
|
85
|
+
/// and `json.dumps` raises while holding only the text written so far.
|
|
53
86
|
pub(super) fn to_value(
|
|
54
87
|
nodes: &mut [Node],
|
|
55
88
|
graph: &Graph,
|
|
56
89
|
root: Obj,
|
|
57
90
|
depth_limit: usize,
|
|
58
91
|
move_strings: bool,
|
|
92
|
+
check_first: bool,
|
|
93
|
+
) -> Result<Value, String> {
|
|
94
|
+
if check_first {
|
|
95
|
+
encode(nodes, graph, root, depth_limit, false, true)?;
|
|
96
|
+
}
|
|
97
|
+
encode(nodes, graph, root, depth_limit, move_strings, false)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fn encode(
|
|
101
|
+
nodes: &mut [Node],
|
|
102
|
+
graph: &Graph,
|
|
103
|
+
root: Obj,
|
|
104
|
+
depth_limit: usize,
|
|
105
|
+
move_strings: bool,
|
|
106
|
+
dry: bool,
|
|
59
107
|
) -> Result<Value, String> {
|
|
60
108
|
let mut enc = Encoder {
|
|
61
109
|
nodes,
|
|
@@ -64,10 +112,14 @@ pub(super) fn to_value(
|
|
|
64
112
|
dict_marks: vec![false; graph.dicts.len()],
|
|
65
113
|
depth_limit,
|
|
66
114
|
move_strings,
|
|
115
|
+
dry,
|
|
116
|
+
list_depths: vec![None; if dry { graph.lists.len() } else { 0 }],
|
|
117
|
+
dict_depths: vec![None; if dry { graph.dicts.len() } else { 0 }],
|
|
67
118
|
};
|
|
68
119
|
let mut stack: Vec<Frame> = Vec::new();
|
|
69
120
|
match enc.open(root, 0)? {
|
|
70
121
|
Opened::Leaf(v) => return Ok(v),
|
|
122
|
+
Opened::Known(_) => return Ok(Value::Null),
|
|
71
123
|
Opened::Frame(f) => stack.push(f),
|
|
72
124
|
}
|
|
73
125
|
loop {
|
|
@@ -94,17 +146,34 @@ pub(super) fn to_value(
|
|
|
94
146
|
}
|
|
95
147
|
};
|
|
96
148
|
match child {
|
|
97
|
-
Some(obj) =>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
149
|
+
Some(obj) => {
|
|
150
|
+
let container = matches!(obj, Obj::List(_) | Obj::Tuple(_) | Obj::Dict(_));
|
|
151
|
+
match enc.open(obj, depth)? {
|
|
152
|
+
Opened::Leaf(v) => {
|
|
153
|
+
if enc.dry {
|
|
154
|
+
top.note_child(usize::from(container));
|
|
155
|
+
} else {
|
|
156
|
+
attach(top, v);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
Opened::Known(below) => top.note_child(below + 1),
|
|
160
|
+
Opened::Frame(f) => stack.push(f),
|
|
161
|
+
}
|
|
162
|
+
}
|
|
101
163
|
None => {
|
|
102
164
|
let Some(done) = stack.pop() else {
|
|
103
165
|
return Ok(Value::Null);
|
|
104
166
|
};
|
|
167
|
+
let below = done.below();
|
|
105
168
|
let value = enc.close(done);
|
|
106
169
|
match stack.last_mut() {
|
|
107
|
-
Some(parent) =>
|
|
170
|
+
Some(parent) => {
|
|
171
|
+
if enc.dry {
|
|
172
|
+
parent.note_child(below + 1);
|
|
173
|
+
} else {
|
|
174
|
+
attach(parent, value);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
108
177
|
None => return Ok(value),
|
|
109
178
|
}
|
|
110
179
|
}
|
|
@@ -152,6 +221,7 @@ impl Encoder<'_> {
|
|
|
152
221
|
Obj::Int(i) => Value::Number(i.into()),
|
|
153
222
|
Obj::BigInt(id) => big_value(&self.graph.bigints[id as usize])?,
|
|
154
223
|
Obj::Float(f) => Number::from_f64(f).map_or(Value::Null, Value::Number),
|
|
224
|
+
Obj::Str(_) if self.dry => Value::Null,
|
|
155
225
|
Obj::Str(id) => Value::String(self.string(id)),
|
|
156
226
|
Obj::Bytes => return Err(type_error("bytes")),
|
|
157
227
|
Obj::Date => return Err(type_error("date")),
|
|
@@ -162,6 +232,19 @@ impl Encoder<'_> {
|
|
|
162
232
|
if parent_depth + 1 > self.depth_limit {
|
|
163
233
|
return Err(RECURSION.to_owned());
|
|
164
234
|
}
|
|
235
|
+
if self.dry {
|
|
236
|
+
let known = match obj {
|
|
237
|
+
Obj::List(id) => self.list_depths[id as usize],
|
|
238
|
+
Obj::Dict(id) => self.dict_depths[id as usize],
|
|
239
|
+
_ => None,
|
|
240
|
+
};
|
|
241
|
+
if let Some(below) = known {
|
|
242
|
+
if parent_depth + 1 + below > self.depth_limit {
|
|
243
|
+
return Err(RECURSION.to_owned());
|
|
244
|
+
}
|
|
245
|
+
return Ok(Opened::Known(below));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
165
248
|
let opened = self.open_container(obj);
|
|
166
249
|
if let Opened::Frame(frame) = &opened {
|
|
167
250
|
self.enter(frame)?;
|
|
@@ -182,8 +265,9 @@ impl Encoder<'_> {
|
|
|
182
265
|
Opened::Frame(Frame::Dict {
|
|
183
266
|
id,
|
|
184
267
|
next: 0,
|
|
185
|
-
out: Map::with_capacity(len),
|
|
268
|
+
out: Map::with_capacity(if self.dry { 0 } else { len }),
|
|
186
269
|
key: String::new(),
|
|
270
|
+
below: 0,
|
|
187
271
|
})
|
|
188
272
|
}
|
|
189
273
|
Obj::List(id) => {
|
|
@@ -194,7 +278,8 @@ impl Encoder<'_> {
|
|
|
194
278
|
Opened::Frame(Frame::List {
|
|
195
279
|
items: ListRef::List(id),
|
|
196
280
|
next: 0,
|
|
197
|
-
out: Vec::with_capacity(len),
|
|
281
|
+
out: Vec::with_capacity(if self.dry { 0 } else { len }),
|
|
282
|
+
below: 0,
|
|
198
283
|
})
|
|
199
284
|
}
|
|
200
285
|
// Tuples (from !!omap / !!pairs) always hold two items and are never shared.
|
|
@@ -202,6 +287,7 @@ impl Encoder<'_> {
|
|
|
202
287
|
items: ListRef::Tuple(id),
|
|
203
288
|
next: 0,
|
|
204
289
|
out: Vec::with_capacity(2),
|
|
290
|
+
below: 0,
|
|
205
291
|
}),
|
|
206
292
|
_ => Opened::Leaf(Value::Null),
|
|
207
293
|
}
|
|
@@ -209,14 +295,22 @@ impl Encoder<'_> {
|
|
|
209
295
|
|
|
210
296
|
fn close(&mut self, frame: Frame) -> Value {
|
|
211
297
|
match frame {
|
|
212
|
-
Frame::List {
|
|
298
|
+
Frame::List {
|
|
299
|
+
items, out, below, ..
|
|
300
|
+
} => {
|
|
213
301
|
if let ListRef::List(id) = items {
|
|
214
302
|
self.list_marks[id as usize] = false;
|
|
303
|
+
if self.dry {
|
|
304
|
+
self.list_depths[id as usize] = Some(below);
|
|
305
|
+
}
|
|
215
306
|
}
|
|
216
307
|
Value::Array(out)
|
|
217
308
|
}
|
|
218
|
-
Frame::Dict { id, out, .. } => {
|
|
309
|
+
Frame::Dict { id, out, below, .. } => {
|
|
219
310
|
self.dict_marks[id as usize] = false;
|
|
311
|
+
if self.dry {
|
|
312
|
+
self.dict_depths[id as usize] = Some(below);
|
|
313
|
+
}
|
|
220
314
|
Value::Object(out)
|
|
221
315
|
}
|
|
222
316
|
}
|
|
@@ -224,6 +318,7 @@ impl Encoder<'_> {
|
|
|
224
318
|
|
|
225
319
|
fn key_string(&mut self, key: Obj) -> Result<String, String> {
|
|
226
320
|
Ok(match key {
|
|
321
|
+
Obj::Str(_) if self.dry => String::new(),
|
|
227
322
|
Obj::Str(id) => self.string(id),
|
|
228
323
|
Obj::Float(f) => json_float(f),
|
|
229
324
|
Obj::Nan(_) => "NaN".to_owned(),
|
|
@@ -97,7 +97,15 @@ fn load_yaml(
|
|
|
97
97
|
drop(parser);
|
|
98
98
|
let (graph, root) = construct::construct(&mut doc, stream_name)?;
|
|
99
99
|
let move_strings = !doc.has_aliases;
|
|
100
|
-
|
|
100
|
+
// With aliases the value can dwarf the node graph: find the encoder's error first.
|
|
101
|
+
dump::to_value(
|
|
102
|
+
&mut doc.nodes,
|
|
103
|
+
&graph,
|
|
104
|
+
root,
|
|
105
|
+
depth_limit,
|
|
106
|
+
move_strings,
|
|
107
|
+
doc.has_aliases,
|
|
108
|
+
)
|
|
101
109
|
};
|
|
102
110
|
run().map_err(|message| LoadError { message })
|
|
103
111
|
}
|
|
@@ -97,6 +97,7 @@ mod http {
|
|
|
97
97
|
#[derive(Clone)]
|
|
98
98
|
pub struct HttpRetriever {
|
|
99
99
|
timeout: Duration,
|
|
100
|
+
proxies: bool,
|
|
100
101
|
agent: OnceLock<Agent>,
|
|
101
102
|
}
|
|
102
103
|
|
|
@@ -110,10 +111,18 @@ mod http {
|
|
|
110
111
|
pub fn with_timeout(timeout: Duration) -> Self {
|
|
111
112
|
Self {
|
|
112
113
|
timeout,
|
|
114
|
+
proxies: true,
|
|
113
115
|
agent: OnceLock::new(),
|
|
114
116
|
}
|
|
115
117
|
}
|
|
116
118
|
|
|
119
|
+
/// The same retriever, ignoring the proxy environment variables (upstream's
|
|
120
|
+
/// `ProxyHandler({})`).
|
|
121
|
+
pub fn without_proxies(mut self) -> Self {
|
|
122
|
+
self.proxies = false;
|
|
123
|
+
self
|
|
124
|
+
}
|
|
125
|
+
|
|
117
126
|
fn agent(&self) -> &Agent {
|
|
118
127
|
self.agent.get_or_init(|| {
|
|
119
128
|
let tls = TlsConfig::builder()
|
|
@@ -145,7 +154,7 @@ mod http {
|
|
|
145
154
|
/// `GET url` through the proxy the environment names for its scheme.
|
|
146
155
|
fn get(&self, url: &str) -> Result<ureq::http::Response<ureq::Body>, RetrieveError> {
|
|
147
156
|
let failed = |error: ureq::Error| RetrieveError::new(error.to_string());
|
|
148
|
-
let proxy = proxy_for(url)
|
|
157
|
+
let proxy = if self.proxies { proxy_for(url)? } else { None };
|
|
149
158
|
let request = self.agent().get(url).config().proxy(proxy).build();
|
|
150
159
|
request.call().map_err(failed)
|
|
151
160
|
}
|
|
@@ -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
|
|
@@ -140,7 +140,7 @@ repos:
|
|
|
140
140
|
name: openapi-spec-validator-rs
|
|
141
141
|
entry: openapi-spec-validator-rs
|
|
142
142
|
language: python
|
|
143
|
-
additional_dependencies: [openapi-spec-validator-rs==0.2.
|
|
143
|
+
additional_dependencies: [openapi-spec-validator-rs==0.2.1]
|
|
144
144
|
files: .*(openapi|swagger).*\.(json|yaml|yml)$
|
|
145
145
|
```
|
|
146
146
|
|
|
@@ -159,12 +159,13 @@ validates FastAPI's `app.openapi()` does), median of 5 runs:
|
|
|
159
159
|
|
|
160
160
|
| Spec | openapi-spec-validator-rs | openapi-spec-validator, jsonschema-rs backend | openapi-spec-validator, default backend |
|
|
161
161
|
|---|---:|---:|---:|
|
|
162
|
-
| GitHub (13.5 MB) | **
|
|
163
|
-
| Stripe (8.3 MB) | **
|
|
164
|
-
| Kubernetes (4.3 MB) | **11 ms** | 0.33 s (
|
|
162
|
+
| GitHub (13.5 MB) | **30 ms** | 4.66 s (158× slower) | 6.40 s (217× slower) |
|
|
163
|
+
| Stripe (8.3 MB) | **15 ms** | 0.89 s (58× slower) | 1.92 s (126× slower) |
|
|
164
|
+
| Kubernetes (4.3 MB) | **11 ms** | 0.33 s (30× slower) | 1.70 s (154× slower) |
|
|
165
165
|
|
|
166
|
-
|
|
167
|
-
GitHub's (98 MiB through the `pip`-installed command)
|
|
166
|
+
On the same machine it also needs about a quarter of the memory on JSON specs: 84 MiB
|
|
167
|
+
instead of 289 MiB for GitHub's (98 MiB through the `pip`-installed command); the ratio
|
|
168
|
+
varies with the platform's allocator and Python build.
|
|
168
169
|
|
|
169
170
|
Validating every spec in the APIs.guru directory that openapi-spec-validator accepts (3,646
|
|
170
171
|
files, 535 MB of YAML) takes 1.6 s instead of 202–332 s.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|