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/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
data/dry-validation-rust.gemspec
CHANGED
|
@@ -17,34 +17,32 @@ Gem::Specification.new do |spec|
|
|
|
17
17
|
spec.required_ruby_version = '>= 3.3'
|
|
18
18
|
spec.homepage = 'https://github.com/alex-tomilov/dry-validation-rust'
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
Dir['ext/dry_validation_rust/src/**/*.rs']
|
|
39
|
-
].flatten.sort
|
|
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
|
|
40
38
|
spec.require_paths = ['lib']
|
|
41
39
|
spec.extensions = ['ext/dry_validation_rust/extconf.rb']
|
|
42
40
|
spec.add_dependency 'bigdecimal', '>= 3.1', '< 5.0'
|
|
43
|
-
spec.add_dependency 'rb_sys', '
|
|
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
44
|
spec.add_development_dependency 'memory_profiler', '~> 1.1'
|
|
45
45
|
spec.add_development_dependency 'minitest', '~> 6.0'
|
|
46
|
-
spec.add_development_dependency 'mutex_m', '~> 0.2'
|
|
47
|
-
spec.add_development_dependency 'ostruct', '~> 0.6'
|
|
48
46
|
spec.add_development_dependency 'rake', '~> 13.1'
|
|
49
47
|
spec.add_development_dependency 'rake-compiler', '~> 1.3'
|
|
50
48
|
spec.add_development_dependency 'rake-compiler-dock', '~> 1.12'
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "dry_validation_rust_native"
|
|
3
|
-
version = "0.1.0-pre.
|
|
3
|
+
version = "0.1.0-pre.6"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
rust-version = "1.75"
|
|
6
6
|
license = "MIT"
|
|
7
|
-
|
|
7
|
+
description = "Native validation engine for dry-validation-rust"
|
|
8
|
+
repository = "https://github.com/alex-tomilov/dry-validation-rust"
|
|
9
|
+
homepage = "https://github.com/alex-tomilov/dry-validation-rust"
|
|
10
|
+
documentation = "https://docs.rs/dry_validation_rust_native"
|
|
11
|
+
keywords = ["ruby", "validation", "dry-validation"]
|
|
12
|
+
categories = ["development-tools"]
|
|
8
13
|
|
|
9
14
|
[lib]
|
|
10
15
|
name = "native"
|
|
@@ -37,8 +42,3 @@ harness = false
|
|
|
37
42
|
[[bench]]
|
|
38
43
|
name = "full_schema"
|
|
39
44
|
harness = false
|
|
40
|
-
|
|
41
|
-
[profile.release]
|
|
42
|
-
codegen-units = 1
|
|
43
|
-
lto = "thin"
|
|
44
|
-
strip = "debuginfo"
|
|
@@ -14,12 +14,24 @@ rescue LoadError
|
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
# rb-sys-dock runs a MinGW Ruby inside a Linux container. Its Cargo binary must
|
|
18
|
+
# use the Linux-host stable toolchain and cross-compile through CARGO_BUILD_TARGET.
|
|
19
|
+
# The container's privilege wrapper strips RB_SYS_DOCK_TMPDIR, but preserves
|
|
20
|
+
# the requested target as RUBY_TARGET.
|
|
21
|
+
if ENV['RUBY_TARGET'] == 'x64-mingw-ucrt'
|
|
22
|
+
rustup_toolchain = 'stable-x86_64-unknown-linux-gnu'
|
|
23
|
+
elsif RUBY_PLATFORM.include?('mingw')
|
|
24
|
+
ENV['RUSTUP_TOOLCHAIN'] ||= '1.75.0-x86_64-pc-windows-gnu'
|
|
25
|
+
rustup_toolchain = ENV.fetch('RUSTUP_TOOLCHAIN')
|
|
26
|
+
end
|
|
27
|
+
|
|
17
28
|
create_rust_makefile('dry_validation_rust/native') do |config|
|
|
18
29
|
config.profile = ENV.fetch('RB_SYS_CARGO_PROFILE', 'release').to_sym
|
|
19
30
|
config.ext_dir = '.'
|
|
20
31
|
config.env = {
|
|
21
32
|
'BINDGEN_EXTRA_CLANG_ARGS' => '-include stdbool.h'
|
|
22
33
|
}
|
|
34
|
+
config.env['RUSTUP_TOOLCHAIN'] = rustup_toolchain if defined?(rustup_toolchain)
|
|
23
35
|
config.extra_rustup_targets = %w[
|
|
24
36
|
aarch64-unknown-linux-gnu
|
|
25
37
|
x86_64-apple-darwin
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "dry_validation_rust_native-fuzz"
|
|
3
|
+
version = "0.0.0"
|
|
4
|
+
publish = false
|
|
5
|
+
edition = "2024"
|
|
6
|
+
|
|
7
|
+
[package.metadata]
|
|
8
|
+
cargo-fuzz = true
|
|
9
|
+
|
|
10
|
+
[dependencies]
|
|
11
|
+
libfuzzer-sys = "0.4"
|
|
12
|
+
native = { package = "dry_validation_rust_native", path = ".." }
|
|
13
|
+
|
|
14
|
+
[[bin]]
|
|
15
|
+
name = "parse_plan"
|
|
16
|
+
path = "fuzz_targets/parse_plan.rs"
|
|
17
|
+
test = false
|
|
18
|
+
doc = false
|
|
19
|
+
bench = false
|
|
@@ -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}]}]}
|
|
@@ -31,7 +31,21 @@ module Dry
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def freeze
|
|
34
|
-
|
|
34
|
+
return super if @root.empty?
|
|
35
|
+
|
|
36
|
+
stack = [[@root, false]]
|
|
37
|
+
until stack.empty?
|
|
38
|
+
node, visited = stack.pop
|
|
39
|
+
if visited
|
|
40
|
+
node.freeze
|
|
41
|
+
next
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
stack << [node, true]
|
|
45
|
+
node.each_value do |child|
|
|
46
|
+
stack << [child, false] if child.is_a?(Hash)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
35
49
|
super
|
|
36
50
|
end
|
|
37
51
|
|
|
@@ -44,13 +58,6 @@ module Dry
|
|
|
44
58
|
def hash
|
|
45
59
|
@root.hash
|
|
46
60
|
end
|
|
47
|
-
|
|
48
|
-
private
|
|
49
|
-
|
|
50
|
-
def freeze_node(node)
|
|
51
|
-
node.each_value { |child| freeze_node(child) if child.is_a?(Hash) }
|
|
52
|
-
node.freeze
|
|
53
|
-
end
|
|
54
61
|
end
|
|
55
62
|
end
|
|
56
63
|
end
|
|
@@ -50,6 +50,14 @@ module Dry
|
|
|
50
50
|
self
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
# The upstream filtering DSL has no equivalent in this compatibility slice.
|
|
54
|
+
# @raise [UnsupportedFeatureError] with migration guidance.
|
|
55
|
+
def filter(*)
|
|
56
|
+
raise UnsupportedFeatureError,
|
|
57
|
+
'the schema filtering DSL is not supported; filter result.to_h explicitly. ' \
|
|
58
|
+
'See: https://github.com/alex-tomilov/dry-validation-rust/blob/main/docs/MIGRATION_RECIPES.md#schema-filtering-dsl'
|
|
59
|
+
end
|
|
60
|
+
|
|
53
61
|
def compile(validate_keys: false, messages: MessageConfig.new)
|
|
54
62
|
Schema.new(
|
|
55
63
|
mode: mode, fields: fields, before_hooks: before_hooks, after_hooks: after_hooks,
|
|
@@ -35,7 +35,9 @@ module Dry
|
|
|
35
35
|
normalized_name = name.to_s.delete_suffix('?').to_sym
|
|
36
36
|
unless (NATIVE_PREDICATES | RUBY_PREDICATES).include?(normalized_name)
|
|
37
37
|
raise UnsupportedFeatureError,
|
|
38
|
-
"predicate #{normalized_name.inspect} is not supported
|
|
38
|
+
"predicate #{normalized_name.inspect} is not supported; " \
|
|
39
|
+
'use a supported predicate or a contract rule. ' \
|
|
40
|
+
'See: https://github.com/alex-tomilov/dry-validation-rust/blob/main/docs/MIGRATION_RECIPES.md#uuid-and-other-dry-logic-predicates'
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
predicates << Predicate.new(name: normalized_name, argument: argument)
|