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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +74 -0
- data/LICENSE +21 -0
- data/NOTICE.md +28 -0
- data/README.md +459 -0
- data/docs/ARCHITECTURE.md +256 -0
- data/docs/COMPATIBILITY.md +198 -0
- data/docs/FEASIBILITY.md +207 -0
- data/docs/SUPPORT_MATRIX.md +66 -0
- data/docs/VERIFICATION.md +128 -0
- data/dry-validation-rust.gemspec +58 -0
- data/ext/dry_validation_rust/Cargo.lock +809 -0
- data/ext/dry_validation_rust/Cargo.toml +44 -0
- data/ext/dry_validation_rust/benches/coercion.rs +77 -0
- data/ext/dry_validation_rust/benches/full_schema.rs +189 -0
- data/ext/dry_validation_rust/benches/plan_compile.rs +37 -0
- data/ext/dry_validation_rust/benches/predicates.rs +105 -0
- data/ext/dry_validation_rust/extconf.rb +29 -0
- data/ext/dry_validation_rust/src/coercion.rs +515 -0
- data/ext/dry_validation_rust/src/engine.rs +416 -0
- data/ext/dry_validation_rust/src/error.rs +82 -0
- data/ext/dry_validation_rust/src/extract_primitive.rs +23 -0
- data/ext/dry_validation_rust/src/generated_predicates.rs +33 -0
- data/ext/dry_validation_rust/src/lib.rs +228 -0
- data/ext/dry_validation_rust/src/plan.rs +611 -0
- data/ext/dry_validation_rust/src/predicates.rs +449 -0
- data/ext/dry_validation_rust/src/ruby_bridge.rs +78 -0
- data/lib/dry/schema.rb +6 -0
- data/lib/dry/validation/rust/block_keyword_parameters.rb +20 -0
- data/lib/dry/validation/rust/config.rb +74 -0
- data/lib/dry/validation/rust/contract/result.rb +180 -0
- data/lib/dry/validation/rust/contract/values.rb +73 -0
- data/lib/dry/validation/rust/contract.rb +400 -0
- data/lib/dry/validation/rust/errors.rb +14 -0
- data/lib/dry/validation/rust/evaluator.rb +295 -0
- data/lib/dry/validation/rust/failures.rb +57 -0
- data/lib/dry/validation/rust/generated_predicates.rb +14 -0
- data/lib/dry/validation/rust/macros.rb +45 -0
- data/lib/dry/validation/rust/message.rb +41 -0
- data/lib/dry/validation/rust/message_backend.rb +115 -0
- data/lib/dry/validation/rust/message_set.rb +159 -0
- data/lib/dry/validation/rust/native.rb +25 -0
- data/lib/dry/validation/rust/path.rb +65 -0
- data/lib/dry/validation/rust/path_trie.rb +57 -0
- data/lib/dry/validation/rust/result.rb +3 -0
- data/lib/dry/validation/rust/rule.rb +62 -0
- data/lib/dry/validation/rust/schema/dsl.rb +76 -0
- data/lib/dry/validation/rust/schema/field_builder.rb +156 -0
- data/lib/dry/validation/rust/schema/field_definition.rb +99 -0
- data/lib/dry/validation/rust/schema/predicate_block.rb +56 -0
- data/lib/dry/validation/rust/schema/processor_hooks.rb +46 -0
- data/lib/dry/validation/rust/schema/result.rb +67 -0
- data/lib/dry/validation/rust/schema/ruby_type_processor.rb +44 -0
- data/lib/dry/validation/rust/schema.rb +323 -0
- data/lib/dry/validation/rust/values.rb +3 -0
- data/lib/dry/validation/rust/version.rb +10 -0
- data/lib/dry/validation/rust.rb +55 -0
- data/lib/dry/validation.rb +66 -0
- data/lib/dry-schema.rb +3 -0
- data/lib/dry-validation.rb +3 -0
- data/lib/dry_validation_rust.rb +3 -0
- data/predicates.yml +67 -0
- data/rust-toolchain.toml +9 -0
- 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.
|