dry-validation-rust 0.1.0.pre5 → 0.1.0.pre6
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.
- checksums.yaml +4 -4
- data/.gitignore +13 -0
- data/.markdownlint.yml +9 -0
- data/.rubocop.yml +124 -0
- data/.ruby-version +1 -0
- data/.tool-versions +1 -0
- data/.yardopts +4 -0
- data/AGENTS.md +376 -0
- data/CHANGELOG.md +71 -0
- data/CODE_OF_CONDUCT.md +42 -0
- data/CONTRIBUTING.md +159 -0
- data/Cargo.lock +809 -0
- data/Cargo.toml +10 -0
- data/GOVERNANCE.md +68 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +132 -0
- data/NOTICE.md +5 -5
- data/README.md +206 -181
- data/Rakefile +350 -0
- data/SECURITY.md +98 -0
- data/SECURITY_AUDIT.md +33 -0
- data/SUMMARY.md +30 -0
- data/SUPPORT.md +67 -0
- data/book.toml +9 -0
- data/codecov.yml +11 -0
- data/compatibility.yml +453 -0
- data/deny.toml +7 -0
- data/dry-validation-rust.gemspec +21 -23
- data/ext/dry_validation_rust/Cargo.toml +7 -7
- data/ext/dry_validation_rust/build.rs +5 -0
- data/ext/dry_validation_rust/extconf.rb +12 -0
- data/ext/dry_validation_rust/fuzz/.gitignore +5 -0
- data/ext/dry_validation_rust/fuzz/Cargo.toml +19 -0
- data/ext/dry_validation_rust/fuzz/corpus/parse_plan/basic_params.json +1 -0
- data/ext/dry_validation_rust/fuzz/fuzz_targets/parse_plan.rs +9 -0
- data/lib/dry/validation/rust/path_trie.rb +15 -8
- data/lib/dry/validation/rust/schema/dsl.rb +8 -0
- data/lib/dry/validation/rust/schema/field_definition.rb +3 -1
- data/lib/dry/validation/rust/schema/predicate_block.rb +2 -1
- data/lib/dry/validation/rust/schema/ruby_type_processor.rb +38 -23
- data/lib/dry/validation/rust/version.rb +1 -1
- data/supply-chain/audits.toml +4 -0
- data/supply-chain/config.toml +368 -0
- data/supply-chain/imports.lock +4 -0
- data/support_matrix.yml +9 -0
- metadata +64 -25
- data/docs/ARCHITECTURE.md +0 -256
- data/docs/COMPATIBILITY.md +0 -198
- data/docs/FEASIBILITY.md +0 -207
- data/docs/SUPPORT_MATRIX.md +0 -66
- data/docs/VERIFICATION.md +0 -128
data/README.md
CHANGED
|
@@ -5,20 +5,27 @@ engine with familiar dry-validation-style contract syntax and a precisely
|
|
|
5
5
|
documented compatible subset. Rust handles the immutable declarative schema
|
|
6
6
|
execution path; Ruby preserves dynamic rules and Ruby-specific semantics.
|
|
7
7
|
|
|
8
|
-
>
|
|
9
|
-
>
|
|
10
|
-
>
|
|
11
|
-
>
|
|
8
|
+
> Note: This is an early-stage project. The side-by-side API is covered by
|
|
9
|
+
> focused tests, differential checks, package verification, and reproducible
|
|
10
|
+
> benchmark evidence, but it is not a full, production-ready drop-in
|
|
11
|
+
> replacement for upstream `dry-validation`.
|
|
12
12
|
|
|
13
13
|
Before adoption, review [the support matrix](docs/SUPPORT_MATRIX.md),
|
|
14
14
|
[compatibility matrix](docs/COMPATIBILITY.md), and
|
|
15
15
|
[verification evidence](docs/VERIFICATION.md) for the exact supported surface,
|
|
16
16
|
platforms, and known boundaries.
|
|
17
17
|
|
|
18
|
+
New users can follow the [Getting started guide](docs/getting-started.md) to
|
|
19
|
+
install the gem and build their first contract.
|
|
20
|
+
|
|
21
|
+
The native extension's [Rust API reference](https://alex-tomilov.github.io/dry-validation-rust/rustdoc/)
|
|
22
|
+
is published with the documentation site.
|
|
23
|
+
|
|
18
24
|
For project participation and reporting routes, see
|
|
19
25
|
[CONTRIBUTING.md](CONTRIBUTING.md), [SUPPORT.md](SUPPORT.md),
|
|
20
26
|
[SECURITY.md](SECURITY.md), [GOVERNANCE.md](GOVERNANCE.md), and the
|
|
21
|
-
[Code of Conduct](CODE_OF_CONDUCT.md).
|
|
27
|
+
[Code of Conduct](CODE_OF_CONDUCT.md). Ask usage questions in
|
|
28
|
+
[GitHub Discussions](https://github.com/alex-tomilov/dry-validation-rust/discussions).
|
|
22
29
|
See the concise [roadmap](docs/ROADMAP.md) for planned outcomes and
|
|
23
30
|
[project-management policy](docs/PROJECT_MANAGEMENT.md) for issue workflow.
|
|
24
31
|
|
|
@@ -44,136 +51,46 @@ desirable. Calling those blocks through Ruby preserves the feature that makes
|
|
|
44
51
|
|
|
45
52
|
## Installation
|
|
46
53
|
|
|
47
|
-
### Precompiled (recommended)
|
|
48
|
-
|
|
49
54
|
When a precompiled gem is published for your platform, install it with:
|
|
50
55
|
|
|
51
56
|
```bash
|
|
52
57
|
gem install dry-validation-rust
|
|
53
58
|
```
|
|
54
59
|
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
```
|
|
60
|
+
See the [support matrix](docs/SUPPORT_MATRIX.md) for the authoritative version
|
|
61
|
+
and platform status. If a source build is required, see the
|
|
62
|
+
[source-build instructions](docs/getting-started.md#build-from-source).
|
|
67
63
|
|
|
68
64
|
## Primary safe API
|
|
69
65
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
```
|
|
66
|
+
For new work, use `require "dry/validation/rust"` and subclass
|
|
67
|
+
`Dry::Validation::Rust::Contract`. The [Getting started guide](docs/getting-started.md)
|
|
68
|
+
has a copy-pasteable contract, rule, error-handling, and web-framework examples.
|
|
103
69
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
70
|
+
Version, platform, and upstream-reference targets are listed in
|
|
71
|
+
[SUPPORT_MATRIX.md](docs/SUPPORT_MATRIX.md). Supported DSL and semantic
|
|
72
|
+
differences are listed in [COMPATIBILITY.md](docs/COMPATIBILITY.md).
|
|
107
73
|
|
|
108
74
|
### Side-by-side API stability
|
|
109
75
|
|
|
110
|
-
|
|
111
|
-
`
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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"
|
|
76
|
+
The public side-by-side API is `Dry::Validation::Rust::Contract`, its nested
|
|
77
|
+
`Result` and `Values` types, and the directly exposed `Schema`, `MessageSet`,
|
|
78
|
+
and `Evaluator` types. Its compatibility policy is defined in the
|
|
79
|
+
[support matrix](docs/SUPPORT_MATRIX.md), with individual classifications in
|
|
80
|
+
[API_STABILITY.md](docs/API_STABILITY.md). The exact-compatibility entrypoints
|
|
81
|
+
are explicitly experimental and are not covered by that policy.
|
|
140
82
|
|
|
141
|
-
|
|
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
|
|
83
|
+
## Loading
|
|
163
84
|
|
|
164
85
|
`require "dry/validation/rust"` exposes only the
|
|
165
86
|
`Dry::Validation::Rust` namespace. It does not define
|
|
166
87
|
`Dry::Validation::Contract` or `Dry::Schema`.
|
|
167
88
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
`
|
|
171
|
-
|
|
172
|
-
-
|
|
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.
|
|
89
|
+
The upstream-like `require "dry/validation"` and `require "dry/schema"`
|
|
90
|
+
entrypoints are deprecated and cannot safely coexist with upstream
|
|
91
|
+
`dry-validation` or `dry-schema` in one Ruby process. Use side-by-side mode for
|
|
92
|
+
new work; existing applications should follow the
|
|
93
|
+
[exact-mode migration guide](docs/MIGRATION_FROM_EXACT_MODE.md).
|
|
177
94
|
|
|
178
95
|
## Supported highlights
|
|
179
96
|
|
|
@@ -206,12 +123,8 @@ The complete exclusions and semantic differences are explicit in
|
|
|
206
123
|
|
|
207
124
|
## Building from source
|
|
208
125
|
|
|
209
|
-
|
|
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.
|
|
126
|
+
Meet the [source-build prerequisites](docs/getting-started.md#build-from-source)
|
|
127
|
+
first.
|
|
215
128
|
|
|
216
129
|
Then:
|
|
217
130
|
|
|
@@ -226,40 +139,133 @@ native-extension lifecycle.
|
|
|
226
139
|
|
|
227
140
|
## Verification
|
|
228
141
|
|
|
229
|
-
|
|
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.
|
|
142
|
+
Representative verification evidence, including its pinned runtime versions,
|
|
143
|
+
is recorded in [VERIFICATION.md](docs/VERIFICATION.md).
|
|
236
144
|
|
|
237
145
|
The test suite covers the native plan, coercion modes, nested data, rules,
|
|
238
146
|
rule skipping, array rules, macros, options, context, inheritance, external
|
|
239
147
|
schemas, loading modes, pattern matching, metadata, concurrent calls, malformed
|
|
240
148
|
input resilience, package contents, and differential compatibility fixtures.
|
|
241
149
|
|
|
242
|
-
|
|
150
|
+
## Benchmarking
|
|
151
|
+
|
|
152
|
+
The validation benchmark system has three deliberately separate jobs:
|
|
153
|
+
|
|
154
|
+
1. an interactive `Benchmark.ips`/`MemoryProfiler` showcase for local
|
|
155
|
+
exploration and screenshots;
|
|
156
|
+
2. a repeatable publication runner for README, release, post, and CV evidence;
|
|
157
|
+
3. CI regression gates for detecting performance changes on CI hosts.
|
|
158
|
+
|
|
159
|
+
The detailed protocol and metric wording are documented in
|
|
160
|
+
[`docs/BENCHMARKING.md`](docs/BENCHMARKING.md).
|
|
161
|
+
|
|
162
|
+
### Interactive comparison
|
|
163
|
+
|
|
164
|
+
Run the representative matrix directly when exploring a change:
|
|
243
165
|
|
|
244
166
|
```bash
|
|
245
167
|
ruby -Ilib benchmark/schema_throughput.rb
|
|
246
|
-
|
|
247
|
-
ENGINE=
|
|
248
|
-
|
|
249
|
-
SCENARIO=array_of_objects
|
|
250
|
-
|
|
168
|
+
ENGINE=rust SCENARIO=medium_form ruby -Ilib benchmark/schema_throughput.rb
|
|
169
|
+
ENGINE=upstream SCENARIO=nested_object ruby -Ilib benchmark/schema_throughput.rb
|
|
170
|
+
IPS_WARMUP=3 IPS_TIME=10 MEMORY_PROFILE_N=5000 \
|
|
171
|
+
SCENARIO=array_of_objects ruby -Ilib benchmark/schema_throughput.rb
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Text output uses `Benchmark.ips` for warmed throughput, `MemoryProfiler` for
|
|
175
|
+
Ruby-side allocation detail, and the fixed-run path for peak RSS. A single text
|
|
176
|
+
run is useful evidence while developing, but it is not the canonical source for
|
|
177
|
+
README/CV performance claims.
|
|
178
|
+
|
|
179
|
+
The matrix currently covers eleven validation shapes: small, medium, and large
|
|
180
|
+
flat forms; deep nesting; arrays of nested objects; an all-invalid error path;
|
|
181
|
+
sparse optional data; mixed scalar coercions; a large primitive array; wide
|
|
182
|
+
nested data; and a contract that combines declarative schema work with
|
|
183
|
+
Ruby-owned dynamic rules.
|
|
184
|
+
|
|
185
|
+
### Publication-quality evidence
|
|
186
|
+
|
|
187
|
+
Use the publication runner before updating benchmark claims:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
bundle exec rake compile
|
|
191
|
+
bundle exec script/benchmark-publication
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
By default it performs five independent measurements per engine/scenario. It
|
|
195
|
+
first calibrates the iteration count for each scenario, then runs each engine in
|
|
196
|
+
a fresh Ruby process using an engine-specific calibrated `N`, so a faster engine
|
|
197
|
+
is not accidentally measured for a much shorter interval. Engine order alternates
|
|
198
|
+
between runs and scenario order reverses every other run to reduce systematic
|
|
199
|
+
host drift. Successful results are never discarded automatically. The full
|
|
200
|
+
default matrix has 110 measurements, so it performs about 9 minutes of measured
|
|
201
|
+
work before calibration, warmups, and process startup; the runner reports live
|
|
202
|
+
calibration and measurement progress to stderr.
|
|
203
|
+
|
|
204
|
+
The runner records the exact Git SHA and dirty state, Ruby/platform/CPU details,
|
|
205
|
+
YJIT state, Rust toolchain, requested and actually loaded upstream gem versions,
|
|
206
|
+
protocol settings, every raw measurement, medians,
|
|
207
|
+
ranges, median absolute deviation, paired throughput ratios, Ruby allocation
|
|
208
|
+
changes, and peak RSS changes.
|
|
209
|
+
|
|
210
|
+
Results and checkpoints are written under `tmp/benchmarks/` (already ignored by
|
|
211
|
+
Git). If a run is interrupted, resume it from the printed checkpoint path:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
RESUME_FROM=tmp/benchmarks/publication-...checkpoint.json \
|
|
215
|
+
bundle exec script/benchmark-publication
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
For a longer final evidence run:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
RUNS=7 TARGET_SECONDS=10 bundle exec script/benchmark-publication
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`RETRIES` applies only to process/tooling failures. The runner does not retry a
|
|
225
|
+
successful measurement merely because it is slow or weakens the speedup claim.
|
|
226
|
+
Publication mode refuses a dirty working tree unless `ALLOW_DIRTY=true` is set;
|
|
227
|
+
dirty runs are exploratory and should not become canonical README/CV evidence.
|
|
228
|
+
|
|
229
|
+
Use `FORMAT=json` on the lower-level benchmark only when tooling needs one
|
|
230
|
+
single fixed-run payload:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
FORMAT=json ENGINE=all SCENARIO=small_form \
|
|
234
|
+
N=10000 WARMUP=1000 LATENCY_SAMPLES=500 \
|
|
235
|
+
ruby -Ilib benchmark/schema_throughput.rb
|
|
251
236
|
```
|
|
252
237
|
|
|
253
|
-
|
|
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.
|
|
238
|
+
### Process-memory evidence
|
|
260
239
|
|
|
261
|
-
|
|
262
|
-
|
|
240
|
+
Ruby allocation counters are useful for understanding GC pressure, but an
|
|
241
|
+
allocated-object count is not a whole-process memory measurement. For a
|
|
242
|
+
same-work comparison of the hybrid and upstream implementations, run:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
bundle exec rake compile
|
|
246
|
+
bundle exec script/benchmark-memory-footprint
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The memory runner uses the same validation count and warmup for both engines in
|
|
250
|
+
each scenario. On Linux it records current/peak RSS plus PSS and USS around the
|
|
251
|
+
timed validation loop. Peak RSS includes resident Ruby and Rust/native memory;
|
|
252
|
+
PSS apportions shared pages and USS reports private resident pages. See
|
|
253
|
+
[`docs/MEMORY_BENCHMARKING.md`](docs/MEMORY_BENCHMARKING.md) for exact metric
|
|
254
|
+
semantics and limitations.
|
|
255
|
+
|
|
256
|
+
These are process-footprint metrics, not cumulative bytes allocated over time.
|
|
257
|
+
Ruby object counts from `GC.stat` remain a separate GC-pressure signal.
|
|
258
|
+
|
|
259
|
+
Ruby allocation counters (`GC.stat` and the interactive `MemoryProfiler`
|
|
260
|
+
section) describe Ruby-side allocation activity. Peak RSS is a whole-process
|
|
261
|
+
resident high-water mark and already includes resident Ruby and Rust/native
|
|
262
|
+
memory, but it is not cumulative allocated bytes and it fully counts shared
|
|
263
|
+
resident pages. Use the separate process-memory runner for same-work RSS/PSS/USS
|
|
264
|
+
comparisons. Throughput ratios apply to validation calls after contract/plan
|
|
265
|
+
construction and must not be presented as end-to-end Rails request speedups.
|
|
266
|
+
|
|
267
|
+
Refresh the allocation-regression baseline only after intentionally reviewing
|
|
268
|
+
an allocation change:
|
|
263
269
|
|
|
264
270
|
```bash
|
|
265
271
|
bundle exec script/record-allocation-baseline
|
|
@@ -270,13 +276,10 @@ artifact without changing the repository. Review its value before replacing
|
|
|
270
276
|
`benchmark/baseline_allocations.json`; do not accept an allocation regression
|
|
271
277
|
merely by refreshing the baseline.
|
|
272
278
|
|
|
273
|
-
|
|
274
|
-
the
|
|
275
|
-
|
|
276
|
-
|
|
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.
|
|
279
|
+
The upstream `dry-validation` gem remains intentionally outside the project
|
|
280
|
+
dependencies. Install the comparison version for the same Ruby before running
|
|
281
|
+
comparison/publication benchmarks. Report the exact upstream version with every
|
|
282
|
+
published result.
|
|
280
283
|
|
|
281
284
|
### Plan-compilation benchmark
|
|
282
285
|
|
|
@@ -318,7 +321,8 @@ The following coercion results were measured locally on 2026-08-14 with CRuby
|
|
|
318
321
|
3.4.4, Rust 1.90.0, and `dry-validation-rust` 0.1.0.pre4 on x86_64 Linux
|
|
319
322
|
(kernel 7.0.0-29-generic, AMD Ryzen 7 5800H). Criterion used 100 samples with
|
|
320
323
|
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
|
|
324
|
+
baseline observations, not cross-host performance guarantees. `Infinity` exercises the intentional
|
|
325
|
+
non-finite-float rejection path; the datetime-shaped date literal exercises the Ruby fallback path.
|
|
322
326
|
|
|
323
327
|
| Group | Input | Criterion estimate (95% confidence interval) | Point estimate |
|
|
324
328
|
| ----------------- | ---------------------- | -------------------------------------------: | -------------: |
|
|
@@ -410,35 +414,56 @@ performance guarantee.
|
|
|
410
414
|
| 100-object array | 318.85–322.47 µs | 320.55 µs |
|
|
411
415
|
| 20-field invalid | 63.480–64.437 µs | 63.953 µs |
|
|
412
416
|
|
|
413
|
-
## Representative
|
|
414
|
-
|
|
415
|
-
The
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
| `
|
|
426
|
-
|
|
427
|
-
| `
|
|
428
|
-
|
|
|
429
|
-
| `
|
|
430
|
-
| `
|
|
431
|
-
| `
|
|
432
|
-
| `
|
|
433
|
-
| `
|
|
434
|
-
| `
|
|
435
|
-
| `
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
417
|
+
## Representative publication results (2026-08-24)
|
|
418
|
+
|
|
419
|
+
The following publication runs compare the hybrid engine with
|
|
420
|
+
dry-validation 1.11.1 (dry-schema 1.16.0 and dry-types 1.9.1) on CRuby 3.3.7,
|
|
421
|
+
x86_64 Linux, and an AMD Ryzen 7 5800H. They are host-local evidence, not a
|
|
422
|
+
cross-host guarantee or an end-to-end Rails request benchmark.
|
|
423
|
+
|
|
424
|
+
The throughput run measured commit `e26bbb18e90f` in seven isolated Ruby
|
|
425
|
+
processes per engine and scenario, targeting 10 seconds each. It measures
|
|
426
|
+
validation calls after contract/plan construction; ranges show the full set of
|
|
427
|
+
successful measurements.
|
|
428
|
+
|
|
429
|
+
| `SCENARIO` | Rust validations/s, median (range) | Upstream validations/s, median (range) | Median speedup (range) |
|
|
430
|
+
| --------------------- | ---------------------------------: | -------------------------------------: | ---------------------: |
|
|
431
|
+
| `small_form` | 100,083 (84,791–101,865) | 40,111 (36,933–41,111) | 2.50× (2.11–2.56) |
|
|
432
|
+
| `medium_form` | 14,517 (13,099–14,657) | 2,792 (2,439–2,921) | 5.14× (4.97–5.51) |
|
|
433
|
+
| `large_form` | 2,140 (1,816–2,153) | 307 (275–344) | 6.73× (6.24–7.36) |
|
|
434
|
+
| `nested_object` | 56,291 (50,939–62,997) | 19,402 (17,589–20,917) | 2.98× (2.90–3.15) |
|
|
435
|
+
| `array_of_objects` | 4,411 (4,202–4,778) | 800 (709–832) | 5.68× (5.33–5.93) |
|
|
436
|
+
| `all_invalid` | 5,397 (5,057–5,625) | 823 (762–887) | 6.44× (6.16–7.38) |
|
|
437
|
+
| `sparse_optional` | 30,623 (29,528–32,315) | 7,345 (7,073–8,073) | 4.12× (3.66–4.41) |
|
|
438
|
+
| `mixed_types` | 38,408 (36,599–39,227) | 15,420 (14,069–16,033) | 2.48× (2.45–2.69) |
|
|
439
|
+
| `array_of_primitives` | 16,656 (15,718–17,299) | 3,220 (3,083–3,272) | 5.20× (4.80–5.32) |
|
|
440
|
+
| `wide_nested_object` | 8,472 (7,993–8,802) | 3,744 (3,440–4,002) | 2.26× (2.06–2.33) |
|
|
441
|
+
| `ruby_rules` | 18,127 (16,774–19,774) | 8,812 (8,487–9,239) | 2.09× (1.93–2.16) |
|
|
442
|
+
|
|
443
|
+
The separate process-memory run measured commit `0f2f46414bc9`, also with
|
|
444
|
+
seven runs and identical validation count/warmup for both engines within each
|
|
445
|
+
scenario. Peak RSS is a whole-process high-water mark during the loop; the
|
|
446
|
+
Linux-only PSS and USS measurements are taken after it. PSS apportions shared
|
|
447
|
+
pages and USS counts private resident pages.
|
|
448
|
+
|
|
449
|
+
| `SCENARIO` | Peak RSS reduction | PSS reduction | USS reduction | Ruby object reduction |
|
|
450
|
+
| --------------------- | -----------------: | ----------------: | ----------------: | --------------------: |
|
|
451
|
+
| `small_form` | 12.9% (12.6–13.2) | 14.8% (14.5–15.0) | 16.2% (15.8–16.4) | -42.9% |
|
|
452
|
+
| `medium_form` | 12.3% (12.2–12.7) | 14.6% (14.3–14.8) | 15.9% (15.6–16.1) | 63.7% |
|
|
453
|
+
| `large_form` | 11.2% (10.7–11.4) | 12.9% (12.2–13.1) | 14.1% (13.5–14.3) | 74.9% |
|
|
454
|
+
| `nested_object` | 13.1% (12.8–13.4) | 15.3% (15.0–15.6) | 16.7% (16.4–17.0) | -13.3% |
|
|
455
|
+
| `array_of_objects` | 13.0% (12.2–13.1) | 15.1% (14.4–15.4) | 16.4% (15.7–16.7) | 3.6% |
|
|
456
|
+
| `all_invalid` | 9.5% (9.0–9.9) | 10.8% (10.2–11.1) | 12.0% (11.4–12.2) | 78.0% |
|
|
457
|
+
| `sparse_optional` | 15.7% (15.6–16.1) | 18.7% (18.2–18.9) | 20.1% (19.7–20.4) | 39.3% |
|
|
458
|
+
| `mixed_types` | 16.0% (15.4–16.2) | 18.8% (18.5–19.1) | 20.2% (19.9–20.6) | -74.3% |
|
|
459
|
+
| `array_of_primitives` | 12.9% (11.9–13.1) | 14.6% (13.4–14.8) | 15.7% (14.5–15.9) | -2.6% |
|
|
460
|
+
| `wide_nested_object` | 14.8% (14.4–15.2) | 17.1% (16.8–17.7) | 18.5% (18.2–19.1) | -179.6% |
|
|
461
|
+
| `ruby_rules` | 11.4% (10.9–12.3) | 13.1% (12.5–14.2) | 14.4% (13.7–15.4) | 34.2% |
|
|
462
|
+
|
|
463
|
+
Ruby object reduction is a `GC.stat` count, not a byte total; negative values
|
|
464
|
+
mean the hybrid path allocated more Ruby objects. None of RSS, PSS, or USS is a
|
|
465
|
+
measure of cumulative allocated bytes. Reproduce fresh evidence with the
|
|
466
|
+
publication runners above rather than extrapolating these host-local figures.
|
|
442
467
|
|
|
443
468
|
## Important performance caveat
|
|
444
469
|
|