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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +13 -0
  3. data/.markdownlint.yml +9 -0
  4. data/.rubocop.yml +124 -0
  5. data/.ruby-version +1 -0
  6. data/.tool-versions +1 -0
  7. data/.yardopts +4 -0
  8. data/AGENTS.md +376 -0
  9. data/CHANGELOG.md +71 -0
  10. data/CODE_OF_CONDUCT.md +42 -0
  11. data/CONTRIBUTING.md +159 -0
  12. data/Cargo.lock +809 -0
  13. data/Cargo.toml +10 -0
  14. data/GOVERNANCE.md +68 -0
  15. data/Gemfile +15 -0
  16. data/Gemfile.lock +132 -0
  17. data/NOTICE.md +5 -5
  18. data/README.md +206 -181
  19. data/Rakefile +350 -0
  20. data/SECURITY.md +98 -0
  21. data/SECURITY_AUDIT.md +33 -0
  22. data/SUMMARY.md +30 -0
  23. data/SUPPORT.md +67 -0
  24. data/book.toml +9 -0
  25. data/codecov.yml +11 -0
  26. data/compatibility.yml +453 -0
  27. data/deny.toml +7 -0
  28. data/dry-validation-rust.gemspec +21 -23
  29. data/ext/dry_validation_rust/Cargo.toml +7 -7
  30. data/ext/dry_validation_rust/build.rs +5 -0
  31. data/ext/dry_validation_rust/extconf.rb +12 -0
  32. data/ext/dry_validation_rust/fuzz/.gitignore +5 -0
  33. data/ext/dry_validation_rust/fuzz/Cargo.toml +19 -0
  34. data/ext/dry_validation_rust/fuzz/corpus/parse_plan/basic_params.json +1 -0
  35. data/ext/dry_validation_rust/fuzz/fuzz_targets/parse_plan.rs +9 -0
  36. data/lib/dry/validation/rust/path_trie.rb +15 -8
  37. data/lib/dry/validation/rust/schema/dsl.rb +8 -0
  38. data/lib/dry/validation/rust/schema/field_definition.rb +3 -1
  39. data/lib/dry/validation/rust/schema/predicate_block.rb +2 -1
  40. data/lib/dry/validation/rust/schema/ruby_type_processor.rb +38 -23
  41. data/lib/dry/validation/rust/version.rb +1 -1
  42. data/supply-chain/audits.toml +4 -0
  43. data/supply-chain/config.toml +368 -0
  44. data/supply-chain/imports.lock +4 -0
  45. data/support_matrix.yml +9 -0
  46. metadata +64 -25
  47. data/docs/ARCHITECTURE.md +0 -256
  48. data/docs/COMPATIBILITY.md +0 -198
  49. data/docs/FEASIBILITY.md +0 -207
  50. data/docs/SUPPORT_MATRIX.md +0 -66
  51. 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
@@ -0,0 +1,7 @@
1
+ [advisories]
2
+ version = 2
3
+ yanked = "warn"
4
+
5
+ [licenses]
6
+ version = 2
7
+ allow = ["MIT", "Apache-2.0", "BSD-3-Clause", "ISC", "Unicode-3.0"]
@@ -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
- spec.files = [
21
- 'CHANGELOG.md',
22
- 'LICENSE',
23
- 'NOTICE.md',
24
- 'predicates.yml',
25
- 'README.md',
26
- 'dry-validation-rust.gemspec',
27
- 'rust-toolchain.toml',
28
- 'docs/ARCHITECTURE.md',
29
- 'docs/COMPATIBILITY.md',
30
- 'docs/FEASIBILITY.md',
31
- 'docs/SUPPORT_MATRIX.md',
32
- 'docs/VERIFICATION.md',
33
- Dir['lib/**/*.rb'],
34
- 'ext/dry_validation_rust/Cargo.lock',
35
- 'ext/dry_validation_rust/Cargo.toml',
36
- 'ext/dry_validation_rust/extconf.rb',
37
- Dir['ext/dry_validation_rust/benches/**/*.rs'],
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', '~> 0.9'
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.5"
3
+ version = "0.1.0-pre.6"
4
4
  edition = "2021"
5
5
  rust-version = "1.75"
6
6
  license = "MIT"
7
- publish = false
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"
@@ -0,0 +1,5 @@
1
+ fn main() {
2
+ if let Ok(libdir) = std::env::var("DEP_RB_RBCONFIG_LIBDIR") {
3
+ println!("cargo:rustc-link-arg=-Wl,-rpath,{}", libdir);
4
+ }
5
+ }
@@ -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,5 @@
1
+ /artifacts/
2
+ /target/
3
+ /Cargo.lock
4
+ /corpus/parse_plan/*
5
+ !/corpus/parse_plan/basic_params.json
@@ -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}]}]}
@@ -0,0 +1,9 @@
1
+ #![no_main]
2
+
3
+ use libfuzzer_sys::fuzz_target;
4
+
5
+ fuzz_target!(|data: &[u8]| {
6
+ if let Ok(json) = std::str::from_utf8(data) {
7
+ native::fuzzing::parse_plan(json);
8
+ }
9
+ });
@@ -31,7 +31,21 @@ module Dry
31
31
  end
32
32
 
33
33
  def freeze
34
- freeze_node(@root)
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 natively; move it to a contract rule"
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)