File Coverage

blib/lib/ThaiSchema.pm
Criterion Covered Total %
statement 157 171 91.8
branch 42 50 84.0
condition 18 24 75.0
subroutine 57 68 83.8
pod 6 9 66.6
total 280 322 86.9


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