File Coverage

blib/lib/ThaiSchema.pm
Criterion Covered Total %
statement 156 170 91.7
branch 42 50 84.0
condition 18 24 75.0
subroutine 56 67 83.5
pod 6 9 66.6
total 278 320 86.8


line stmt bran cond sub pod time code
1             package ThaiSchema;
2 4     4   204895 use strict;
  4         8  
  4         157  
3 4     4   23 use warnings;
  4         10  
  4         114  
4 4     4   227 use 5.010001;
  4         19  
  4         412  
5             our $VERSION = '0.09';
6 4     4   3756 use parent qw/Exporter/;
  4         1416  
  4         23  
7              
8             our $STRICT = 0;
9             our $ALLOW_EXTRA = 0;
10             our @_ERRORS;
11             our $_NAME = '';
12              
13             our @EXPORT = qw/
14             match_schema
15             type_int type_str type_number type_hash type_array type_maybe type_bool type_null
16             /;
17              
18 4     4   6394 use JSON;
  4         165487  
  4         27  
19 4     4   666 use B;
  4         7  
  4         229  
20 4     4   29445 use Data::Dumper;
  4         76589  
  4         402  
21              
22 4     4   44 use Scalar::Util qw/blessed/;
  4         8  
  4         19814  
23              
24             sub match_schema {
25 5     5 0 12 local @_ERRORS;
26 5         12 local $_NAME = '';
27 5         15 my $ok = _match_schema(@_);
28 5 50       30 return wantarray ? ($ok, \@_ERRORS) : $ok;
29             }
30              
31             sub _match_schema {
32 18     18   37 my ($value, $schema) = @_;
33 18 100       61 if (ref $schema eq 'HASH') {
34 6         102 $schema = ThaiSchema::Hash->new(schema => $schema);
35             }
36 18 50 33     281 if (blessed $schema && $schema->can('match')) {
37 18 100       55 if ($schema->match($value)) {
38 7         43 return 1;
39             } else {
40 11 100       34 if ($schema->error) {
41 5         18 push @_ERRORS, $_NAME .' '. $schema->error();
42             }
43 11         50 return 0;
44             }
45             } else {
46 0         0 die "Unsupported schema: " . ref $schema;
47             }
48             }
49              
50             sub type_str() {
51 13     13 1 5660 ThaiSchema::Str->new();
52             }
53              
54             sub type_int() {
55 40     40 1 120505 ThaiSchema::Int->new();
56             }
57              
58             sub type_maybe($) {
59 3     3 1 27 ThaiSchema::Maybe->new(schema => shift);
60             }
61              
62             sub type_number() {
63 10     10 0 1499 ThaiSchema::Number->new();
64             }
65              
66             sub type_hash($) {
67 22     22 1 7061 ThaiSchema::Hash->new(schema => shift);
68             }
69              
70             sub type_array(;$) {
71 26     26 1 12332 ThaiSchema::Array->new(schema => shift);
72             }
73              
74             sub type_bool() {
75 15     15 1 29326 ThaiSchema::Bool->new()
76             }
77              
78             sub type_null() {
79 0     0 0 0 ThaiSchema::Null->new()
80             }
81              
82             package ThaiSchema::Extra;
83             # dummy object for extra key.
84 4     4   41 use parent qw/ThaiSchema::Base/;
  4         11  
  4         42  
85              
86 3     3   12 sub is_array { 1 }
87 1     1   5 sub is_hash { 1 }
88 2     2   8 sub is_bool { 1 }
89 1     1   4 sub is_number { 1 }
90 1     1   6 sub is_integer { 1 }
91 0     0   0 sub is_null { 1 }
92 1     1   4 sub is_string { 1 }
93              
94 4     4   14 sub schema { ThaiSchema::Extra->new() }
95              
96             package ThaiSchema::Hash;
97              
98 4     4   719 use parent qw/ThaiSchema::Base/;
  4         6  
  4         44  
99              
100             sub schema {
101 21     21   105 my ($self) = @_;
102 21         159 return $self->{schema};
103             }
104              
105             sub match {
106 13     13   24 my ($self, $value) = @_;
107 13 100       167 return 0 unless ref $value eq 'HASH';
108              
109 12         32 my $schema = $self->{schema};
110              
111 12         18 my $fail = 0;
112 12         33 my %rest_keys = map { $_ => 1 } keys %$value;
  12         41  
113 12         34 for my $key (keys %$schema) {
114 11 100       33 local $_NAME = $_NAME ? "$_NAME.$key" : $key;
115 11 100       47 if (not ThaiSchema::_match_schema($value->{$key}, $schema->{$key})) {
116 5         7 $fail++;
117             }
118 11         40 delete $rest_keys{$key};
119             }
120 12 100 100     47 if (%rest_keys && !$ThaiSchema::ALLOW_EXTRA) {
121 1         2 push @_ERRORS, 'have extra keys';
122 1         5 return 0;
123             }
124 11         42 return !$fail;
125             }
126              
127             sub error {
128 4     4   11 return ();
129             }
130              
131 21     21   69 sub is_hash { 1 }
132              
133             package ThaiSchema::Array;
134 4     4   1411 use parent qw/ThaiSchema::Base/;
  4         15  
  4         16  
