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.
Files changed (115) hide show
  1. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/Cargo.lock +3 -3
  2. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/Cargo.toml +3 -3
  3. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/PKG-INFO +13 -12
  4. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/README.md +12 -11
  5. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/README.md +12 -11
  6. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/document.rs +98 -57
  7. {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
  8. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/loader.rs +9 -1
  9. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/retrieve.rs +10 -1
  10. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs-cli/README.md +12 -11
  11. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/LICENSE +0 -0
  12. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/NOTICE +0 -0
  13. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/Cargo.toml +0 -0
  14. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/LICENSE +0 -0
  15. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/NOTICE +0 -0
  16. {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
  17. {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
  18. {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
  19. {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
  20. {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
  21. {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
  22. {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
  23. {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
  24. {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
  25. {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
  26. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/error.rs +0 -0
  27. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/formats.rs +0 -0
  28. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/keywords.rs +0 -0
  29. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/lib.rs +0 -0
  30. {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
  31. {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
  32. {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
  33. {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
  34. {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
  35. {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
  36. {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
  37. {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
  38. {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
  39. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/meta.rs +0 -0
  40. {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
  41. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/pyformat.rs +0 -0
  42. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/pyjsonschema.rs +0 -0
  43. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/pyobj.rs +0 -0
  44. {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
  45. {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
  46. {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
  47. {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
  48. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/pyregex.rs +0 -0
  49. {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
  50. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/pyrepr.rs +0 -0
  51. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/pyurl.rs +0 -0
  52. {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
  53. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/values.rs +0 -0
  54. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs/src/version.rs +0 -0
  55. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs-cli/Cargo.toml +0 -0
  56. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs-cli/LICENSE +0 -0
  57. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs-cli/NOTICE +0 -0
  58. {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
  59. {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
  60. {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
  61. {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
  62. {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
  63. {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
  64. {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
  65. {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
  66. {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
  67. {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
  68. {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
  69. {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
  70. {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
  71. {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
  72. {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
  73. {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
  74. {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
  75. {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
  76. {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
  77. {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
  78. {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
  79. {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
  80. {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
  81. {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
  82. {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
  83. {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
  84. {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
  85. {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
  86. {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
  87. {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
  88. {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
  89. {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
  90. {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
  91. {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
  92. {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
  93. {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
  94. {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
  95. {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
  96. {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
  97. {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
  98. {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
  99. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/crates/openapi-spec-validator-rs-py/Cargo.toml +0 -0
  100. {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
  101. {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
  102. {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
  103. {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
  104. {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
  105. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/pyproject.toml +0 -0
  106. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/python/openapi_spec_validator_rs/__init__.py +0 -0
  107. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/python/openapi_spec_validator_rs/__main__.py +0 -0
  108. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/python/openapi_spec_validator_rs/_core.pyi +0 -0
  109. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/python/openapi_spec_validator_rs/exceptions.py +0 -0
  110. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/python/openapi_spec_validator_rs/handlers.py +0 -0
  111. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/python/openapi_spec_validator_rs/proxies.py +0 -0
  112. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/python/openapi_spec_validator_rs/py.typed +0 -0
  113. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/python/openapi_spec_validator_rs/shortcuts.py +0 -0
  114. {openapi_spec_validator_rs-0.2.0 → openapi_spec_validator_rs-0.2.1}/python/openapi_spec_validator_rs/validators.py +0 -0
  115. {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.0"
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.0"
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.0"
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.0"
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.0", default-features = false }
18
- openapi-spec-validator-rs-cli = { path = "crates/openapi-spec-validator-rs-cli", version = "0.2.0", default-features = false }
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.0
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 40 to 145 times faster on large
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.41 s – 8.18 s | **113–144×** |
43
- | GitHub (OpenAPI 3.0, YAML, 10.3 MB) | **152 ms** | 6.45 s – 8.20 s | **42–54×** |
44
- | Stripe (OpenAPI 3.0, JSON, 8.3 MB) | **31 ms** | 1.80 s – 2.82 s | **59–92×** |
45
- | Kubernetes (Swagger 2.0, JSON, 4.3 MB) | **20 ms** | 762 ms – 2.13 s | **39–109×** |
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.0]
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) | **33 ms** | 4.67 s (143× slower) | 6.38 s (196× slower) |
188
- | Stripe (8.3 MB) | **16 ms** | 0.89 s (55× slower) | 1.91 s (119× slower) |
189
- | Kubernetes (4.3 MB) | **11 ms** | 0.33 s (29× slower) | 1.70 s (149× slower) |
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
- It also needs about a quarter of the memory on JSON specs: 81 MiB instead of 290 MiB for
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 40 to 145 times faster on large
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.41 s – 8.18 s | **113–144×** |
18
- | GitHub (OpenAPI 3.0, YAML, 10.3 MB) | **152 ms** | 6.45 s – 8.20 s | **42–54×** |
19
- | Stripe (OpenAPI 3.0, JSON, 8.3 MB) | **31 ms** | 1.80 s – 2.82 s | **59–92×** |
20
- | Kubernetes (Swagger 2.0, JSON, 4.3 MB) | **20 ms** | 762 ms – 2.13 s | **39–109×** |
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.0]
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) | **33 ms** | 4.67 s (143× slower) | 6.38 s (196× slower) |
163
- | Stripe (8.3 MB) | **16 ms** | 0.89 s (55× slower) | 1.91 s (119× slower) |
164
- | Kubernetes (4.3 MB) | **11 ms** | 0.33 s (29× slower) | 1.70 s (149× slower) |
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
- It also needs about a quarter of the memory on JSON specs: 81 MiB instead of 290 MiB for
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 40 to 145 times faster on large
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.41 s – 8.18 s | **113–144×** |
18
- | GitHub (OpenAPI 3.0, YAML, 10.3 MB) | **152 ms** | 6.45 s – 8.20 s | **42–54×** |
19
- | Stripe (OpenAPI 3.0, JSON, 8.3 MB) | **31 ms** | 1.80 s – 2.82 s | **59–92×** |
20
- | Kubernetes (Swagger 2.0, JSON, 4.3 MB) | **20 ms** | 762 ms – 2.13 s | **39–109×** |
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.0]
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) | **33 ms** | 4.67 s (143× slower) | 6.38 s (196× slower) |
163
- | Stripe (8.3 MB) | **16 ms** | 0.89 s (55× slower) | 1.91 s (119× slower) |
164
- | Kubernetes (4.3 MB) | **11 ms** | 0.33 s (29× slower) | 1.70 s (149× slower) |
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
- It also needs about a quarter of the memory on JSON specs: 81 MiB instead of 290 MiB for
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) => match enc.open(obj, depth)? {
98
- Opened::Leaf(v) => attach(top, v),
99
- Opened::Frame(f) => stack.push(f),
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) => attach(parent, value),
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 { items, out, .. } => {
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
- dump::to_value(&mut doc.nodes, &graph, root, depth_limit, move_strings)
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 40 to 145 times faster on large
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.41 s – 8.18 s | **113–144×** |
18
- | GitHub (OpenAPI 3.0, YAML, 10.3 MB) | **152 ms** | 6.45 s – 8.20 s | **42–54×** |
19
- | Stripe (OpenAPI 3.0, JSON, 8.3 MB) | **31 ms** | 1.80 s – 2.82 s | **59–92×** |
20
- | Kubernetes (Swagger 2.0, JSON, 4.3 MB) | **20 ms** | 762 ms – 2.13 s | **39–109×** |
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.0]
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) | **33 ms** | 4.67 s (143× slower) | 6.38 s (196× slower) |
163
- | Stripe (8.3 MB) | **16 ms** | 0.89 s (55× slower) | 1.91 s (119× slower) |
164
- | Kubernetes (4.3 MB) | **11 ms** | 0.33 s (29× slower) | 1.70 s (149× slower) |
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
- It also needs about a quarter of the memory on JSON specs: 81 MiB instead of 290 MiB for
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.