dry-validation-rust 0.1.0.pre5

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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +74 -0
  3. data/LICENSE +21 -0
  4. data/NOTICE.md +28 -0
  5. data/README.md +459 -0
  6. data/docs/ARCHITECTURE.md +256 -0
  7. data/docs/COMPATIBILITY.md +198 -0
  8. data/docs/FEASIBILITY.md +207 -0
  9. data/docs/SUPPORT_MATRIX.md +66 -0
  10. data/docs/VERIFICATION.md +128 -0
  11. data/dry-validation-rust.gemspec +58 -0
  12. data/ext/dry_validation_rust/Cargo.lock +809 -0
  13. data/ext/dry_validation_rust/Cargo.toml +44 -0
  14. data/ext/dry_validation_rust/benches/coercion.rs +77 -0
  15. data/ext/dry_validation_rust/benches/full_schema.rs +189 -0
  16. data/ext/dry_validation_rust/benches/plan_compile.rs +37 -0
  17. data/ext/dry_validation_rust/benches/predicates.rs +105 -0
  18. data/ext/dry_validation_rust/extconf.rb +29 -0
  19. data/ext/dry_validation_rust/src/coercion.rs +515 -0
  20. data/ext/dry_validation_rust/src/engine.rs +416 -0
  21. data/ext/dry_validation_rust/src/error.rs +82 -0
  22. data/ext/dry_validation_rust/src/extract_primitive.rs +23 -0
  23. data/ext/dry_validation_rust/src/generated_predicates.rs +33 -0
  24. data/ext/dry_validation_rust/src/lib.rs +228 -0
  25. data/ext/dry_validation_rust/src/plan.rs +611 -0
  26. data/ext/dry_validation_rust/src/predicates.rs +449 -0
  27. data/ext/dry_validation_rust/src/ruby_bridge.rs +78 -0
  28. data/lib/dry/schema.rb +6 -0
  29. data/lib/dry/validation/rust/block_keyword_parameters.rb +20 -0
  30. data/lib/dry/validation/rust/config.rb +74 -0
  31. data/lib/dry/validation/rust/contract/result.rb +180 -0
  32. data/lib/dry/validation/rust/contract/values.rb +73 -0
  33. data/lib/dry/validation/rust/contract.rb +400 -0
  34. data/lib/dry/validation/rust/errors.rb +14 -0
  35. data/lib/dry/validation/rust/evaluator.rb +295 -0
  36. data/lib/dry/validation/rust/failures.rb +57 -0
  37. data/lib/dry/validation/rust/generated_predicates.rb +14 -0
  38. data/lib/dry/validation/rust/macros.rb +45 -0
  39. data/lib/dry/validation/rust/message.rb +41 -0
  40. data/lib/dry/validation/rust/message_backend.rb +115 -0
  41. data/lib/dry/validation/rust/message_set.rb +159 -0
  42. data/lib/dry/validation/rust/native.rb +25 -0
  43. data/lib/dry/validation/rust/path.rb +65 -0
  44. data/lib/dry/validation/rust/path_trie.rb +57 -0
  45. data/lib/dry/validation/rust/result.rb +3 -0
  46. data/lib/dry/validation/rust/rule.rb +62 -0
  47. data/lib/dry/validation/rust/schema/dsl.rb +76 -0
  48. data/lib/dry/validation/rust/schema/field_builder.rb +156 -0
  49. data/lib/dry/validation/rust/schema/field_definition.rb +99 -0
  50. data/lib/dry/validation/rust/schema/predicate_block.rb +56 -0
  51. data/lib/dry/validation/rust/schema/processor_hooks.rb +46 -0
  52. data/lib/dry/validation/rust/schema/result.rb +67 -0
  53. data/lib/dry/validation/rust/schema/ruby_type_processor.rb +44 -0
  54. data/lib/dry/validation/rust/schema.rb +323 -0
  55. data/lib/dry/validation/rust/values.rb +3 -0
  56. data/lib/dry/validation/rust/version.rb +10 -0
  57. data/lib/dry/validation/rust.rb +55 -0
  58. data/lib/dry/validation.rb +66 -0
  59. data/lib/dry-schema.rb +3 -0
  60. data/lib/dry-validation.rb +3 -0
  61. data/lib/dry_validation_rust.rb +3 -0
  62. data/predicates.yml +67 -0
  63. data/rust-toolchain.toml +9 -0
  64. metadata +260 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a457918e553c82967fd1cbaaf2af8c3e30fc7304d0e127947da63d20550af8eb
