| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Poz::Types::object; |
|
2
|
11
|
|
|
11
|
|
210
|
use 5.032; |
|
|
11
|
|
|
|
|
41
|
|
|
3
|
11
|
|
|
11
|
|
59
|
use strict; |
|
|
11
|
|
|
|
|
21
|
|
|
|
11
|
|
|
|
|
283
|
|
|
4
|
11
|
|
|
11
|
|
47
|
use warnings; |
|
|
11
|
|
|
|
|
22
|
|
|
|
11
|
|
|
|
|
565
|
|
|
5
|
11
|
|
|
11
|
|
64
|
use Carp (); |
|
|
11
|
|
|
|
|
21
|
|
|
|
11
|
|
|
|
|
350
|
|
|
6
|
11
|
|
|
11
|
|
57
|
use Try::Tiny; |
|
|
11
|
|
|
|
|
20
|
|
|
|
11
|
|
|
|
|
1041
|
|
|
7
|
11
|
|
|
11
|
|
62
|
use parent 'Poz::Types'; |
|
|
11
|
|
|
|
|
20
|
|
|
|
11
|
|
|
|
|
78
|
|
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
sub new { |
|
10
|
13
|
|
|
13
|
1
|
22
|
my ($class, $struct) = @_; |
|
11
|
13
|
|
|
|
|
67
|
my $self = bless { |
|
12
|
|
|
|
|
|
|
__struct__ => {}, |
|
13
|
|
|
|
|
|
|
__as__ => undef, |
|
14
|
|
|
|
|
|
|
__is__ => undef, |
|
15
|
|
|
|
|
|
|
}, $class; |
|
16
|
13
|
|
|
|
|
41
|
for my $key (keys %$struct) { |
|
17
|
24
|
|
|
|
|
35
|
my $v = $struct->{$key}; |
|
18
|
24
|
50
|
|
|
|
80
|
if ($v->isa('Poz::Types')) { |
|
19
|
24
|
|
|
|
|
57
|
$self->{__struct__}{$key} = $v; |
|
20
|
|
|
|
|
|
|
} |
|
21
|
|
|
|
|
|
|
} |
|
22
|
13
|
|
|
|
|
48
|
return $self; |
|
23
|
|
|
|
|
|
|
} |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
sub as { |
|
26
|
7
|
|
|
7
|
1
|
12
|
my ($self, $typename) = @_; |
|
27
|
7
|
|
|
|
|
11
|
$self->{__as__} = $typename; |
|
28
|
7
|
|
|
|
|
17
|
return $self; |
|
29
|
|
|
|
|
|
|
} |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
sub is { |
|
32
|
1
|
|
|
1
|
1
|
3
|
my ($self, $typename) = @_; |
|
33
|
1
|
|
|
|
|
3
|
$self->{__is__} = $typename; |
|
34
|
1
|
|
|
|
|
3
|
return $self; |
|
35
|
|
|
|
|
|
|
} |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
sub constructor { |
|
38
|
1
|
|
|
1
|
1
|
3
|
my ($self) = @_; |
|
39
|
1
|
|
|
|
|
2
|
my $caller_class = caller(); |
|
40
|
11
|
|
|
11
|
|
3801
|
no strict 'refs'; |
|
|
11
|
|
|
|
|
22
|
|
|
|
11
|
|
|
|
|
9079
|
|
|
41
|
1
|
|
|
|
|
10
|
*{"$caller_class\::new"} = sub { |
|
42
|
1
|
|
|
1
|
|
14
|
my ($class, %args) = @_; |
|
43
|
1
|
|
|
|
|
7
|
return $self |
|
44
|
|
|
|
|
|
|
->as($caller_class) |
|
45
|
|
|
|
|
|
|
->parse({%args}); |
|
46
|
|
|
|
|
|
|
} |
|
47
|
1
|
|
|
|
|
16
|
} |
|
48
|
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
sub parse { |
|
50
|
14
|
|
|
14
|
1
|
4821
|
my ($self, $data) = @_; |
|
51
|
14
|
|
|
|
|
77
|
my ($valid, $errors) = $self->safe_parse($data); |
|
52
|
14
|
100
|
|
|
|
29
|
if ($errors) { |
|
53
|
3
|
|
|
|
|
10
|
my $error_message = _errors_to_string($errors); |
|
54
|
3
|
|
|
|
|
49
|
Carp::croak($error_message); |
|
55
|
|
|
|
|
|
|
} |
|
56
|
11
|
|
|
|
|
36
|
return $valid; |
|
57
|
|
|
|
|
|
|
} |
|
58
|
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
sub safe_parse { |
|
60
|
19
|
100
|
|
19
|
1
|
4843
|
Carp::croak "Must handle error" unless wantarray; |
|
61
|
|
|
|
|
|
|
|
|
62
|
18
|
|
|
|
|
32
|
my ($self, $data) = @_; |
|
63
|
18
|
|
|
|
|
29
|
my @errors = (); |
|
64
|
18
|
|
|
|
|
29
|
my $valid = {}; |
|
65
|
18
|
100
|
|
|
|
41
|
if (!_is_hashref_or_object($data)) { |
|
66
|
1
|
|
|
|
|
6
|
push @errors, { |
|
67
|
|
|
|
|
|
|
key => '(root)', |
|
68
|
|
|
|
|
|
|
error => "Invalid data: is not hashref" |
|
69
|
|
|
|
|
|
|
}; |
|
70
|
|
|
|
|
|
|
} else { |
|
71
|
17
|
100
|
|
|
|
50
|
if ($self->{__is__}) { |
|
72
|
2
|
|
|
|
|
4
|
my $is = $self->{__is__}; |
|
73
|
2
|
100
|
|
|
|
11
|
if (!$data->isa($is)) { |
|
74
|
1
|
|
|
|
|
8
|
push @errors, { |
|
75
|
|
|
|
|
|
|
key => '(root)', |
|
76
|
|
|
|
|
|
|
error => "Invalid data: is not $is" |
|
77
|
|
|
|
|
|
|
}; |
|
78
|
|
|
|
|
|
|
} |
|
79
|
|
|
|
|
|
|
} |
|
80
|
17
|
|
|
|
|
30
|
for my $key (sort keys %{$self->{__struct__}}) { |
|
|
17
|
|
|
|
|
76
|
|
|
81
|
47
|
|
|
|
|
473
|
my $v = $self->{__struct__}{$key}; |
|
82
|
47
|
|
|
|
|
95
|
my $val = $data->{$key}; |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# if the value is not defined, try to transform it |
|
85
|
47
|
100
|
100
|
|
|
108
|
if (!defined $val && scalar(@{$v->{transform}}) > 0) { |
|
|
14
|
|
|
|
|
50
|
|
|
86
|
13
|
|
|
|
|
14
|
for my $transformer (@{$v->{transform}}) { |
|
|
13
|
|
|
|
|
28
|
|
|
87
|
13
|
|
|
|
|
28
|
$val = $transformer->($v, $val); |
|
88
|
|
|
|
|
|
|
} |
|
89
|
|
|
|
|
|
|
} |
|
90
|
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
try { |
|
92
|
47
|
|
|
47
|
|
2864
|
my $_parsed = $v->parse($val); |
|
93
|
45
|
|
|
|
|
150
|
$valid->{$key} = $_parsed; |
|
94
|
|
|
|
|
|
|
} catch { |
|
95
|
2
|
|
|
2
|
|
948
|
my $error_message = $_; |
|
96
|
2
|
|
|
|
|
18
|
$error_message =~ s/ at .+ line [0-9]+\.\n//; |
|
97
|
2
|
|
|
|
|
27
|
push @errors, { |
|
98
|
|
|
|
|
|
|
key => $key, |
|
99
|
|
|
|
|
|
|
error => $error_message, |
|
100
|
|
|
|
|
|
|
}; |
|
101
|
47
|
|
|
|
|
2491
|
}; |
|
102
|
|
|
|
|
|
|
} |
|
103
|
|
|
|
|
|
|
} |
|
104
|
18
|
100
|
|
|
|
271
|
if (scalar(@errors) > 0) { |
|
105
|
4
|
|
|
|
|
22
|
return (undef, [@errors]) |
|
106
|
|
|
|
|
|
|
} |
|
107
|
14
|
|
100
|
|
|
47
|
my $classname = $self->{__as__} || $self->{__is__}; |
|
108
|
14
|
100
|
|
|
|
96
|
$valid = bless $valid, $classname if $classname; |
|
109
|
14
|
|
|
|
|
39
|
return ($valid, undef); |
|
110
|
|
|
|
|
|
|
} |
|
111
|
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
sub _is_hashref_or_object { |
|
113
|
18
|
|
|
18
|
|
26
|
my $data = shift; |
|
114
|
18
|
|
66
|
|
|
155
|
return defined $data && (ref($data) eq 'HASH' || $data->isa('HASH')); |
|
115
|
|
|
|
|
|
|
} |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
sub _errors_to_string { |
|
118
|
3
|
|
|
3
|
|
6
|
my $errors = shift; |
|
119
|
3
|
|
|
|
|
5
|
my @error_strings = (); |
|
120
|
3
|
|
|
|
|
7
|
for my $error (@$errors) { |
|
121
|
3
|
|
|
|
|
15
|
push @error_strings, sprintf("%s on key `%s`", $error->{error}, $error->{key}); |
|
122
|
|
|
|
|
|
|
} |
|
123
|
3
|
|
|
|
|
11
|
return join(", and ", @error_strings); |
|
124
|
|
|
|
|
|
|
} |
|
125
|
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
1; |
|
127
|
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
=head1 NAME |
|
129
|
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
Poz::Types::object - A module for handling structured data with type validation |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
133
|
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
use Poz qw/z/; |
|
135
|
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
# Schema for a person, cast to Some::Class when valid |
|
137
|
|
|
|
|
|
|
my $object = z->object({ |
|
138
|
|
|
|
|
|
|
name => z->string, |
|
139
|
|
|
|
|
|
|
age => z->number, |
|
140
|
|
|
|
|
|
|
})->as('Some::Class'); |
|
141
|
|
|
|
|
|
|
my $data = { |
|
142
|
|
|
|
|
|
|
name => 'John Doe', |
|
143
|
|
|
|
|
|
|
age => 30, |
|
144
|
|
|
|
|
|
|
}; |
|
145
|
|
|
|
|
|
|
my $parsed_data = $object->parse($data); # isa Some::Class |
|
146
|
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
# Schema for a person, validate that the data is an instance of Some::Class |
|
148
|
|
|
|
|
|
|
my $another_object = z->object({ |
|
149
|
|
|
|
|
|
|
name => z->string, |
|
150
|
|
|
|
|
|
|
age => z->number, |
|
151
|
|
|
|
|
|
|
})->is('Another::Class'); |
|
152
|
|
|
|
|
|
|
my $other = bless { |
|
153
|
|
|
|
|
|
|
name => 'John Doe', |
|
154
|
|
|
|
|
|
|
age => 30, |
|
155
|
|
|
|
|
|
|
}, 'Another::Class'; |
|
156
|
|
|
|
|
|
|
my $someone = bless { |
|
157
|
|
|
|
|
|
|
name => 'Jane Doe', |
|
158
|
|
|
|
|
|
|
age => 25, |
|
159
|
|
|
|
|
|
|
}, 'Some::Class'; |
|
160
|
|
|
|
|
|
|
my $parsed_data = $another_object->parse($other); # isa Another::Class |
|
161
|
|
|
|
|
|
|
my $someone_else = $another_object->parse($someone); # throws an exception, because not an instance of Another::Class |
|
162
|
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
# or use Poz as your class constructor |
|
164
|
|
|
|
|
|
|
{ |
|
165
|
|
|
|
|
|
|
package My::Class; |
|
166
|
|
|
|
|
|
|
use Poz qw/z/; |
|
167
|
|
|
|
|
|
|
z->object({ |
|
168
|
|
|
|
|
|
|
name => z->string, |
|
169
|
|
|
|
|
|
|
age => z->number, |
|
170
|
|
|
|
|
|
|
})->constructor; |
|
171
|
|
|
|
|
|
|
} |
|
172
|
|
|
|
|
|
|
my $instance = My::Class->new( |
|
173
|
|
|
|
|
|
|
name => 'Alice', |
|
174
|
|
|
|
|
|
|
age => 20, |
|
175
|
|
|
|
|
|
|
); |
|
176
|
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
179
|
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
Poz::Types::object is a module for handling structured data with type validation. It allows you to define a structure with specific types and validate data against this structure. |
|
181
|
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
=head1 METHODS |
|
183
|
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=head2 as |
|
185
|
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
$object->as($typename); |
|
187
|
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
Sets the class name to bless the parsed data into. The C<$typename> parameter should be a string representing the class name. |
|
189
|
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
=head2 is |
|
191
|
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
$object->is($typename); |
|
193
|
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
Validates that the parsed data is an instance of the given class. The C<$typename> parameter should be a string representing the class name. |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
=head2 constructor |
|
197
|
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
$object->constructor; |
|
199
|
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
Creates a constructor method into your class. |
|
201
|
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
=head2 parse |
|
203
|
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
my $parsed_data = $object->parse($data); |
|
205
|
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
Parses and validates the given data against the structure. If the data is valid, it returns the parsed data. If the data is invalid, it throws an exception with the validation errors. |
|
207
|
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=head2 safe_parse |
|
209
|
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
my ($valid, $errors) = $object->safe_parse($data); |
|
211
|
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
Parses and validates the given data against the structure. If the data is valid, it returns the parsed data and undef for errors. If the data is invalid, it returns undef for valid data and an array reference of errors. |
|
213
|
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
=head1 LICENSE |
|
215
|
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
Copyright (C) ytnobody. |
|
217
|
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or modify |
|
219
|
|
|
|
|
|
|
it under the same terms as Perl itself. |
|
220
|
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
=head1 AUTHOR |
|
222
|
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
ytnobody E<lt>ytnobody@gmail.comE<gt> |
|
224
|
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
=cut |