openapi-spec-validator-rs 0.2.1__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.1 → openapi_spec_validator_rs-0.2.2}/Cargo.lock +3 -3
  2. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/Cargo.toml +3 -3
  3. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/PKG-INFO +14 -5
  4. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/README.md +13 -4
  5. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/README.md +13 -4
  6. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/document.rs +13 -6
  7. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/formats.rs +1 -1
  8. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/keywords.rs +3 -5
  9. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/lib.rs +14 -2
  10. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/loader/libyaml.rs +33 -26
  11. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/loader/scalars.rs +8 -4
  12. openapi_spec_validator_rs-0.2.2/crates/openapi-spec-validator-rs/src/loader/stream.rs +242 -0
  13. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/loader.rs +3 -2
  14. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/meta.rs +167 -35
  15. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/pyjsonschema.rs +15 -15
  16. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/pyobj.rs +52 -4
  17. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/pyrepr.rs +109 -4
  18. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/retrieve.rs +8 -10
  19. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/values.rs +19 -16
  20. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/version.rs +9 -2
  21. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/README.md +13 -4
  22. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/src/lib.rs +17 -12
  23. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-py/src/convert.rs +111 -15
  24. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-py/src/errors.rs +20 -9
  25. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-py/src/lib.rs +32 -17
  26. openapi_spec_validator_rs-0.2.2/crates/openapi-spec-validator-rs-py/src/stack.rs +62 -0
  27. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/python/openapi_spec_validator_rs/__main__.py +3 -0
  28. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/python/openapi_spec_validator_rs/handlers.py +42 -6
  29. openapi_spec_validator_rs-0.2.2/python/openapi_spec_validator_rs/readers.py +53 -0
  30. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/python/openapi_spec_validator_rs/shortcuts.py +2 -2
  31. openapi_spec_validator_rs-0.2.2/python/openapi_spec_validator_rs/validation/__init__.py +37 -0
  32. openapi_spec_validator_rs-0.2.2/python/openapi_spec_validator_rs/validation/exceptions.py +21 -0
  33. openapi_spec_validator_rs-0.2.2/python/openapi_spec_validator_rs/validation/proxies.py +23 -0
  34. openapi_spec_validator_rs-0.2.2/python/openapi_spec_validator_rs/validation/validators.py +19 -0
  35. openapi_spec_validator_rs-0.2.2/python/openapi_spec_validator_rs/versions/consts.py +11 -0
  36. openapi_spec_validator_rs-0.2.2/python/openapi_spec_validator_rs/versions/datatypes.py +5 -0
  37. openapi_spec_validator_rs-0.2.2/python/openapi_spec_validator_rs/versions/exceptions.py +5 -0
  38. openapi_spec_validator_rs-0.2.2/python/openapi_spec_validator_rs/versions/shortcuts.py +5 -0
  39. openapi_spec_validator_rs-0.2.1/crates/openapi-spec-validator-rs-py/src/stack.rs +0 -33
  40. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/LICENSE +0 -0
  41. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/NOTICE +0 -0
  42. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/Cargo.toml +0 -0
  43. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/LICENSE +0 -0
  44. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/NOTICE +0 -0
  45. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/dialects/oas3.1/metaschema.json +0 -0
  46. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/dialects/oas3.1/vocabularies/base.json +0 -0
  47. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/dialects/oas3.2/dialect/2025-09-17.json +0 -0
  48. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/dialects/oas3.2/meta/2025-09-17.json +0 -0
  49. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/metaschemas/draft-04.json +0 -0
  50. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/metaschemas/draft-07.json +0 -0
  51. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/schemas/v2.0/schema.json +0 -0
  52. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/schemas/v3.0/schema.json +0 -0
  53. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/schemas/v3.1/schema.json +0 -0
  54. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/resources/schemas/v3.2/schema.json +0 -0
  55. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/error.rs +0 -0
  56. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/loader/bigint.rs +0 -0
  57. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/loader/compose.rs +0 -0
  58. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/loader/construct.rs +0 -0
  59. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/loader/dump.rs +0 -0
  60. {openapi_spec_validator_rs-0.2.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/pyformat.rs +0 -0
  66. {openapi_spec_validator_rs-0.2.1 → 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.1 → 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.1 → 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.1 → 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.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/pyregex.rs +0 -0
  71. {openapi_spec_validator_rs-0.2.1 → 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.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs/src/pyurl.rs +0 -0
  73. {openapi_spec_validator_rs-0.2.1 → 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.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/Cargo.toml +0 -0
  75. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/LICENSE +0 -0
  76. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-cli/NOTICE +0 -0
  77. {openapi_spec_validator_rs-0.2.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → 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.1 → openapi_spec_validator_rs-0.2.2}/crates/openapi-spec-validator-rs-py/Cargo.toml +0 -0
  118. {openapi_spec_validator_rs-0.2.1 → 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.1 → openapi_spec_validator_rs-0.2.2}/pyproject.toml +0 -0
  120. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/python/openapi_spec_validator_rs/__init__.py +0 -0
  121. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/python/openapi_spec_validator_rs/_core.pyi +0 -0
  122. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/python/openapi_spec_validator_rs/exceptions.py +0 -0
  123. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/python/openapi_spec_validator_rs/proxies.py +0 -0
  124. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/python/openapi_spec_validator_rs/py.typed +0 -0
  125. {openapi_spec_validator_rs-0.2.1 → openapi_spec_validator_rs-0.2.2}/python/openapi_spec_validator_rs/validators.py +0 -0
  126. /openapi_spec_validator_rs-0.2.1/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.1"
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.1"
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.1"
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.1"
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.1", default-features = false }
18
- openapi-spec-validator-rs-cli = { path = "crates/openapi-spec-validator-rs-cli", version = "0.2.1", 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.1
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
@@ -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.1]
174
+ additional_dependencies: [openapi-spec-validator-rs==0.2.2]
169
175
  files: .*(openapi|swagger).*\.(json|yaml|yml)$
170
176
  ```
171
177
 
@@ -204,8 +210,10 @@ The validator is a Rust library (`crates/openapi-spec-validator-rs`) with a CLI
204
210
  PyO3 bindings on top. It follows the reference implementation step by step:
205
211
 
206
212
  1. **Load**: JSON goes through serde_json when libyaml would read it the same way; anything
207
- else goes through libyaml (the parser inside PyYAML) with PyYAML's YAML 1.1 rules, then
208
- 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.
209
217
  2. **Meta-schema**: the whole document is validated against the official schema for its
210
218
  version with the [`jsonschema`](https://crates.io/crates/jsonschema) crate.
211
219
  3. **Walk**: the document is walked like `keywords.py`, following `$ref`s (into other
@@ -233,4 +241,5 @@ and [NOTICE][notice].
233
241
  [compatibility]: https://github.com/Arjun-Aravind/openapi-spec-validator-rs/blob/main/COMPATIBILITY.md
234
242
  [license]: https://github.com/Arjun-Aravind/openapi-spec-validator-rs/blob/main/LICENSE
235
243
  [notice]: https://github.com/Arjun-Aravind/openapi-spec-validator-rs/blob/main/NOTICE
244
+ [unsafe-libyaml]: https://crates.io/crates/unsafe-libyaml
236
245
 
@@ -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.1]
149
+ additional_dependencies: [openapi-spec-validator-rs==0.2.2]
144
150
  files: .*(openapi|swagger).*\.(json|yaml|yml)$
145
151
  ```
146
152
 
@@ -179,8 +185,10 @@ The validator is a Rust library (`crates/openapi-spec-validator-rs`) with a CLI
179
185
  PyO3 bindings on top. It follows the reference implementation step by step:
180
186
 
181
187
  1. **Load**: JSON goes through serde_json when libyaml would read it the same way; anything
182
- else goes through libyaml (the parser inside PyYAML) with PyYAML's YAML 1.1 rules, then
183
- 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.
184
192
  2. **Meta-schema**: the whole document is validated against the official schema for its
185
193
  version with the [`jsonschema`](https://crates.io/crates/jsonschema) crate.
186
194
  3. **Walk**: the document is walked like `keywords.py`, following `$ref`s (into other
@@ -208,3 +216,4 @@ and [NOTICE][notice].
208
216
  [compatibility]: https://github.com/Arjun-Aravind/openapi-spec-validator-rs/blob/main/COMPATIBILITY.md
209
217
  [license]: https://github.com/Arjun-Aravind/openapi-spec-validator-rs/blob/main/LICENSE
210
218
  [notice]: https://github.com/Arjun-Aravind/openapi-spec-validator-rs/blob/main/NOTICE
219
+ [unsafe-libyaml]: https://crates.io/crates/unsafe-libyaml
@@ -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.1]
149
+ additional_dependencies: [openapi-spec-validator-rs==0.2.2]
144
150
  files: .*(openapi|swagger).*\.(json|yaml|yml)$
145
151
  ```
146
152
 
@@ -179,8 +185,10 @@ The validator is a Rust library (`crates/openapi-spec-validator-rs`) with a CLI
179
185
  PyO3 bindings on top. It follows the reference implementation step by step:
180
186
 
181
187
  1. **Load**: JSON goes through serde_json when libyaml would read it the same way; anything
182
- else goes through libyaml (the parser inside PyYAML) with PyYAML's YAML 1.1 rules, then
183
- 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.
184
192
  2. **Meta-schema**: the whole document is validated against the official schema for its
185
193
  version with the [`jsonschema`](https://crates.io/crates/jsonschema) crate.
186
194
  3. **Walk**: the document is walked like `keywords.py`, following `$ref`s (into other
@@ -208,3 +216,4 @@ and [NOTICE][notice].
208
216
  [compatibility]: https://github.com/Arjun-Aravind/openapi-spec-validator-rs/blob/main/COMPATIBILITY.md
209
217
  [license]: https://github.com/Arjun-Aravind/openapi-spec-validator-rs/blob/main/LICENSE
210
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;
@@ -227,7 +228,7 @@ impl<'a> Documents<'a> {
227
228
  let path = url
228
229
  .to_file_path()
229
230
  .map_err(|()| format!("not a file path: {uri}"))?;
230
- 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())?;
231
232
  // Upstream reads files through `urlopen`, which is the stream name
232
233
  // PyYAML reports.
233
234
  loader::load(&bytes, "<urllib response>").map_err(|error| error.message)
@@ -345,7 +346,7 @@ impl<'a> Documents<'a> {
345
346
  Value::Array(items) => position(segment, items.len())?
346
347
  .map(|i| &items[i])
347
348
  .ok_or_else(nowhere)?,
348
- Value::Object(map) => {
349
+ Value::Object(map) if pyrepr::foreign(contents).is_none() => {
349
350
  let key = segment.replace("~1", "/").replace("~0", "~");
350
351
  map.get(&key).ok_or_else(nowhere)?
351
352
  }
@@ -370,8 +371,8 @@ impl<'a> Documents<'a> {
370
371
 
371
372
  /// The child `key` of a mapping node, resolved (`path / key`).
372
373
  pub fn get<'d>(&'d self, node: Node<'d>, key: &str) -> Result<Option<Node<'d>>, Crash> {
373
- match node.value {
374
- Value::Object(map) => match map.get(key) {
374
+ match pyobj::as_dict(node.value) {
375
+ Some(map) => match map.get(key) {
375
376
  Some(value) => self
376
377
  .resolve(Node {
377
378
  value,
@@ -410,7 +411,7 @@ impl<'a> Documents<'a> {
410
411
  ) -> Result<Vec<(Key<'d>, Node<'d>)>, Crash> {
411
412
  let doc = node.doc;
412
413
  match node.value {
413
- Value::Object(map) => Ok(map
414
+ Value::Object(map) if pyrepr::foreign(node.value).is_none() => Ok(map
414
415
  .iter()
415
416
  .map(|(key, value)| (Key::Str(key), Node { value, doc }))
416
417
  .collect()),
@@ -433,11 +434,12 @@ pub(crate) enum Key<'d> {
433
434
 
434
435
  /// `is_ref` from `jsonschema-path`: a mapping whose `$ref` is a string.
435
436
  pub(crate) fn as_ref(value: &Value) -> Option<&str> {
436
- value.as_object()?.get("$ref")?.as_str()
437
+ pyobj::as_dict(value)?.get("$ref")?.as_str()
437
438
  }
438
439
 
439
440
  /// Python `int(s)` for base-10 literals (ASCII digits, sign, underscores, whitespace).
440
441
  fn python_int(s: &str) -> Option<i128> {
442
+ let s = pyobj::decimal_to_ascii(s)?;
441
443
  let s = s.trim();
442
444
  let (negative, digits) = match s.as_bytes().first()? {
443
445
  b'-' => (true, &s[1..]),
@@ -594,5 +596,10 @@ mod tests {
594
596
  assert_eq!(python_int("1_0"), Some(10));
595
597
  assert_eq!(python_int("1__0"), None);
596
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);
597
604
  }
598
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.
@@ -45,23 +45,26 @@ pub(super) enum Source<'a> {
45
45
  }
46
46
 
47
47
  pub(super) struct Parser<'a> {
48
- // Boxed so the self-referential pointers libyaml keeps (read handler data, buffers) stay valid.
49
- raw: Box<unsafe_yaml::yaml_parser_t>,
50
- // Read through the raw pointer registered with libyaml; owned here so it outlives the parser.
51
- feed: Option<Box<TextFeed<'a>>>,
48
+ // Heap-allocated and only ever touched through this raw pointer: libyaml's string reader
49
+ // keeps a pointer to the parser inside the parser itself (`read_handler_data`), which a
50
+ // `Box` or `&mut` reborrow would invalidate under Rust's aliasing rules. Freed in `drop`.
51
+ raw: *mut unsafe_yaml::yaml_parser_t,
52
+ // The read handler's data, registered with libyaml and read through that pointer only;
53
+ // owned here so it outlives the parser. Freed in `drop`.
54
+ feed: Option<*mut TextFeed<'a>>,
52
55
  stream_name: &'a str,
53
56
  _input: PhantomData<&'a [u8]>,
54
57
  }
55
58
 
56
59
  impl<'a> Parser<'a> {
57
60
  pub fn new(source: Source<'a>, stream_name: &'a str) -> Self {
58
- let mut uninit = Box::<unsafe_yaml::yaml_parser_t>::new_uninit();
61
+ let raw = Box::into_raw(Box::<unsafe_yaml::yaml_parser_t>::new_uninit())
62
+ .cast::<unsafe_yaml::yaml_parser_t>();
59
63
  // SAFETY: `yaml_parser_initialize` zeroes the whole struct and allocates its buffers,
60
64
  // leaving it fully initialised. With Rust's allocator it only fails by aborting.
61
- let mut raw = unsafe {
62
- let _ = unsafe_yaml::yaml_parser_initialize(uninit.as_mut_ptr());
63
- uninit.assume_init()
64
- };
65
+ unsafe {
66
+ let _ = unsafe_yaml::yaml_parser_initialize(raw);
67
+ }
65
68
  let mut feed = None;
66
69
  match source {
67
70
  Source::Bytes(bytes) => {
@@ -69,25 +72,20 @@ impl<'a> Parser<'a> {
69
72
  // parser (lifetime `'a`), and libyaml only reads `bytes.len()` bytes from it.
70
73
  unsafe {
71
74
  unsafe_yaml::yaml_parser_set_input_string(
72
- &mut *raw,
75
+ raw,
73
76
  bytes.as_ptr(),
74
77
  bytes.len() as u64,
75
78
  );
76
79
  }
77
80
  }
78
81
  Source::Text(text) => {
79
- let mut boxed = Box::new(text);
80
- let data: *mut TextFeed<'a> = &mut *boxed;
81
- // SAFETY: the feed is heap-allocated and owned by `self`, so `data` stays valid
82
- // for as long as libyaml can call `read_handler`.
82
+ let data = Box::into_raw(Box::new(text));
83
+ // SAFETY: the feed is heap-allocated and owned by `self` (freed in `drop`), so
84
+ // `data` stays valid for as long as libyaml can call `read_handler`.
83
85
  unsafe {
84
- unsafe_yaml::yaml_parser_set_input(
85
- &mut *raw,
86
- read_handler,
87
- data.cast::<c_void>(),
88
- );
86
+ unsafe_yaml::yaml_parser_set_input(raw, read_handler, data.cast::<c_void>());
89
87
  }
90
- feed = Some(boxed);
88
+ feed = Some(data);
91
89
  }
92
90
  }
93
91
  Self {
@@ -103,8 +101,7 @@ impl<'a> Parser<'a> {
103
101
  let mut event = MaybeUninit::<unsafe_yaml::yaml_event_t>::uninit();
104
102
  // SAFETY: the parser is initialised with an input; `yaml_parser_parse` zero-fills the
105
103
  // event before doing anything else, so it is initialised whatever the outcome.
106
- let failed =
107
- unsafe { unsafe_yaml::yaml_parser_parse(&mut *self.raw, event.as_mut_ptr()).fail };
104
+ let failed = unsafe { unsafe_yaml::yaml_parser_parse(self.raw, event.as_mut_ptr()).fail };
108
105
  if failed {
109
106
  return Err(self.error());
110
107
  }
@@ -123,11 +120,13 @@ impl<'a> Parser<'a> {
123
120
  }
124
121
 
125
122
  fn error(&self) -> String {
126
- if let Some(message) = self.feed.as_ref().and_then(|f| f.failure.clone()) {
123
+ // SAFETY: the feed is live until `drop`, and libyaml is not running.
124
+ if let Some(message) = self.feed.and_then(|f| unsafe { (*f).failure.clone() }) {
127
125
  // The Python exception raised inside the stream's read() propagates unchanged.
128
126
  return message;
129
127
  }
130
- let p: &unsafe_yaml::yaml_parser_t = &self.raw;
128
+ // SAFETY: the parser is live until `drop`, and libyaml is not running.
129
+ let p: &unsafe_yaml::yaml_parser_t = unsafe { &*self.raw };
131
130
  // SAFETY: libyaml sets `problem`/`context` to static NUL-terminated strings or null.
132
131
  let problem = unsafe { c_str(p.problem) }.map(|problem| {
133
132
  // unsafe-libyaml fixed a typo that libyaml 0.2.5 (bundled with PyYAML) still prints.
@@ -165,8 +164,16 @@ impl<'a> Parser<'a> {
165
164
 
166
165
  impl Drop for Parser<'_> {
167
166
  fn drop(&mut self) {
168
- // SAFETY: the parser was initialised in `new` and is deleted exactly once.
169
- unsafe { unsafe_yaml::yaml_parser_delete(&mut *self.raw) };
167
+ // SAFETY: the parser was initialised in `new` and is deleted exactly once; the parser
168
+ // and the feed were allocated with `Box::into_raw` in `new` and are freed exactly once,
169
+ // after libyaml can no longer reach them.
170
+ unsafe {
171
+ unsafe_yaml::yaml_parser_delete(self.raw);
172
+ drop(Box::from_raw(self.raw));
173
+ if let Some(feed) = self.feed {
174
+ drop(Box::from_raw(feed));
175
+ }
176
+ }
170
177
  }
171
178
  }
172
179
 
@@ -5,6 +5,7 @@
5
5
 
6
6
  use super::bigint::BigInt;
7
7
  use super::pyfmt::{lower, repr_str, repr_str_200, strip, unicode_encode_error};
8
+ use crate::pyobj::decimal_to_ascii;
8
9
 
9
10
  /// `sys.get_int_max_str_digits()` default.
10
11
  const MAX_STR_DIGITS: usize = 4300;
@@ -73,7 +74,8 @@ pub(super) fn py_int(s: &str, base: u32) -> Result<BigInt, String> {
73
74
  repr_str_200(s)
74
75
  )
75
76
  };
76
- let t = strip(s);
77
+ let ascii = decimal_to_ascii(s).ok_or_else(invalid)?;
78
+ let t = strip(&ascii);
77
79
  let (negative, t) = match t.as_bytes().first() {
78
80
  Some(b'-') => (true, &t[1..]),
79
81
  Some(b'+') => (false, &t[1..]),
@@ -147,9 +149,11 @@ pub(super) fn float_value(s: &str) -> Result<FloatValue, String> {
147
149
  Ok(FloatValue::Num(sign * py_float(value)?))
148
150
  }
149
151
 
150
- /// Python's `float(s)` for ASCII input.
152
+ /// Python's `float(s)`.
151
153
  pub(super) fn py_float(s: &str) -> Result<f64, String> {
152
- let t = strip(s);
154
+ let invalid = || format!("could not convert string to float: {}", repr_str(s));
155
+ let ascii = decimal_to_ascii(s).ok_or_else(invalid)?;
156
+ let t = strip(&ascii);
153
157
  // Rust's grammar matches Python's for these strings: optional sign, digits with an optional
154
158
  // point and exponent, or inf/infinity/nan in any case. Underscores are already gone.
155
159
  let plausible = !t.is_empty()
@@ -158,7 +162,7 @@ pub(super) fn py_float(s: &str) -> Result<f64, String> {
158
162
  plausible
159
163
  .then(|| t.parse::<f64>().ok())
160
164
  .flatten()
161
- .ok_or_else(|| format!("could not convert string to float: {}", repr_str(s)))
165
+ .ok_or_else(invalid)
162
166
  }
163
167
 
164
168
  /// `construct_yaml_binary`'s checks; the bytes themselves are never needed because