-
Notifications
You must be signed in to change notification settings - Fork 1
/
StructureValidator.rakumod
95 lines (83 loc) · 3.84 KB
/
StructureValidator.rakumod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# ABSTRACT: Validate basic data structures against a simple schema
use MUGS::Core;
class X::MUGS::InvalidStructure is X::MUGS {
has Mu $.data is required;
has Str:D $.type is required;
has Str:D $.path is required;
has Str:D $.error is required;
method message() { "$.type.tclc() structure invalid at $.path: $.error (got {$.data.^name})" }
}
role Optional is export {
method ACCEPTS(Mu $other) { self.^mixin_base.ACCEPTS($other) }
}
sub validate-structure($type, $data, $schema, $path = 'root') is export {
use nqp;
my &validate = -> \data, \schema, \path {
if nqp::istype(schema, Optional) && !data.defined {
# Optional matches undefined
}
elsif nqp::istype(schema, Positional) {
X::MUGS::InvalidStructure.new(:$type, :path(path), :data(data),
:error('must be Positional')).throw
unless nqp::istype(data, Positional);
if schema && schema.elems {
my $s := schema[0];
validate(data[$_], $s, path ~ "/$_") for data.keys;
}
elsif nqp::istype(schema, array) {
X::MUGS::InvalidStructure.new(:$type, :path(path), :data(data),
:error("must be {schema.raku}")).throw
unless nqp::istype(data, schema);
}
}
elsif nqp::istype(schema, Associative) {
X::MUGS::InvalidStructure.new(:$type, :path(path), :data(data),
:error('must be Associative')).throw
unless nqp::istype(data, Associative);
validate(data{$_}, schema{$_}, path ~ "/$_") for schema.keys;
}
elsif nqp::istype(schema, Junction) {
my str $jtype = nqp::getattr(nqp::decont(schema), Junction, '$!type');
my $jstates := nqp::getattr(nqp::decont(schema), Junction, '$!eigenstates');
my $path = path ~ "/$jtype\(…)";
if $jtype eq 'any' {
for $jstates -> $state {
try validate(data, $state, $path);
last unless $!;
}
X::MUGS::InvalidStructure.new(:$type, :$path, :data(data),
:error('no matching any junction variant')).throw if $!;
}
elsif $jtype eq 'all' {
validate(data, $_, $path) for $jstates;
}
elsif $jtype eq 'none' {
for $jstates -> $state {
try validate(data, $state, $path);
X::MUGS::InvalidStructure.new(:$type, :$path, :data(data),
:error('matched a none junction variant')).throw unless $!;
}
}
elsif $jtype eq 'one' {
my int $count = 0;
for $jstates -> $state {
try validate(data, $state, $path);
X::MUGS::InvalidStructure.new(:$type, :$path, :data(data),
:error('matched too many one junction variants')).throw if !$! && $count++;
}
X::MUGS::InvalidStructure.new(:$type, :$path, :data(data),
:error('no matching one junction variant')).throw;
}
else {
X::MUGS::InvalidStructure.new(:$type, :$path, :data(data),
:error("schema uses unknown Junction type '$jtype'")).throw;
}
}
else {
X::MUGS::InvalidStructure.new(:$type, :path(path), :data(data),
:error("must be {schema.raku}")).throw
unless data ~~ schema;
}
}
validate($data, $schema, $path)
}