openapi-spec-validator-rs 0.2.0__tar.gz → 0.2.2__tar.gz

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