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
|
@@ -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
|
+
}
|