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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +74 -0
  3. data/LICENSE +21 -0
  4. data/NOTICE.md +28 -0
  5. data/README.md +459 -0
  6. data/docs/ARCHITECTURE.md +256 -0
  7. data/docs/COMPATIBILITY.md +198 -0
  8. data/docs/FEASIBILITY.md +207 -0
  9. data/docs/SUPPORT_MATRIX.md +66 -0
  10. data/docs/VERIFICATION.md +128 -0
  11. data/dry-validation-rust.gemspec +58 -0
  12. data/ext/dry_validation_rust/Cargo.lock +809 -0
  13. data/ext/dry_validation_rust/Cargo.toml +44 -0
  14. data/ext/dry_validation_rust/benches/coercion.rs +77 -0
  15. data/ext/dry_validation_rust/benches/full_schema.rs +189 -0
  16. data/ext/dry_validation_rust/benches/plan_compile.rs +37 -0
  17. data/ext/dry_validation_rust/benches/predicates.rs +105 -0
  18. data/ext/dry_validation_rust/extconf.rb +29 -0
  19. data/ext/dry_validation_rust/src/coercion.rs +515 -0
  20. data/ext/dry_validation_rust/src/engine.rs +416 -0
  21. data/ext/dry_validation_rust/src/error.rs +82 -0
  22. data/ext/dry_validation_rust/src/extract_primitive.rs +23 -0
  23. data/ext/dry_validation_rust/src/generated_predicates.rs +33 -0
  24. data/ext/dry_validation_rust/src/lib.rs +228 -0
  25. data/ext/dry_validation_rust/src/plan.rs +611 -0
  26. data/ext/dry_validation_rust/src/predicates.rs +449 -0
  27. data/ext/dry_validation_rust/src/ruby_bridge.rs +78 -0
  28. data/lib/dry/schema.rb +6 -0
  29. data/lib/dry/validation/rust/block_keyword_parameters.rb +20 -0
  30. data/lib/dry/validation/rust/config.rb +74 -0
  31. data/lib/dry/validation/rust/contract/result.rb +180 -0
  32. data/lib/dry/validation/rust/contract/values.rb +73 -0
  33. data/lib/dry/validation/rust/contract.rb +400 -0
  34. data/lib/dry/validation/rust/errors.rb +14 -0
  35. data/lib/dry/validation/rust/evaluator.rb +295 -0
  36. data/lib/dry/validation/rust/failures.rb +57 -0
  37. data/lib/dry/validation/rust/generated_predicates.rb +14 -0
  38. data/lib/dry/validation/rust/macros.rb +45 -0
  39. data/lib/dry/validation/rust/message.rb +41 -0
  40. data/lib/dry/validation/rust/message_backend.rb +115 -0
  41. data/lib/dry/validation/rust/message_set.rb +159 -0
  42. data/lib/dry/validation/rust/native.rb +25 -0
  43. data/lib/dry/validation/rust/path.rb +65 -0
  44. data/lib/dry/validation/rust/path_trie.rb +57 -0
  45. data/lib/dry/validation/rust/result.rb +3 -0
  46. data/lib/dry/validation/rust/rule.rb +62 -0
  47. data/lib/dry/validation/rust/schema/dsl.rb +76 -0
  48. data/lib/dry/validation/rust/schema/field_builder.rb +156 -0
  49. data/lib/dry/validation/rust/schema/field_definition.rb +99 -0
  50. data/lib/dry/validation/rust/schema/predicate_block.rb +56 -0
  51. data/lib/dry/validation/rust/schema/processor_hooks.rb +46 -0
  52. data/lib/dry/validation/rust/schema/result.rb +67 -0
  53. data/lib/dry/validation/rust/schema/ruby_type_processor.rb +44 -0
  54. data/lib/dry/validation/rust/schema.rb +323 -0
  55. data/lib/dry/validation/rust/values.rb +3 -0
  56. data/lib/dry/validation/rust/version.rb +10 -0
  57. data/lib/dry/validation/rust.rb +55 -0
  58. data/lib/dry/validation.rb +66 -0
  59. data/lib/dry-schema.rb +3 -0
  60. data/lib/dry-validation.rb +3 -0
  61. data/lib/dry_validation_rust.rb +3 -0
  62. data/predicates.yml +67 -0
  63. data/rust-toolchain.toml +9 -0
  64. metadata +260 -0