4
+ data.tar.gz: 5a396ce51b3d66424c2176118588391fd9e6c5d5888d478cfdf17e7ef6aa5949
5
+ SHA512:
6
+ metadata.gz: 63c703d19a65753e820e7f498fbcf230b193391a10649a1858d8184855d6f5aa8ec5380810ec644c89e05af1eec215cf6dd6ea27976d8ceeaf4529fa1538fb11
7
+ data.tar.gz: '03582b1fb5b5dc11d658e3c8b523ce44e2dd2c79d83a99ec71bcb7fa495b96e02f684a8f8f7c52893c68e9f2491796fe67a93c968985ccb77c7aaaa8b22c7c59'
data/CHANGELOG.md ADDED
@@ -0,0 +1,74 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ - Added pluggable schema message backends through custom `MessageBackend`
6
+ subclasses.
7
+ - Changed the native engine boundary to return a typed `SchemaResult` with
8
+ `#output` and `#errors` accessors.
9
+ - Fixed native predicate evaluation to propagate exceptions raised by Ruby
10
+ predicate methods instead of returning validation failures.
11
+
12
+ ## 0.1.0.pre3 — 2026-08-09
13
+
14
+ - Added `config.validate_keys = true` for `params` and `json` schemas to
15
+ report undeclared keys, including in nested hashes.
16
+ - Added supported predicate-composition blocks to schema value declarations.
17
+ - Added `:value_coercer` schema processor hooks that run before and after
18
+ native schema evaluation.
19
+ - Added direct-field support for custom `dry-types` objects and constructors.
20
+ - Added configurable YAML message templates and an optional I18n message
21
+ backend for schema errors.
22
+ - Added source-gem package auditing with an isolated installation smoke test.
23
+ - Documented the coordinated vulnerability-disclosure process, including a
24
+ 90-day post-fix-release embargo and the weekly dependency-audit schedule.
25
+
26
+ ## 0.1.0.pre2 — 2026-08-03
27
+
28
+ - Added typed native predicate arguments; invalid null and object arguments are
29
+ rejected when compiling a schema plan.
30
+ - Added explicit depth limits for native schema plans and nested schema
31
+ traversal.
32
+ - Changed `MessageSet#messages` to return a read-only view; use `#add` to add
33
+ messages to a mutable message set.
34
+ - Relaxed the native extension's Magnus dependency to compatible `0.8.x`
35
+ patch releases.
36
+ - Added coercion boundary and concurrent contract-call coverage.
37
+ - Added Rust package-manifest verification to CI.
38
+ - Added product-scope documentation, including the support matrix and pinned
39
+ upstream compatibility references.
40
+ - Added canonical verification and benchmark-smoke scripts.
41
+ - Added fixture-backed baseline behavior tests and verification documentation.
42
+ - Excluded local Cargo target output from source gem file selection.
43
+ - Added public RubyGems metadata and a package audit task for source-gem
44
+ contents, artifact rejection, isolated install, and safe-entrypoint smoke
45
+ verification.
46
+ - Added GitHub Actions workflows for Ruby/Rust CI, compatibility preflight,
47
+ security audit, package audit, and scheduled fuzz preflight.
48
+ - Added Dependabot configuration, dependency audit policy, and dependency
49
+ version capture in canonical verification logs.
50
+ - Added contribution, conduct, security, support, and governance policies plus
51
+ structured issue forms and a pull request template.
52
+ - Added project-management policy, roadmap-to-milestone mapping, issue-quality
53
+ form, canonical labels, work-in-progress limits, and an idempotent GitHub API
54
+ synchronizer with dry-run defaults.
55
+ - Replaced the extensive stage catalog with a compact outcome-oriented roadmap
56
+ and aligned project synchronization, issue milestones, and workflow references.
57
+ - Removed prose-only documentation/community/project-policy tests and made
58
+ remaining configuration tests protect executable, package, or security
59
+ behavior without freezing exact roadmap counts.
60
+
61
+ ## 0.1.0.pre1 — 2026-07-12
62
+
63
+ - Added a Magnus/rb-sys native extension with immutable compiled schema plans.
64
+ - Added `params`, `json`, and non-coercing `schema` modes.
65
+ - Added required, optional, filled, maybe, hash, array, primitive member,
66
+ nested member, coercion, type check, and common predicate support.
67
+ - Added ordered Ruby contract rules, multi-key rules, nested paths,
68
+ `rule.each`, key/base failures, context, options, and macros.
69
+ - Added result, values, message, message-set, pattern-matching, inheritance,
70
+ external schema reuse, and safe/exact entrypoints.
71
+ - Added compatibility, architecture, feasibility, testing, and benchmark
72
+ documentation.
73
+
74
+ This is an experiment, not a production-compatible release.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dry-validation-rust contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/NOTICE.md ADDED
@@ -0,0 +1,28 @@
1
+ # Notices
2
+
3
+ `dry-validation-rust` is an independent experimental compatibility project.
4
+ It is not an official dry-rb or Hanakai project and is not endorsed by those
5
+ maintainers.
6
+
7
+ The public interfaces, documentation, tests, dependency metadata, and
8
+ architecture of these MIT-licensed projects were studied:
9
+
10
+ - dry-validation, copyright 2015–2026 Hanakai team:
11
+ https://github.com/dry-rb/dry-validation
12
+ - dry-schema, copyright its contributors:
13
+ https://github.com/dry-rb/dry-schema
14
+ - dry-logic and dry-types:
15
+ https://github.com/dry-rb
16
+
17
+ No upstream implementation source is copied into this project. The compatible
18
+ surface was independently implemented. If future development incorporates
19
+ upstream source or substantial portions of it, the corresponding MIT notices
20
+ must be preserved.
21
+
22
+ The native extension uses:
23
+
24
+ - Magnus (MIT): https://github.com/matsadler/magnus
25
+ - rb-sys (MIT OR Apache-2.0): https://github.com/oxidize-rb/rb-sys
26
+ - serde and serde_json (MIT OR Apache-2.0)
27
+
28
+ Their complete license texts are distributed by their respective packages.
data/README.md ADDED
@@ -0,0 +1,459 @@
1
+ # dry-validation-rust
2
+
3
+ `dry-validation-rust` is a performance-oriented hybrid Ruby/Rust validation
4
+ engine with familiar dry-validation-style contract syntax and a precisely
5
+ documented compatible subset. Rust handles the immutable declarative schema
6
+ execution path; Ruby preserves dynamic rules and Ruby-specific semantics.
7
+
8
+ > Status: `0.1.0.pre5` alpha pre-release. The side-by-side API has a defined
9
+ > `0.1.x` compatibility promise and is covered by focused tests, differential
10
+ > checks, package verification, and reproducible benchmark evidence. It is not
11
+ > a full, production-ready drop-in replacement for upstream `dry-validation`.
12
+
13
+ Before adoption, review [the support matrix](docs/SUPPORT_MATRIX.md),
14
+ [compatibility matrix](docs/COMPATIBILITY.md), and
15
+ [verification evidence](docs/VERIFICATION.md) for the exact supported surface,
16
+ platforms, and known boundaries.
17
+
18
+ For project participation and reporting routes, see
19
+ [CONTRIBUTING.md](CONTRIBUTING.md), [SUPPORT.md](SUPPORT.md),
20
+ [SECURITY.md](SECURITY.md), [GOVERNANCE.md](GOVERNANCE.md), and the
21
+ [Code of Conduct](CODE_OF_CONDUCT.md).
22
+ See the concise [roadmap](docs/ROADMAP.md) for planned outcomes and
23
+ [project-management policy](docs/PROJECT_MANAGEMENT.md) for issue workflow.
24
+
25
+ ## What this project is
26
+
27
+ - Rust owns the immutable schema plan, key lookup and normalization, nested
28
+ traversal, built-in coercion, type checks, native predicates, output
29
+ filtering, and structural error collection.
30
+ - Ruby owns class-level DSL capture, arbitrary rule blocks, injected Ruby
31
+ objects, macros, custom behavior, and Ruby-specific predicate semantics.
32
+
33
+ Rewriting arbitrary Ruby blocks into Rust is neither generally possible nor
34
+ desirable. Calling those blocks through Ruby preserves the feature that makes
35
+ `dry-validation` useful: domain validation can be normal Ruby.
36
+
37
+ ## What this project is not
38
+
39
+ - It is not a proven drop-in replacement for upstream `dry-validation`.
40
+ - It is not a full Rust rewrite of the dry-rb validation stack.
41
+ - It does not claim full upstream compatibility without fixture-backed,
42
+ version-pinned differential evidence.
43
+ - It does not claim general speedups without representative benchmarks.
44
+
45
+ ## Installation
46
+
47
+ ### Precompiled (recommended)
48
+
49
+ When a precompiled gem is published for your platform, install it with:
50
+
51
+ ```bash
52
+ gem install dry-validation-rust
53
+ ```
54
+
55
+ The current `0.1.x` support target is source builds; see the
56
+ [support matrix](docs/SUPPORT_MATRIX.md) for the authoritative platform status.
57
+
58
+ ### From source
59
+
60
+ Requires Rust 1.75 or newer, libclang, and a C toolchain. The MSRV is Rust
61
+ 1.75 and is tested in CI. A source checkout pins Rust 1.75.0 automatically
62
+ through `rust-toolchain.toml`.
63
+
64
+ ```bash
65
+ gem install dry-validation-rust --platform ruby
66
+ ```
67
+
68
+ ## Primary safe API
69
+
70
+ Use the side-by-side namespace first:
71
+
72
+ ```ruby
73
+ require "dry/validation/rust"
74
+
75
+ class NewUserContract < Dry::Validation::Rust::Contract
76
+ params do
77
+ required(:email).filled(:string, format?: /\A[^@]+@[^@]+\z/)
78
+ required(:age).value(:integer)
79
+ optional(:display_name).maybe(:string)
80
+
81
+ required(:addresses).array(:hash) do
82
+ required(:city).filled(:string)
83
+ required(:postcode).filled(:string)
84
+ end
85
+ end
86
+
87
+ rule(:age) do
88
+ key.failure("must be at least 18") if value < 18
89
+ end
90
+ end
91
+
92
+ result = NewUserContract.new.call(
93
+ "email" => "jane@example.org",
94
+ "age" => "17",
95
+ "display_name" => "",
96
+ "addresses" => [{"city" => "Astana", "postcode" => "010000"}]
97
+ )
98
+
99
+ result.to_h
100
+ result.success?
101
+ result.errors.to_h
102
+ ```
103
+
104
+ This is the primary supported API. Version, platform, and upstream-reference
105
+ targets are listed in [SUPPORT_MATRIX.md](docs/SUPPORT_MATRIX.md). Supported
106
+ DSL and semantic differences are listed in [COMPATIBILITY.md](docs/COMPATIBILITY.md).
107
+
108
+ ### Side-by-side API stability
109
+
110
+ For the `0.1.x` line, the public side-by-side API is
111
+ `Dry::Validation::Rust::Contract`, its nested `Result` and `Values` types, and
112
+ the directly exposed `Schema`, `MessageSet`, and `Evaluator` types. Their
113
+ documented public methods will not be removed or changed incompatibly in a patch
114
+ release; a breaking side-by-side API change requires the next minor release. The
115
+ exact-compatibility entrypoints are explicitly experimental and are not covered
116
+ by this promise.
117
+
118
+ ## Migration-compatible subset
119
+
120
+ The safe API intentionally keeps familiar contract syntax where that behavior
121
+ is implemented and covered. Use it for comparison work and gradual migration
122
+ without taking over upstream constants:
123
+
124
+ ```ruby
125
+ require "dry/validation/rust"
126
+
127
+ class AgeContract < Dry::Validation::Rust::Contract
128
+ params do
129
+ required(:age).value(:integer)
130
+ end
131
+ end
132
+ ```
133
+
134
+ ## Exact compatibility shim
135
+
136
+ Exact compatibility mode keeps upstream-like require paths and constants:
137
+
138
+ ```ruby
139
+ require "dry/validation"
140
+
141
+ class AgeContract < Dry::Validation::Contract
142
+ params do
143
+ required(:age).value(:integer)
144
+ end
145
+ end
146
+ ```
147
+
148
+ > Collision warning: exact compatibility mode is experimental and opt-in. Do
149
+ > not install or activate upstream `dry-validation` / `dry-schema` in the same
150
+ > process when using `require "dry/validation"` or `require "dry/schema"` from
151
+ > this gem. Both implementations own the same require paths and constants. This
152
+ > gem raises a clear `LoadError` when it can detect such a collision.
153
+
154
+ The exact shim currently lives in this gem. If maintaining the shim separately
155
+ becomes necessary, the intended product split is `dry-validation-rust` for the
156
+ safe namespace and `dry-validation-rust-compat` for the upstream-like require
157
+ paths. No split is planned for the `0.1.x` line without concrete maintenance
158
+ evidence.
159
+
160
+ ## Loading modes
161
+
162
+ ### Side-by-side mode
163
+
164
+ `require "dry/validation/rust"` exposes only the
165
+ `Dry::Validation::Rust` namespace. It does not define
166
+ `Dry::Validation::Contract` or `Dry::Schema`.
167
+
168
+ ### Exact compatibility mode
169
+
170
+ `require "dry/validation"` defines:
171
+
172
+ - `Dry::Validation::Contract` and related result/message aliases;
173
+ - minimal `Dry::Schema.Params`, `Dry::Schema.JSON`, and
174
+ `Dry::Schema.define` factories for reusable schemas.
175
+
176
+ The collision warning above applies to every exact-mode entrypoint.
177
+
178
+ ## Supported highlights
179
+
180
+ This section is a summary. Version and platform support is authoritative in
181
+ [SUPPORT_MATRIX.md](docs/SUPPORT_MATRIX.md); feature support is authoritative
182
+ in [COMPATIBILITY.md](docs/COMPATIBILITY.md).
183
+
184
+ - `params`, `json`, and plain `schema` modes.
185
+ - String-key normalization for Params and JSON.
186
+ - Integer, float, decimal, boolean, symbol, Date, DateTime, and Time Params
187
+ coercions.
188
+ - Required/optional keys, `filled`, `maybe`, hashes, arrays, primitive array
189
+ members, and arrays of nested hashes.
190
+ - Numeric and size predicates in Rust; format, inclusion, exclusion, and Ruby
191
+ equality predicates in Ruby for semantic fidelity.
192
+ - Ordered rules that run only when their schema dependencies succeeded.
193
+ - Symbol, dot-string, array, and simple hash rule paths.
194
+ - `value`, `values`, `key?`, `key.failure`, `key(path).failure`,
195
+ `base.failure`, `schema_error?`, `rule_error?`, and
196
+ `base_rule_error?`.
197
+ - `rule.each` with `index:`.
198
+ - Global and class macros, macro arguments, injected `option` values, and
199
+ mutable per-call context.
200
+ - Result hashes, message sets, metadata, full messages, filtering, and Ruby
201
+ pattern matching.
202
+ - Contract inheritance and compatible schema reuse.
203
+
204
+ The complete exclusions and semantic differences are explicit in
205
+ [COMPATIBILITY.md](docs/COMPATIBILITY.md).
206
+
207
+ ## Building from source
208
+
209
+ Requirements:
210
+
211
+ - Ruby 3.3 or newer with development headers;
212
+ - Rust 1.75 or newer and Cargo (the MSRV, tested in CI);
213
+ - a C toolchain;
214
+ - libclang where the selected `rb-sys` build uses bindgen.
215
+
216
+ Then:
217
+
218
+ ```bash
219
+ bundle install
220
+ bundle exec rake compile
221
+ bundle exec rake test
222
+ ```
223
+
224
+ The source gem declares `rb_sys ~> 0.9` and builds through the ordinary Ruby
225
+ native-extension lifecycle.
226
+
227
+ ## Verification
228
+
229
+ Current release evidence was collected with:
230
+
231
+ - CRuby 3.3.7;
232
+ - Rust 1.97.0;
233
+ - Magnus 0.8.2;
234
+ - rb-sys 0.9.128;
235
+ - an optimized release profile.
236
+
237
+ The test suite covers the native plan, coercion modes, nested data, rules,
238
+ rule skipping, array rules, macros, options, context, inheritance, external
239
+ schemas, loading modes, pattern matching, metadata, concurrent calls, malformed
240
+ input resilience, package contents, and differential compatibility fixtures.
241
+
242
+ Run the representative benchmark matrix with:
243
+
244
+ ```bash
245
+ ruby -Ilib benchmark/schema_throughput.rb
246
+ N=500000 ruby -Ilib benchmark/schema_throughput.rb
247
+ ENGINE=rust ruby -Ilib benchmark/schema_throughput.rb
248
+ ENGINE=upstream ruby -Ilib benchmark/schema_throughput.rb
249
+ SCENARIO=array_of_objects ENGINE=rust ruby -Ilib benchmark/schema_throughput.rb
250
+ VALIDATE_KEYS=true SCENARIO=large_form ENGINE=rust ruby -Ilib benchmark/schema_throughput.rb
251
+ ```
252
+
253
+ The six fixed scenarios cover small (5-field), medium (25-field, 80% valid),
254
+ and large (100-field, 50% valid) forms; a 10-level nested object; 100 objects
255
+ with five fields each (90% valid); and a 20-field all-invalid case. Each result
256
+ includes validations/second, sampled p50/p95/p99 latency, Ruby allocations per
257
+ call, and peak process RSS under the sustained run. Use `FORMAT=json` for
258
+ machine-readable output, and tune `WARMUP`, `N`, and `LATENCY_SAMPLES` when
259
+ collecting evidence.
260
+
261
+ Refresh the allocation-regression baseline only after intentionally reviewing an
262
+ allocation change:
263
+
264
+ ```bash
265
+ bundle exec script/record-allocation-baseline
266
+ ```
267
+
268
+ The manual **Record Allocation Baseline** workflow produces the same JSON as an
269
+ artifact without changing the repository. Review its value before replacing
270
+ `benchmark/baseline_allocations.json`; do not accept an allocation regression
271
+ merely by refreshing the baseline.
272
+
273
+ By default the benchmark compares this Rust-backed hybrid implementation with
274
+ the upstream `dry-validation` gem in a separate Ruby process. The upstream gem
275
+ is intentionally not a project dependency; install it for the same Ruby with
276
+ `gem install dry-validation` before running `ENGINE=all` or `ENGINE=upstream`.
277
+ The matrix is a reproducible measurement harness, not a published performance
278
+ claim: compare repeated runs on the same machine and report neutral or negative
279
+ results alongside favorable ones.
280
+
281
+ ### Plan-compilation benchmark
282
+
283
+ Measure native JSON plan deserialization independently of validation calls:
284
+
285
+ ```bash
286
+ cargo bench --locked --manifest-path ext/dry_validation_rust/Cargo.toml --bench plan_compile
287
+ ```
288
+
289
+ CI runs this non-blocking benchmark on Ubuntu and retains the combined
290
+ Criterion reports as the `native-benchmarks` artifact for 30 days. On
291
+ 2026-08-14, the
292
+ following 100-sample Criterion results were measured locally on x86_64 Linux
293
+ (kernel 7.0.0-29-generic, AMD Ryzen 7 5800H) with Rust 1.90.0. Each generated
294
+ Params-mode plan has `validate_keys` enabled; every field is a required string
295
+ with one `min_size(1)` predicate. These figures are local baseline evidence,
296
+ not a cross-host performance guarantee.
297
+
298
+ | Plan size | Criterion estimate (95% confidence interval) | Point estimate |
299
+ | ---------- | -------------------------------------------: | -------------: |
300
+ | 5 fields | 1.8861–1.8954 µs | 1.8905 µs |
301
+ | 50 fields | 20.520–20.686 µs | 20.604 µs |
302
+ | 200 fields | 83.591–87.909 µs | 85.538 µs |
303
+
304
+ ### Coercion benchmark
305
+
306
+ Measure the native Params-mode coercion path independently for common and
307
+ Ruby-fallback literals:
308
+
309
+ ```bash
310
+ cargo bench --locked --manifest-path ext/dry_validation_rust/Cargo.toml --bench coercion
311
+ ```
312
+
313
+ CI runs the plan-compilation and coercion benchmarks non-blockingly on Ubuntu
314
+ and retains their combined Criterion reports as the `native-benchmarks`
315
+ artifact for 30 days.
316
+
317
+ The following coercion results were measured locally on 2026-08-14 with CRuby
318
+ 3.4.4, Rust 1.90.0, and `dry-validation-rust` 0.1.0.pre4 on x86_64 Linux
319
+ (kernel 7.0.0-29-generic, AMD Ryzen 7 5800H). Criterion used 100 samples with
320
+ a 500 ms warm-up and 1 s measurement period per case. These are host-local
321
+ baseline observations, not cross-host performance guarantees. `Infinity` exercises the intentional non-finite-float rejection path; the datetime-shaped date literal exercises the Ruby fallback path.
322
+
323
+ | Group | Input | Criterion estimate (95% confidence interval) | Point estimate |
324
+ | ----------------- | ---------------------- | -------------------------------------------: | -------------: |
325
+ | Integer | `42` | 119.60–126.54 ns | 122.99 ns |
326
+ | Integer | `-99` | 116.84–120.98 ns | 118.79 ns |
327
+ | Integer | `1_000` | 129.17–135.89 ns | 132.29 ns |
328
+ | Integer | `0xFF` | 119.38–124.78 ns | 121.89 ns |
329
+ | Float | `3.14` | 131.74–133.14 ns | 132.40 ns |
330
+ | Float | `-2.5e10` | 180.62–181.84 ns | 181.17 ns |
331
+ | Float (rejection) | `Infinity` | 83.982–84.862 ns | 84.402 ns |
332
+ | Boolean | `true` | 80.455–83.671 ns | 81.937 ns |
333
+ | Boolean | `false` | 81.399–86.340 ns | 83.778 ns |
334
+ | Boolean | `1` | 123.77–132.78 ns | 128.38 ns |
335
+ | Boolean | `0` | 132.99–153.74 ns | 143.02 ns |
336
+ | Boolean | `yes` | 99.111–102.16 ns | 100.53 ns |
337
+ | Boolean | `no` | 99.193–100.15 ns | 99.620 ns |
338
+ | Date | `2024-01-01` | 573.70–596.35 ns | 584.53 ns |
339
+ | Date (fallback) | `2024-01-01T12:00:00Z` | 2.7212–3.1991 µs | 2.9518 µs |
340
+ | Decimal | `123.456` | 746.42–781.89 ns | 761.67 ns |
341
+ | Decimal | `0.0000001` | 726.38–738.29 ns | 731.78 ns |
342
+
343
+ ### Predicate benchmark
344
+
345
+ Measure the native comparison, size, and parity predicate paths with Ruby
346
+ values created once before timing:
347
+
348
+ ```bash
349
+ cargo bench --locked --manifest-path ext/dry_validation_rust/Cargo.toml --bench predicates
350
+ ```
351
+
352
+ CI runs the plan-compilation, coercion, and predicate benchmarks non-blockingly
353
+ on Ubuntu and retains their combined Criterion reports as the
354
+ `native-benchmarks` artifact for 30 days.
355
+
356
+ The following results were measured locally on 2026-08-15 with CRuby 3.4.4,
357
+ Rust 1.90.0, and `dry-validation-rust` 0.1.0.pre4 on x86_64 Linux (kernel
358
+ 7.0.0-29-generic, AMD Ryzen 7 5800H). Criterion used 100 samples with a
359
+ 500 ms warm-up and 1 s measurement period per case. Every input passes its
360
+ predicate; values and predicate plans are prepared before the timed loop.
361
+ These are host-local baseline observations, not cross-host performance
362
+ guarantees.
363
+
364
+ | Group | Case | Criterion estimate (95% confidence interval) | Point estimate |
365
+ | ---------- | ----------------- | -------------------------------------------: | -------------: |
366
+ | Comparison | `gt` integer | 8.5136–8.5606 ns | 8.5340 ns |
367
+ | Comparison | `gteq` integer | 8.6687–8.9753 ns | 8.7905 ns |
368
+ | Comparison | `lt` integer | 9.0641–9.5832 ns | 9.3130 ns |
369
+ | Comparison | `lteq` integer | 8.5890–8.6289 ns | 8.6062 ns |
370
+ | Comparison | `gt` float | 7.8649–7.9178 ns | 7.8890 ns |
371
+ | Comparison | `gteq` float | 7.8643–7.8929 ns | 7.8779 ns |
372
+ | Comparison | `lt` float | 7.8780–7.9643 ns | 7.9147 ns |
373
+ | Comparison | `lteq` float | 7.8838–8.6503 ns | 8.2060 ns |
374
+ | Size | `size` string | 33.124–33.230 ns | 33.178 ns |
375
+ | Size | `min_size` string | 35.507–35.567 ns | 35.536 ns |
376
+ | Size | `max_size` string | 32.986–33.263 ns | 33.111 ns |
377
+ | Size | `size` array | 10.187–10.252 ns | 10.216 ns |
378
+ | Size | `min_size` array | 10.253–10.457 ns | 10.338 ns |
379
+ | Size | `max_size` array | 10.160–10.352 ns | 10.228 ns |
380
+ | Size | `size` hash | 10.763–10.849 ns | 10.799 ns |
381
+ | Size | `min_size` hash | 11.384–11.968 ns | 11.639 ns |
382
+ | Size | `max_size` hash | 11.270–11.804 ns | 11.496 ns |
383
+ | Parity | `odd?` integer | 7.8135–7.9664 ns | 7.8716 ns |
384
+ | Parity | `even?` integer | 8.1242–8.2117 ns | 8.1567 ns |
385
+
386
+ ### Full-schema benchmark
387
+
388
+ Measure the native engine end-to-end with plans and Ruby Hash inputs prepared
389
+ before Criterion begins timing:
390
+
391
+ ```bash
392
+ cargo bench --locked --manifest-path ext/dry_validation_rust/Cargo.toml --bench full_schema
393
+ ```
394
+
395
+ The following results were measured locally on 2026-08-15 with CRuby 3.4.4,
396
+ Rust 1.90.0, and `dry-validation-rust` 0.1.0.pre4 on x86_64 Linux (kernel
397
+ 7.0.0-29-generic, AMD Ryzen 7 5800H). Criterion used 100 samples with a
398
+ 3-second warm-up and a 5-second measurement period per scenario. Plans and
399
+ inputs are built once; mixed-validity scenarios cycle their prebuilt inputs.
400
+ These figures measure `Engine::call` only, and are host-local baseline
401
+ evidence—not a comparison with the Ruby contract benchmark or a cross-host
402
+ performance guarantee.
403
+
404
+ | Scenario | Criterion estimate (95% confidence interval) | Point estimate |
405
+ | ---------------- | -------------------------------------------: | -------------: |
406
+ | Small form | 3.9515–3.9641 µs | 3.9573 µs |
407
+ | Medium form | 29.051–29.235 µs | 29.141 µs |
408
+ | Large form | 190.63–191.64 µs | 191.12 µs |
409
+ | 10-level nested | 8.8245–9.1422 µs | 8.9877 µs |
410
+ | 100-object array | 318.85–322.47 µs | 320.55 µs |
411
+ | 20-field invalid | 63.480–64.437 µs | 63.953 µs |
412
+
413
+ ## Representative benchmark results
414
+
415
+ The six default rows were measured on 2026-08-13 with CRuby 3.3.7 on x86_64 Linux (kernel 7.0.0-29-generic, AMD Ryzen 7 5800H), comparing dry-validation-rust 0.1.0.pre4 with dry-validation 1.11.1. The strict-key row remains the 2026-08-10 pre3 measurement. Each `SCENARIO` ran in its own process three times with `N=1000`, `WARMUP=200`, and `LATENCY_SAMPLES=200`; the table shows medians and the throughput range across those runs. Values are evidence for this host and workload only, not a general performance guarantee.
416
+
417
+ | `SCENARIO` | Rust validations/s (range) | Upstream validations/s (range) | Throughput ratio | Rust p50/p95/p99 | Upstream p50/p95/p99 |
418
+ | ------------------------------ | -------------------------: | -----------------------------: | ---------------: | -------------------------: | -------------------------: |
419
+ | `small_form` | 71,433 (67,480–76,023) | 35,157 (32,095–36,674) | 2.03× | 12.9/17.5/63.9 µs | 27.6/36.2/91.8 µs |
420
+ | `medium_form` | 10,595 (9,783–11,012) | 2,605 (1,731–2,689) | 4.07× | 38.3/295.9/475.9 µs | 86.0/1,707.8/1,945.3 µs |
421
+ | `large_form` | 1,549 (1,475–1,654) | 307 (209–331) | 5.04× | 955.9/1,298.0/2,117.3 µs | 5,440.0/6,719.5/7,119.9 µs |
422
+ | `nested_object` | 48,723 (35,219–49,256) | 19,212 (12,892–19,990) | 2.54× | 19.5/29.5/216.5 µs | 51.5/77.1/397.1 µs |
423
+ | `array_of_objects` | 1,951 (1,811–2,028) | 781 (743–802) | 2.50× | 455.9/707.2/824.6 µs | 1,207.2/1,594.1/1,979.3 µs |
424
+ | `all_invalid` | 4,071 (3,560–4,088) | 772 (730–837) | 5.28× | 218.5/420.6/611.6 µs | 1,209.3/1,582.9/1,918.4 µs |
425
+ | `large_form` (`validate_keys`) | 974 (970–1,022) | 304 (303–304) | 3.21× | 1,621.4/2,118.8/2,345.3 µs | 5,432.8/6,300.7/7,223.7 µs |
426
+
427
+ | `SCENARIO` | Rust Ruby allocations/call | Upstream Ruby allocations/call | Rust peak RSS | Upstream peak RSS |
428
+ | ------------------------------ | -------------------------: | -----------------------------: | ------------: | ----------------: |
429
+ | `small_form` | 81.01 | 49.01 | 24.6 MiB | 29.5 MiB |
430
+ | `medium_form` | 451.00 | 1,116.80 | 25.4 MiB | 29.9 MiB |
431
+ | `large_form` | 2,836.00 | 10,286.00 | 25.6 MiB | 30.4 MiB |
432
+ | `nested_object` | 145.00 | 113.00 | 25.8 MiB | 30.5 MiB |
433
+ | `array_of_objects` | 3,460.80 | 1,713.60 | 25.8 MiB | 30.8 MiB |
434
+ | `all_invalid` | 976.00 | 4,063.00 | 25.8 MiB | 30.9 MiB |
435
+ | `large_form` (`validate_keys`) | 2,835.01 | 10,490.01 | 25.3 MiB | 30.4 MiB |
436
+
437
+ The Rust path had higher Ruby allocation counts for the small, nested, and array scenarios; this benchmark does not establish a native-allocation total. It measures validation calls after plan construction, so it does not isolate plan-deserialization changes. Peak RSS is process high-water memory, not per-call memory. Reproduce an individual row with, for example:
438
+
439
+ ```bash
440
+ N=1000 WARMUP=200 LATENCY_SAMPLES=200 ENGINE=all SCENARIO=large_form ruby -Ilib benchmark/schema_throughput.rb
441
+ ```
442
+
443
+ ## Important performance caveat
444
+
445
+ The native engine currently reads and creates Ruby objects, so it runs under
446
+ the GVL. Rust reduces Ruby method dispatch and intermediate DSL execution; it
447
+ does not automatically make validation parallel.
448
+
449
+ A future batch API could copy supported values into Rust-owned memory and
450
+ release the GVL, but serialization/copy cost and Ruby object semantics make
451
+ that a separate feature—not a free property of using Rust.
452
+
453
+ ## License and relationship to dry-rb
454
+
455
+ This code is MIT licensed and independent. `dry-validation` and its related
456
+ dry-rb projects are MIT licensed as well, which permits reimplementation and
457
+ derivative work subject to preserving required notices when source is copied.
458
+ See [NOTICE.md](NOTICE.md). The distinct gem name and explicit non-affiliation
459
+ are intentional.