dry-validation-rust 0.1.0.pre6-x64-mingw-ucrt
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/.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 +145 -0
- data/CODE_OF_CONDUCT.md +42 -0
- data/CONTRIBUTING.md +159 -0
- data/GOVERNANCE.md +68 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +132 -0
- data/LICENSE +21 -0
- data/NOTICE.md +28 -0
- data/README.md +484 -0
- 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 +56 -0
- data/ext/dry_validation_rust/fuzz/.gitignore +5 -0
- data/ext/dry_validation_rust/fuzz/corpus/parse_plan/basic_params.json +1 -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/native.so +0 -0
- data/lib/dry/validation/rust/path.rb +65 -0
- data/lib/dry/validation/rust/path_trie.rb +64 -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 +84 -0
- data/lib/dry/validation/rust/schema/field_builder.rb +156 -0
- data/lib/dry/validation/rust/schema/field_definition.rb +101 -0
- data/lib/dry/validation/rust/schema/predicate_block.rb +57 -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 +59 -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
- 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 +261 -0
data/compatibility.yml
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Compatibility target and matrix
|
|
3
|
+
blocks:
|
|
4
|
+
- heading: Target
|
|
5
|
+
- markdown: |-
|
|
6
|
+
The public target for the `0.1.x` line is the common `dry-validation` contract
|
|
7
|
+
surface. The authoritative machine-readable pin is `dry-validation` 1.11.1 in
|
|
8
|
+
the root `Gemfile`; its locked `dry-schema` dependency is 1.16.0. This is not a
|
|
9
|
+
claim of full behavioral compatibility. `bundle exec rake compatibility:differential`
|
|
10
|
+
executes the initial corpus against both engines in separate Ruby processes.
|
|
11
|
+
|
|
12
|
+
Version, platform, and release-line support are documented in
|
|
13
|
+
[SUPPORT_MATRIX.md](SUPPORT_MATRIX.md).
|
|
14
|
+
- heading: Runtime build dependencies
|
|
15
|
+
- markdown: "The source gem supports `bigdecimal` 3.1.x through 4.x. CI compiles and
|
|
16
|
+
runs\nthe test suite with 3.1.0, 3.2.0, and 4.0.0. The lower bound keeps support
|
|
17
|
+
for\nthe earliest version used by the supported Ruby line; the `< 5.0` bound makes
|
|
18
|
+
a\nnew major release an explicit compatibility decision.\n\nThe source extension
|
|
19
|
+
supports `rb_sys` 0.9.100 through the latest 0.9.x. CI\ntests 0.9.100, 0.9.128,
|
|
20
|
+
and the latest resolvable 0.9.x. Its native `rb-sys`\ncrate enables only `stable-api-compiled-fallback`:
|
|
21
|
+
the fallback supplies a\ncompiled stable Ruby C API implementation when `rb-sys`
|
|
22
|
+
has no generated stable\nAPI candidate for the running Ruby. No default or unrelated
|
|
23
|
+
`rb-sys` features\nare enabled.\n\nFor `0.1.x`, the side-by-side API rooted at\n`Dry::Validation::Rust::Contract`
|
|
24
|
+
is stable: a breaking change to its documented\npublic surface, including its
|
|
25
|
+
nested `Result` and `Values` types and the `Schema`,\n`MessageSet`, and `Evaluator`
|
|
26
|
+
types, requires a minor release. Exact-compatibility\nentrypoints are deprecated,
|
|
27
|
+
remain temporarily available, and are excluded from\nthis compatibility promise.
|
|
28
|
+
Migrate to the side-by-side namespace using\n[MIGRATION_FROM_EXACT_MODE.md](MIGRATION_FROM_EXACT_MODE.md).\n\nLegend:\n\n-
|
|
29
|
+
✅ implemented and covered by this prototype's tests;\n- \U0001F7E1 partial or
|
|
30
|
+
intentionally narrower;\n- ⚠️ deprecated; available temporarily only for migration;\n-
|
|
31
|
+
❌ not implemented;\n- N/A intentionally left to Ruby rather than translated."
|
|
32
|
+
- heading: Loading and factories
|
|
33
|
+
- table:
|
|
34
|
+
columns:
|
|
35
|
+
- Surface
|
|
36
|
+
- Status
|
|
37
|
+
- Notes
|
|
38
|
+
rows:
|
|
39
|
+
- - '`require "dry/validation/rust"`'
|
|
40
|
+
- "✅"
|
|
41
|
+
- Side-by-side namespace
|
|
42
|
+
- - "`Dry::Validation::Rust::Contract`"
|
|
43
|
+
- "✅"
|
|
44
|
+
- Safe migration superclass
|
|
45
|
+
- - '`require "dry/validation"`'
|
|
46
|
+
- "⚠️"
|
|
47
|
+
- Deprecated; migrate to side-by-side
|
|
48
|
+
- - "`Dry::Validation::Contract`"
|
|
49
|
+
- "⚠️"
|
|
50
|
+
- Deprecated alias; migrate
|
|
51
|
+
- - "`Dry::Validation.Contract { ... }`"
|
|
52
|
+
- "⚠️"
|
|
53
|
+
- Deprecated factory; migrate
|
|
54
|
+
- - "`Dry::Validation::Rust.Contract { ... }`"
|
|
55
|
+
- "✅"
|
|
56
|
+
- Safe factory
|
|
57
|
+
- - "`Dry::Schema.Params` / `JSON` / `define`"
|
|
58
|
+
- "⚠️"
|
|
59
|
+
- Deprecated exact-mode factories
|
|
60
|
+
- - Co-install exact mode with upstream gems
|
|
61
|
+
- "❌"
|
|
62
|
+
- Require path and constant collision
|
|
63
|
+
- heading: Schema definition
|
|
64
|
+
- table:
|
|
65
|
+
columns:
|
|
66
|
+
- Surface
|
|
67
|
+
- Status
|
|
68
|
+
- Notes
|
|
69
|
+
rows:
|
|
70
|
+
- - "`params do ... end`"
|
|
71
|
+
- "✅"
|
|
72
|
+
- HTTP-like key/scalar coercion
|
|
73
|
+
- - "`json do ... end`"
|
|
74
|
+
- "✅"
|
|
75
|
+
- Key normalization, no scalar coercion
|
|
76
|
+
- - "`schema do ... end`"
|
|
77
|
+
- "✅"
|
|
78
|
+
- Symbol keys, no coercion
|
|
79
|
+
- - "`required(:key)`"
|
|
80
|
+
- "✅"
|
|
81
|
+
- ''
|
|
82
|
+
- - "`optional(:key)`"
|
|
83
|
+
- "✅"
|
|
84
|
+
- ''
|
|
85
|
+
- - "`value(:type)`"
|
|
86
|
+
- "✅"
|
|
87
|
+
- ''
|
|
88
|
+
- - "`filled(:type)` / `filled`"
|
|
89
|
+
- "✅"
|
|
90
|
+
- Nil and empty String/Array/Hash
|
|
91
|
+
- - "`maybe(:type)`"
|
|
92
|
+
- "✅"
|
|
93
|
+
- Params empty string becomes nil
|
|
94
|
+
- - "`hash do ... end`"
|
|
95
|
+
- "✅"
|
|
96
|
+
- Recursive
|
|
97
|
+
- - "`array(:type)`"
|
|
98
|
+
- "✅"
|
|
99
|
+
- Coerced primitive members
|
|
100
|
+
- - "`array(:hash) { ... }`"
|
|
101
|
+
- "✅"
|
|
102
|
+
- Nested member schema
|
|
103
|
+
- - External schema arguments
|
|
104
|
+
- "✅"
|
|
105
|
+
- Rust schemas only
|
|
106
|
+
- - Contract schema inheritance
|
|
107
|
+
- "✅"
|
|
108
|
+
- Child schema extends parent
|
|
109
|
+
- - Multiple schema declaration guard
|
|
110
|
+
- "✅"
|
|
111
|
+
- Raises `DuplicateSchemaError`
|
|
112
|
+
- - Key validation when declaring rules
|
|
113
|
+
- "✅"
|
|
114
|
+
- Common nested paths
|
|
115
|
+
- - Predicate-composition blocks
|
|
116
|
+
- "✅"
|
|
117
|
+
- Supported predicates only; boolean AST composition is unsupported. See [migration
|
|
118
|
+
recipe](MIGRATION_RECIPES.md#boolean-predicate-composition).
|
|
119
|
+
- - Schema `before` / `after` processor hooks
|
|
120
|
+
- "✅"
|
|
121
|
+
- "`:value_coercer` only; callbacks run outside the native engine. Before hooks
|
|
122
|
+
receive an isolated deep copy of the input."
|
|
123
|
+
- - Schema merge operators / AST access
|
|
124
|
+
- "❌"
|
|
125
|
+
- ''
|
|
126
|
+
- - "`config.validate_keys = true`"
|
|
127
|
+
- "✅"
|
|
128
|
+
- Rejects unknown keys in `params` and `json` schemas
|
|
129
|
+
- - Filtering DSL
|
|
130
|
+
- "❌"
|
|
131
|
+
- Will not implement for 0.1.x; filter `result.to_h` explicitly. See [migration
|
|
132
|
+
recipe](MIGRATION_RECIPES.md#schema-filtering-dsl).
|
|
133
|
+
- heading: Types and coercions
|
|
134
|
+
- table:
|
|
135
|
+
columns:
|
|
136
|
+
- Type
|
|
137
|
+
- Params
|
|
138
|
+
- JSON/schema checks
|
|
139
|
+
- Notes
|
|
140
|
+
rows:
|
|
141
|
+
- - "`:any`"
|
|
142
|
+
- "✅"
|
|
143
|
+
- "✅"
|
|
144
|
+
- ''
|
|
145
|
+
- - "`:nil`"
|
|
146
|
+
- N/A
|
|
147
|
+
- "✅"
|
|
148
|
+
- ''
|
|
149
|
+
- - "`:bool` / `:true` / `:false`"
|
|
150
|
+
- "✅"
|
|
151
|
+
- "✅"
|
|
152
|
+
- "`true/false`, `t/f`, `1/0`, `on/off`, `yes/no`, and `y/n` strings"
|
|
153
|
+
- - "`:integer`"
|
|
154
|
+
- "✅"
|
|
155
|
+
- "✅"
|
|
156
|
+
- Native signed-64-bit decimal, underscore, and `0x`/`0b`/`0o` paths; Ruby fallback
|
|
157
|
+
preserves Bignums and unusual syntax
|
|
158
|
+
- - "`:float`"
|
|
159
|
+
- "✅"
|
|
160
|
+
- "✅"
|
|
161
|
+
- Native finite decimal/scientific literals; Ruby fallback preserves overflow
|
|
162
|
+
and other syntax; literal `Infinity` and `NaN` rejected
|
|
163
|
+
- - "`:decimal`"
|
|
164
|
+
- "✅"
|
|
165
|
+
- "✅"
|
|
166
|
+
- Native finite `bigdecimal` parse followed by BigDecimal construction; Ruby
|
|
167
|
+
fallback preserves arbitrary precision; infinities and NaN rejected
|
|
168
|
+
- - "`:string`"
|
|
169
|
+
- "✅"
|
|
170
|
+
- "✅"
|
|
171
|
+
- No non-string-to-string coercion
|
|
172
|
+
- - "`:symbol`"
|
|
173
|
+
- "✅"
|
|
174
|
+
- "✅"
|
|
175
|
+
- ''
|
|
176
|
+
- - "`:array` / `:hash`"
|
|
177
|
+
- "✅"
|
|
178
|
+
- "✅"
|
|
179
|
+
- ''
|
|
180
|
+
- - "`:date`"
|
|
181
|
+
- "✅"
|
|
182
|
+
- "✅"
|
|
183
|
+
- Native `YYYY-MM-DD`; Ruby ISO 8601 fallback for broader date syntax
|
|
184
|
+
- - "`:date_time` / `:datetime`"
|
|
185
|
+
- "✅"
|
|
186
|
+
- "✅"
|
|
187
|
+
- Native whole-second RFC 3339 and timezone-free `YYYY-MM-DDTHH:MM:SS`; Ruby
|
|
188
|
+
fallback otherwise
|
|
189
|
+
- - "`:time`"
|
|
190
|
+
- "✅"
|
|
191
|
+
- "✅"
|
|
192
|
+
- Native RFC 3339; Ruby `Time.parse` fallback for time-only and broader syntax
|
|
193
|
+
- - dry-types objects/constructors
|
|
194
|
+
- "✅"
|
|
195
|
+
- "✅"
|
|
196
|
+
- Ruby-owned fields call `#try`; conversion failures use the generic `is invalid`
|
|
197
|
+
message
|
|
198
|
+
- - Sum types
|
|
199
|
+
- "✅"
|
|
200
|
+
- "✅"
|
|
201
|
+
- Ruby-owned direct `value(type)` fields only
|
|
202
|
+
- - Enums/maps/intersections
|
|
203
|
+
- "❌"
|
|
204
|
+
- "❌"
|
|
205
|
+
- ''
|
|
206
|
+
- - Params hash-to-array coercion
|
|
207
|
+
- "❌"
|
|
208
|
+
- N/A
|
|
209
|
+
- ''
|
|
210
|
+
- markdown: |-
|
|
211
|
+
The supported scalar corpus is differentially checked against the pinned
|
|
212
|
+
upstream version for numeric boundaries, boolean spellings, temporal parsing,
|
|
213
|
+
and symbols. This is a focused compatibility slice, not a claim of complete
|
|
214
|
+
`dry-types` coercion parity. Custom type objects are intentionally processed
|
|
215
|
+
by Ruby after native traversal; their conversion output is preserved, but
|
|
216
|
+
dry-schema's type-specific failure-message compilation and custom array-member
|
|
217
|
+
types remain unsupported.
|
|
218
|
+
- heading: Schema predicates
|
|
219
|
+
- table:
|
|
220
|
+
columns:
|
|
221
|
+
- Predicate
|
|
222
|
+
- Status
|
|
223
|
+
- Owner
|
|
224
|
+
rows:
|
|
225
|
+
- - "`gt?`, `gteq?`, `lt?`, `lteq?`"
|
|
226
|
+
- "✅"
|
|
227
|
+
- Rust
|
|
228
|
+
- - "`size?`, `min_size?`, `max_size?`"
|
|
229
|
+
- "✅"
|
|
230
|
+
- Rust
|
|
231
|
+
- - "`odd?`, `even?`"
|
|
232
|
+
- "✅"
|
|
233
|
+
- Rust
|
|
234
|
+
- - "`format?`"
|
|
235
|
+
- "✅"
|
|
236
|
+
- Ruby Regexp
|
|
237
|
+
- - "`included_in?`, `excluded_from?`"
|
|
238
|
+
- "✅"
|
|
239
|
+
- Ruby
|
|
240
|
+
- - "`eql?`, `not_eql?`"
|
|
241
|
+
- "✅"
|
|
242
|
+
- Ruby
|
|
243
|
+
- - Arbitrary/custom predicate name
|
|
244
|
+
- "❌"
|
|
245
|
+
- Explicit error at execution
|
|
246
|
+
- - Boolean predicate AST composition
|
|
247
|
+
- "❌"
|
|
248
|
+
- Will not implement for 0.1.x; predicate blocks support sequential calls only.
|
|
249
|
+
See [migration recipe](MIGRATION_RECIPES.md#boolean-predicate-composition).
|
|
250
|
+
- - UUID and other dry-logic predicates
|
|
251
|
+
- "❌"
|
|
252
|
+
- Will not implement for 0.1.x; use `format?` or a contract rule. See [migration
|
|
253
|
+
recipe](MIGRATION_RECIPES.md#uuid-and-other-dry-logic-predicates).
|
|
254
|
+
- markdown: |-
|
|
255
|
+
Ruby-owned predicates execute after structural processing and are skipped when
|
|
256
|
+
the same field already has a type/structural error.
|
|
257
|
+
- heading: Contract rules
|
|
258
|
+
- table:
|
|
259
|
+
columns:
|
|
260
|
+
- Surface
|
|
261
|
+
- Status
|
|
262
|
+
- Notes
|
|
263
|
+
rows:
|
|
264
|
+
- - "`rule(:key) { ... }`"
|
|
265
|
+
- "✅"
|
|
266
|
+
- Ordered
|
|
267
|
+
- - Multi-key rules
|
|
268
|
+
- "✅"
|
|
269
|
+
- ''
|
|
270
|
+
- - Dot-string nested paths
|
|
271
|
+
- "✅"
|
|
272
|
+
- ''
|
|
273
|
+
- - Array path form
|
|
274
|
+
- "✅"
|
|
275
|
+
- ''
|
|
276
|
+
- - Simple/multi hash path form
|
|
277
|
+
- "✅"
|
|
278
|
+
- ''
|
|
279
|
+
- - Skip when schema dependency fails
|
|
280
|
+
- "✅"
|
|
281
|
+
- Prefix-aware
|
|
282
|
+
- - "`value` / `values`"
|
|
283
|
+
- "✅"
|
|
284
|
+
- ''
|
|
285
|
+
- - "`key?`"
|
|
286
|
+
- "✅"
|
|
287
|
+
- Nested and indexed paths
|
|
288
|
+
- - "`key.failure` / `key(path).failure`"
|
|
289
|
+
- "✅"
|
|
290
|
+
- ''
|
|
291
|
+
- - "`base.failure`"
|
|
292
|
+
- "✅"
|
|
293
|
+
- Base key is nil in `to_h`
|
|
294
|
+
- - Explicit String failures
|
|
295
|
+
- "✅"
|
|
296
|
+
- ''
|
|
297
|
+
- - Hash failure metadata
|
|
298
|
+
- "✅"
|
|
299
|
+
- ''
|
|
300
|
+
- - Symbol/localized failure identifiers
|
|
301
|
+
- "\U0001F7E1"
|
|
302
|
+
- Small built-in fallback, no locale backend
|
|
303
|
+
- - "`schema_error?`"
|
|
304
|
+
- "✅"
|
|
305
|
+
- ''
|
|
306
|
+
- - "`rule_error?` / `base_rule_error?`"
|
|
307
|
+
- "✅"
|
|
308
|
+
- ''
|
|
309
|
+
- - "`rule(:array).each`"
|
|
310
|
+
- "✅"
|
|
311
|
+
- Provides `index:`
|
|
312
|
+
- - Rule context
|
|
313
|
+
- "✅"
|
|
314
|
+
- Hash rather than Concurrent::Map
|
|
315
|
+
- - Delegate to contract methods
|
|
316
|
+
- "✅"
|
|
317
|
+
- Includes private methods
|
|
318
|
+
- - Rule block exceptions
|
|
319
|
+
- "✅"
|
|
320
|
+
- Propagate as Ruby exceptions
|
|
321
|
+
- heading: Options and macros
|
|
322
|
+
- table:
|
|
323
|
+
columns:
|
|
324
|
+
- Surface
|
|
325
|
+
- Status
|
|
326
|
+
- Notes
|
|
327
|
+
rows:
|
|
328
|
+
- - Required `option :name`
|
|
329
|
+
- "✅"
|
|
330
|
+
- ''
|
|
331
|
+
- - Optional option
|
|
332
|
+
- "✅"
|
|
333
|
+
- ''
|
|
334
|
+
- - Static/callable default
|
|
335
|
+
- "✅"
|
|
336
|
+
- Callable receives no instance
|
|
337
|
+
- - dry-auto_inject integration
|
|
338
|
+
- "\U0001F7E1"
|
|
339
|
+
- Likely works through keywords; not certified
|
|
340
|
+
- - Global macros
|
|
341
|
+
- "✅"
|
|
342
|
+
- ''
|
|
343
|
+
- - Per-contract macros and inheritance
|
|
344
|
+
- "✅"
|
|
345
|
+
- ''
|
|
346
|
+
- - Macro arguments and `macro:`
|
|
347
|
+
- "✅"
|
|
348
|
+
- ''
|
|
349
|
+
- - "`rule.validate`"
|
|
350
|
+
- "✅"
|
|
351
|
+
- ''
|
|
352
|
+
- - Predicate-as-macro extension
|
|
353
|
+
- "\U0001F7E1"
|
|
354
|
+
- Common numeric/size/format cases only
|
|
355
|
+
- - Full localized macro templates
|
|
356
|
+
- "❌"
|
|
357
|
+
- ''
|
|
358
|
+
- heading: Results and messages
|
|
359
|
+
- table:
|
|
360
|
+
columns:
|
|
361
|
+
- Surface
|
|
362
|
+
- Status
|
|
363
|
+
- Notes
|
|
364
|
+
rows:
|
|
365
|
+
- - "`success?` / `failure?`"
|
|
366
|
+
- "✅"
|
|
367
|
+
- ''
|
|
368
|
+
- - "`to_h` / `[]` / `key?` / `values`"
|
|
369
|
+
- "✅"
|
|
370
|
+
- ''
|
|
371
|
+
- - "`errors.to_h`"
|
|
372
|
+
- "✅"
|
|
373
|
+
- Nested hashes and integer indexes
|
|
374
|
+
- - Enumerable errors
|
|
375
|
+
- "✅"
|
|
376
|
+
- ''
|
|
377
|
+
- - Mutable `errors.messages` collection
|
|
378
|
+
- "\U0001F7E1"
|
|
379
|
+
- Read-only view; mutate a set through `#add`
|
|
380
|
+
- - "`errors[:path]`"
|
|
381
|
+
- "✅"
|
|
382
|
+
- Prefix filter
|
|
383
|
+
- - "`errors.filter(:base?)`"
|
|
384
|
+
- "✅"
|
|
385
|
+
- Also `schema?` and `rule?`
|
|
386
|
+
- - "`errors(full: true)`"
|
|
387
|
+
- "\U0001F7E1"
|
|
388
|
+
- Simple humanized paths
|
|
389
|
+
- - Error `text`, `path`, `meta`, `code`
|
|
390
|
+
- "✅"
|
|
391
|
+
- ''
|
|
392
|
+
- - Hash pattern matching
|
|
393
|
+
- "✅"
|
|
394
|
+
- ''
|
|
395
|
+
- - Tuple values/context pattern matching
|
|
396
|
+
- "✅"
|
|
397
|
+
- ''
|
|
398
|
+
- - YAML messages/configured load paths
|
|
399
|
+
- "✅"
|
|
400
|
+
- Supports localized schema templates and `%{token}` interpolation
|
|
401
|
+
- - I18n backend/locales
|
|
402
|
+
- "✅"
|
|
403
|
+
- Delegates to the optional `i18n` gem; add it to the application bundle
|
|
404
|
+
- - Exact upstream message wording
|
|
405
|
+
- "\U0001F7E1"
|
|
406
|
+
- Common English messages only
|
|
407
|
+
- - Hints/info message extensions
|
|
408
|
+
- "❌"
|
|
409
|
+
- ''
|
|
410
|
+
- - Monads extension
|
|
411
|
+
- "❌"
|
|
412
|
+
- "`load_extensions` raises explicitly"
|
|
413
|
+
- heading: Runtime and platform
|
|
414
|
+
- table:
|
|
415
|
+
columns:
|
|
416
|
+
- Property
|
|
417
|
+
- Status
|
|
418
|
+
rows:
|
|
419
|
+
- - CRuby 3.3
|
|
420
|
+
- "✅ compiled/tested"
|
|
421
|
+
- - Ruby 3.4/current
|
|
422
|
+
- "\U0001F7E1 expected, not verified here"
|
|
423
|
+
- - Linux x86-64
|
|
424
|
+
- "✅"
|
|
425
|
+
- - Linux arm64/musl
|
|
426
|
+
- "\U0001F7E1 source design supports it; not tested"
|
|
427
|
+
- - macOS
|
|
428
|
+
- "\U0001F7E1 source design supports it; not tested"
|
|
429
|
+
- - Windows
|
|
430
|
+
- "\U0001F7E1 RubyInstaller UCRT CI runs, allowed to fail"
|
|
431
|
+
- - JRuby
|
|
432
|
+
- "❌"
|
|
433
|
+
- - TruffleRuby
|
|
434
|
+
- "❌"
|
|
435
|
+
- - Ruby threads
|
|
436
|
+
- "✅ call isolation tested"
|
|
437
|
+
- - Ractors
|
|
438
|
+
- "❌ no compatibility promise"
|
|
439
|
+
- - GVL release
|
|
440
|
+
- "❌ native Ruby-object path holds GVL"
|
|
441
|
+
- heading: Migration guidance
|
|
442
|
+
- markdown: |-
|
|
443
|
+
1. Start with side-by-side mode and a small contract.
|
|
444
|
+
2. Build a fixture corpus from production-shaped valid and invalid payloads.
|
|
445
|
+
3. Run upstream and Rust contracts in separate processes.
|
|
446
|
+
4. Compare output values, classes, errors, paths, metadata, and exceptions.
|
|
447
|
+
5. Do not add new exact-mode entrypoints; migrate existing ones to the
|
|
448
|
+
side-by-side namespace with [MIGRATION_FROM_EXACT_MODE.md](MIGRATION_FROM_EXACT_MODE.md).
|
|
449
|
+
6. Benchmark only after semantic parity.
|
|
450
|
+
|
|
451
|
+
Any unsupported feature should fail loudly. A silent approximation is treated
|
|
452
|
+
as a bug in this prototype.
|
|
453
|
+
|
data/deny.toml
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/dry/validation/rust/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'dry-validation-rust'
|
|
7
|
+
spec.version = Dry::Validation::Rust::VERSION
|
|
8
|
+
spec.authors = ['Alexey Tomilov']
|
|
9
|
+
|
|
10
|
+
spec.summary = 'Experimental Rust-backed validation contracts with a dry-validation-like API'
|
|
11
|
+
spec.description = <<~DESCRIPTION
|
|
12
|
+
A hybrid Ruby/Rust validation gem that compiles a documented subset of
|
|
13
|
+
dry-validation's declarative schema DSL into a native Rust execution plan
|
|
14
|
+
while retaining Ruby rule blocks and Ruby-owned dynamic behavior.
|
|
15
|
+
DESCRIPTION
|
|
16
|
+
spec.license = 'MIT'
|
|
17
|
+
spec.required_ruby_version = '>= 3.3'
|
|
18
|
+
spec.homepage = 'https://github.com/alex-tomilov/dry-validation-rust'
|
|
19
|
+
|
|
20
|
+
excluded_package_paths = %w[
|
|
21
|
+
.agents/
|
|
22
|
+
.github/
|
|
23
|
+
benchmark/
|
|
24
|
+
compat/
|
|
25
|
+
docs/
|
|
26
|
+
examples/
|
|
27
|
+
features/
|
|
28
|
+
script/
|
|
29
|
+
spec/
|
|
30
|
+
test/
|
|
31
|
+
].freeze
|
|
32
|
+
|
|
33
|
+
spec.files = Dir.chdir(__dir__) do
|
|
34
|
+
`git ls-files -z`.split("\x0").reject do |path|
|
|
35
|
+
path == 'expected_gem_contents.txt' || path.start_with?(*excluded_package_paths)
|
|
36
|
+
end.sort
|
|
37
|
+
end
|
|
38
|
+
spec.require_paths = ['lib']
|
|
39
|
+
spec.extensions = ['ext/dry_validation_rust/extconf.rb']
|
|
40
|
+
spec.add_dependency 'bigdecimal', '>= 3.1', '< 5.0'
|
|
41
|
+
spec.add_dependency 'rb_sys', '>= 0.9.100', '< 0.10'
|
|
42
|
+
spec.add_development_dependency 'benchmark', '>= 0.3', '< 1.0'
|
|
43
|
+
spec.add_development_dependency 'benchmark-ips', '~> 2.14'
|
|
44
|
+
spec.add_development_dependency 'memory_profiler', '~> 1.1'
|
|
45
|
+
spec.add_development_dependency 'minitest', '~> 6.0'
|
|
46
|
+
spec.add_development_dependency 'rake', '~> 13.1'
|
|
47
|
+
spec.add_development_dependency 'rake-compiler', '~> 1.3'
|
|
48
|
+
spec.add_development_dependency 'rake-compiler-dock', '~> 1.12'
|
|
49
|
+
spec.add_development_dependency 'yard', '~> 0.9'
|
|
50
|
+
|
|
51
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
52
|
+
spec.metadata['source_code_uri'] = 'https://github.com/alex-tomilov/dry-validation-rust'
|
|
53
|
+
spec.metadata['changelog_uri'] = 'https://github.com/alex-tomilov/dry-validation-rust/blob/main/CHANGELOG.md'
|
|
54
|
+
spec.metadata['documentation_uri'] = 'https://github.com/alex-tomilov/dry-validation-rust/blob/main/README.md'
|
|
55
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/alex-tomilov/dry-validation-rust/issues'
|
|
56
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"engine_version":1,"mode":"params","fields":[{"name":"age","required":true,"nullable":false,"filled":true,"type":"integer","member":null,"children":[],"predicates":[{"name":"gteq","argument":18}]}]}
|
data/lib/dry/schema.rb
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The experimental replacement ships only the minimal schema factories needed
|
|
4
|
+
# by its contract DSL. Loading this entrypoint also loads exact compatibility
|
|
5
|
+
# mode; it is not the full upstream dry-schema gem.
|
|
6
|
+
require 'dry/validation'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Validation
|
|
5
|
+
module Rust
|
|
6
|
+
# @api private
|
|
7
|
+
module BlockKeywordParameters
|
|
8
|
+
EMPTY = [].freeze
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def extract(block)
|
|
13
|
+
block.parameters.filter_map do |kind, name|
|
|
14
|
+
name if %i[key keyreq].include?(kind)
|
|
15
|
+
end.freeze
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dry
|
|
4
|
+
module Validation
|
|
5
|
+
module Rust
|
|
6
|
+
# Configures the message backend used by compiled schemas.
|
|
7
|
+
class MessageConfig
|
|
8
|
+
BACKENDS = { yaml: YamlBackend, i18n: I18nBackend }.freeze
|
|
9
|
+
|
|
10
|
+
# @return [:yaml, :i18n, Class] the selected built-in identifier or custom backend class.
|
|
11
|
+
attr_reader :backend
|
|
12
|
+
attr_accessor :default_locale, :top_namespace, :load_paths
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@backend = :yaml
|
|
16
|
+
@default_locale = :en
|
|
17
|
+
@top_namespace = :dry_validation
|
|
18
|
+
@load_paths = []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Selects a built-in backend or a custom {MessageBackend} subclass.
|
|
22
|
+
#
|
|
23
|
+
# @param backend [:yaml, :i18n, Class] backend identifier or adapter class.
|
|
24
|
+
# @raise [ArgumentError] if the backend is unsupported.
|
|
25
|
+
def backend=(backend)
|
|
26
|
+
@backend = BACKENDS.key?(backend) ? backend : validate_backend_class(backend)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def dup
|
|
30
|
+
copy = super
|
|
31
|
+
copy.load_paths = load_paths.dup
|
|
32
|
+
copy
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @api private
|
|
36
|
+
def backend_class
|
|
37
|
+
BACKENDS.fetch(backend, backend)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def validate_backend_class(backend)
|
|
43
|
+
return backend if backend.is_a?(Class) && backend < MessageBackend
|
|
44
|
+
|
|
45
|
+
raise ArgumentError, backend_error(backend)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def backend_error(backend)
|
|
49
|
+
"messages.backend must be :yaml, :i18n, or a MessageBackend subclass; got #{backend.inspect}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class Config
|
|
54
|
+
attr_reader :validate_keys
|
|
55
|
+
attr_accessor :messages
|
|
56
|
+
|
|
57
|
+
def initialize
|
|
58
|
+
@validate_keys = false
|
|
59
|
+
@messages = MessageConfig.new
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def validate_keys=(value)
|
|
63
|
+
@validate_keys = !!value
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def dup
|
|
67
|
+
copy = super
|
|
68
|
+
copy.messages = messages.dup
|
|
69
|
+
copy
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|