@@ -0,0 +1,228 @@
1
+ use magnus::{
2
+ function, gc::Marker, method, prelude::*, value::Opaque, DataTypeFunctions, Error, RArray,
3
+ RHash, RModule, Ruby, TypedData,
4
+ };
5
+
6
+ mod coercion;
7
+ mod engine;
8
+ mod error;
9
+ mod extract_primitive;
10
+ mod plan;
11
+ mod predicates;
12
+ mod ruby_bridge;
13
+
14
+ /// Entrypoints used only by the standalone `cargo fuzz` harness.
15
+ ///
16
+ /// Keeping this module small ensures the fuzzer exercises the same plan parser
17
+ /// that Ruby uses without requiring a Ruby VM for every generated input.
18
+ pub mod fuzzing {
19
+ pub fn parse_plan(json: &str) -> Result<(), String> {
20
+ crate::plan::deserialize_plan(json).map(|_| ())
21
+ }
22
+ }
23
+
24
+ /// Support for Criterion benchmarks of private native paths.
25
+ ///
26
+ /// This is intentionally hidden from the extension's Ruby API. It lets the
27
+ /// standalone benchmark crate prepare the embedded Ruby runtime once, then
28
+ /// measure the same coercion entrypoint the engine uses.
29
+ #[doc(hidden)]
30
+ pub mod benchmark {
31
+ use magnus::{prelude::*, Error, RHash, Ruby, Value};
32
+
33
+ use crate::{
34
+ coercion::coerce,
35
+ plan::{FieldPlan, Mode, PredicateArg, PredicateOp, PredicatePlan},
36
+ predicates::apply_predicates,
37
+ ruby_bridge::RuntimeClasses,
38
+ };
39
+
40
+ pub struct CoercionRuntime {
41
+ classes: RuntimeClasses,
42
+ }
43
+
44
+ impl CoercionRuntime {
45
+ pub fn new(ruby: &Ruby) -> Result<Self, Error> {
46
+ ruby.eval::<Value>("require 'date'; require 'bigdecimal'")?;
47
+ Ok(Self {
48
+ classes: RuntimeClasses::all(ruby)?,
49
+ })
50
+ }
51
+
52
+ pub fn coerce(&self, ruby: &Ruby, kind: &str, source: &str) -> Result<(), Error> {
53
+ let value = ruby.str_new(source).as_value();
54
+ coerce(ruby, &self.classes, Mode::Params, kind, value).map(|_| ())
55
+ }
56
+ }
57
+
58
+ /// Prepared native engine used by the end-to-end Criterion benchmark.
59
+ pub struct FullSchemaRuntime {
60
+ engine: super::Engine,
61
+ }
62
+
63
+ impl FullSchemaRuntime {
64
+ pub fn new(ruby: &Ruby, plan_json: String) -> Result<Self, Error> {
65
+ Ok(Self {
66
+ engine: super::Engine::new(ruby, plan_json)?,
67
+ })
68
+ }
69
+
70
+ pub fn call(&self, input: RHash) -> Result<(), Error> {
71
+ self.engine.call(input).map(|_| ())
72
+ }
73
+
74
+ pub fn error_count(&self, ruby: &Ruby, input: RHash) -> Result<usize, Error> {
75
+ self.engine
76
+ .call(input)
77
+ .map(|result| super::SchemaResult::errors(ruby, &result).len())
78
+ }
79
+ }
80
+
81
+ /// Reusable predicate plans for Criterion benchmarks of native predicate paths.
82
+ pub struct PredicateRuntime {
83
+ fields: Vec<FieldPlan>,
84
+ }
85
+
86
+ #[derive(Clone, Copy)]
87
+ pub enum PredicateCase {
88
+ GtInteger,
89
+ GteqInteger,
90
+ LtInteger,
91
+ LteqInteger,
92
+ GtFloat,
93
+ GteqFloat,
94
+ LtFloat,
95
+ LteqFloat,
96
+ Size,
97
+ MinSize,
98
+ MaxSize,
99
+ Odd,
100
+ Even,
101
+ }
102
+
103
+ impl Default for PredicateRuntime {
104
+ fn default() -> Self {
105
+ Self {
106
+ fields: vec![
107
+ predicate_field(PredicateOp::Gt, PredicateArg::Int(18)),
108
+ predicate_field(PredicateOp::Gteq, PredicateArg::Int(18)),
109
+ predicate_field(PredicateOp::Lt, PredicateArg::Int(20)),
110
+ predicate_field(PredicateOp::Lteq, PredicateArg::Int(19)),
111
+ predicate_field(PredicateOp::Gt, PredicateArg::Float(1.25)),
112
+ predicate_field(PredicateOp::Gteq, PredicateArg::Float(1.5)),
113
+ predicate_field(PredicateOp::Lt, PredicateArg::Float(1.75)),
114
+ predicate_field(PredicateOp::Lteq, PredicateArg::Float(1.5)),
115
+ predicate_field(PredicateOp::Size, PredicateArg::Int(3)),
116
+ predicate_field(PredicateOp::MinSize, PredicateArg::Int(3)),
117
+ predicate_field(PredicateOp::MaxSize, PredicateArg::Int(3)),
118
+ predicate_field(PredicateOp::Odd, PredicateArg::Bool(true)),
119
+ predicate_field(PredicateOp::Even, PredicateArg::Bool(true)),
120
+ ],
121
+ }
122
+ }
123
+ }
124
+
125
+ impl PredicateRuntime {
126
+ pub fn new() -> Self {
127
+ Self::default()
128
+ }
129
+
130
+ pub fn evaluate(
131
+ &self,
132
+ ruby: &Ruby,
133
+ predicate_case: PredicateCase,
134
+ value: Value,
135
+ ) -> Result<(), Error> {
136
+ let mut errors = Vec::new();
137
+ apply_predicates(
138
+ ruby,
139
+ &self.fields[predicate_case.index()],
140
+ value,
141
+ &[],
142
+ &mut errors,
143
+ )
144
+ }
145
+ }
146
+
147
+ impl PredicateCase {
148
+ const fn index(self) -> usize {
149
+ match self {
150
+ Self::GtInteger => 0,
151
+ Self::GteqInteger => 1,
152
+ Self::LtInteger => 2,
153
+ Self::LteqInteger => 3,
154
+ Self::GtFloat => 4,
155
+ Self::GteqFloat => 5,
156
+ Self::LtFloat => 6,
157
+ Self::LteqFloat => 7,
158
+ Self::Size => 8,
159
+ Self::MinSize => 9,
160
+ Self::MaxSize => 10,
161
+ Self::Odd => 11,
162
+ Self::Even => 12,
163
+ }
164
+ }
165
+ }
166
+
167
+ fn predicate_field(op: PredicateOp, argument: PredicateArg) -> FieldPlan {
168
+ FieldPlan {
169
+ name: None,
170
+ required: false,
171
+ nullable: false,
172
+ filled: false,
173
+ kind: "any".to_owned(),
174
+ member: None,
175
+ children: Vec::new(),
176
+ predicates: vec![PredicatePlan {
177
+ name: "benchmark".to_owned(),
178
+ op,
179
+ argument,
180
+ }],
181
+ }
182
+ }
183
+ }
184
+
185
+ use engine::Engine;
186
+
187
+ #[derive(TypedData)]
188
+ #[magnus(
189
+ class = "Dry::Validation::Rust::Native::SchemaResult",
190
+ free_immediately,
191
+ mark,
192
+ size
193
+ )]
194
+ pub(crate) struct SchemaResult {
195
+ output: Opaque<RHash>,
196
+ errors: Opaque<RArray>,
197
+ }
198
+
199
+ impl DataTypeFunctions for SchemaResult {
200
+ fn mark(&self, marker: &Marker) {
201
+ marker.mark(self.output);
202
+ marker.mark(self.errors);
203
+ }
204
+ }
205
+
206
+ impl SchemaResult {
207
+ fn output(ruby: &Ruby, result: &Self) -> RHash {
208
+ ruby.get_inner(result.output)
209
+ }
210
+
211
+ fn errors(ruby: &Ruby, result: &Self) -> RArray {
212
+ ruby.get_inner(result.errors)
213
+ }
214
+ }
215
+
216
+ #[magnus::init(name = "native")]
217
+ fn init(ruby: &Ruby) -> Result<(), Error> {
218
+ let native: RModule = ruby.eval("Dry::Validation::Rust::Native")?;
219
+ let class = native.define_class("Engine", ruby.class_object())?;
220
+ class.define_singleton_method("new", function!(Engine::new, 1))?;
221
+ class.define_method("call", method!(Engine::call, 1))?;
222
+ class.define_method("field_count", method!(Engine::field_count, 0))?;
223
+ class.define_method("plan_bytes", method!(Engine::plan_bytes, 0))?;
224
+ let result_class = native.define_class("SchemaResult", ruby.class_object())?;
225
+ result_class.define_method("output", method!(SchemaResult::output, 0))?;
226
+ result_class.define_method("errors", method!(SchemaResult::errors, 0))?;
227
+ Ok(())
228
+ }