135              
136 23     23   82 sub is_array { 1 }
137              
138 23     23   96 sub schema { shift->{schema} }
139              
140             sub match {
141 4     4   7 my ($self, $value) = @_;
142 4 100       25 return 0 unless ref $value eq 'ARRAY';
143 3         19 my $schema = $self->{schema};
144 3 100       15 if (defined $schema) {
145 2         4 for (my $i=0; $i<@{$value}; $i++) {
  2         7  
146 2         9 local $_NAME = $_NAME . "[$i]";
147 2         4 my $elem = $value->[$i];
148 2 50       8 return 0 unless ThaiSchema::_match_schema($elem, $schema);
149             }
150             }
151 1         5 return 1;
152             }
153              
154             sub error {
155 2     2   7 return ();
156             }
157              
158             package ThaiSchema::Maybe;
159 4     4   1006 use parent qw/ThaiSchema::Base/;
  4         9  
  4         16  
160              
161             sub match {
162 0     0   0 my ($self, $value) = @_;
163 0 0       0 return 1 unless defined $value;
164 0         0 return $self->{schema}->match($value);
165             }
166 0     0   0 sub error { "is not maybe " . $_[0]->{schema}->name }
167              
168             sub name {
169 1     1   7 return 'Maybe[' . $_[0]->{schema}->name .']';
170             }
171              
172 1     1   6 sub is_null { 1 }
173              
174             for my $method (qw/is_array is_bool is_hash is_number is_integer is_string/) {
175 4     4   876 no strict 'refs';
  4         20  
  4         410  
176 3     3   18 *{__PACKAGE__ . "::$method"} = sub { $_[0]->{schema}->$method() };
177             }
178              
179             package ThaiSchema::Str;
180 4     4   20 use parent qw/ThaiSchema::Base/;
  4         7  
  4         17  
181              
182             sub match {
183 12     12   20 my ($self, $value) = @_;
184 12 50       27 return 0 unless defined $value;
185 12 100       22 if ($ThaiSchema::STRICT) {
186 4         13 my $b_obj = B::svref_2object(\$value);
187 4         9 my $flags = $b_obj->FLAGS;
188 4 100 66     21 return 0 if $flags & ( B::SVp_IOK | B::SVp_NOK ) and !( $flags & B::SVp_POK ); # SvTYPE is IV or NV?
189 2         8 return 1;
190             } else {
191 8         29 return not ref $value;
192             }
193             }
194 0     0   0 sub error { "is not str" }
195 1     1   4 sub is_string { 1 }
196              
197             package ThaiSchema::Int;
198 4     4   994 use parent qw/ThaiSchema::Base/;
  4         8  
  4         15  
199             sub match {
200 16     16   25 my ($self, $value) = @_;
201 16 100       42 return 0 unless defined $value;
202 15 100       96 if ($ThaiSchema::STRICT) {
203 5         18 my $b_obj = B::svref_2object(\$value);
204 5         382 my $flags = $b_obj->FLAGS;
205 5 100 100     35 return 1 if $flags & ( B::SVp_IOK | B::SVp_NOK ) and int($value) == $value and !( $flags & B::SVp_POK ); # SvTYPE is IV or NV?
      66        
206 3         14 return 0;
207             } else {
208 10         80 return $value =~ /\A(?:[1-9][0-9]*|0)\z/;
209             }
210             }
211 10     10   32 sub error { "is not int" }
212 12     12   41 sub is_number { 1 }
213 12     12   90 sub is_integer { 1 }
214              
215             package ThaiSchema::Number;
216 4     4   1111 use parent qw/ThaiSchema::Base/;
  4         7  
  4         16  
217 4     4   203 use Scalar::Util ();
  4         8  
  4         2541  
218 0     0   0 sub is_number { 1 }
219             sub match {
220 10     10   19 my ($self, $value) = @_;
221 10 50       105 return 0 unless defined $value;
222 10 100       20 if ($ThaiSchema::STRICT) {
223 5         20 my $b_obj = B::svref_2object(\$value);
224 5         13 my $flags = $b_obj->FLAGS;
225 5 100 66     28 return 1 if $flags & ( B::SVp_IOK | B::SVp_NOK ) and !( $flags & B::SVp_POK ); # SvTYPE is IV or NV?
226 3         13 return 0;
227             } else {
228 5         27 return Scalar::Util::looks_like_number($value);
229             }
230             }
231 0     0   0 sub error { 'is not number' }
232              
233             package ThaiSchema::Bool;
234 4     4   28 use parent qw/ThaiSchema::Base/;
  4         7  
  4         21  
235 3     3   11 sub is_bool { 1 }
236 4     4   280 use JSON;
  4         15  
  4         35  
237             sub match {
238 6     6   17 my ($self, $value) = @_;
239 6 50       15 return 0 unless defined $value;
240 6 100       20 return 1 if JSON::is_bool($value);
241 4 100 100     50 return 1 if ref($value) eq 'SCALAR' && ($$value eq 1 || $$value eq 0);
      66        
242 2         9 return 0;
243             }
244 0     0     sub error { 'is not bool' }
245              
246             package ThaiSchema::Null;
247 4     4   1132 use parent qw/ThaiSchema::Base/;
  4         8  
  4         31  
248 0     0     sub is_null { 1 }
249             sub match {
250 0     0     die "Not implemented.";
251             }
252 0     0     sub error { 'is not null' }
253              
254             1;
255             __END__