File Coverage

blib/lib/Email/MIME/Kit/Validator/Rx.pm
Criterion Covered Total %
statement 60 62 96.7
branch 9 12 75.0
condition 5 6 83.3
subroutine 16 16 100.0
pod 0 5 0.0
total 90 101 89.1


line stmt bran cond sub pod time code
1             use Moose;
2 1     1   77659 with 'Email::MIME::Kit::Role::Validator';
  1         3  
  1         6  
3             # ABSTRACT: validate assembly stash with Rx (from JSON in kit)
4              
5             use Data::Rx 0.007;
6 1     1   7083 use Data::Rx::TypeBundle::Perl 0.005;
  1         9100  
  1         33  
7 1     1   392 use JSON;
  1         7457  
  1         28  
8 1     1   8 use Moose::Util::TypeConstraints;
  1         2  
  1         7  
9 1     1   143 use Try::Tiny;
  1         3  
  1         9  
10 1     1   2270  
  1         3  
  1         849  
11             #pod =head1 SYNOPSIS
12             #pod
13             #pod Email::MIME::Kit::Validator::Rx is a Validator plugin for Email::MIME::Kit that
14             #pod allows an Rx schema to be used to validate kit assembly data.
15             #pod
16             #pod A simple mkit's manifest might include the following:
17             #pod
18             #pod {
19             #pod "renderer" : "TT",
20             #pod "validator": "Rx",
21             #pod "header" : [ ... mail headers ... ],
22             #pod "type" : "text/plain",
23             #pod "path" : "path/to/template.txt"
24             #pod }
25             #pod
26             #pod In this simple configuration, the use of "Rx" as the validator will load the
27             #pod plugin in its simplest configuration. It will look for a file called
28             #pod F<rx.json> in the kit and will load its contents (as JSON) and use them as a
29             #pod schema to validate the data passed to the it's C<assemble> method.
30             #pod
31             #pod More complex configurations are simple.
32             #pod
33             #pod This configuration supplies an alternate filename for the JSON file:
34             #pod
35             #pod "validator": [ "Rx", { "path": "rx-schema.json" } ],
36             #pod
37             #pod This configuration supplies the schema definition inline:
38             #pod
39             #pod "validator": [
40             #pod "Rx",
41             #pod {
42             #pod "schema": {
43             #pod "type" : "//rec",
44             #pod "required": {
45             #pod "subject": "//str",
46             #pod "rcpt" : { "type": "/perl/obj", "isa": "Email::Address" }
47             #pod }
48             #pod }
49             #pod }
50             #pod ]
51             #pod
52             #pod Notice, above, the C</perl/> prefix. By default,
53             #pod L<Data::Rx::TypeBundle::Perl|Data::Rx::TypeBundle::Perl> is loaded along with
54             #pod the core types.
55             #pod
56             #pod If a C<combine> argument is given, multiple schema definitions may be provided.
57             #pod They will be combined with the logic named by the combine argument. In this
58             #pod release, only "all" is valid, and will require all schemata to match. Here is
59             #pod an example:
60             #pod
61             #pod "validator": [
62             #pod "Rx",
63             #pod {
64             #pod "combine": "all",
65             #pod "path" : "rx.json",
66             #pod "schema" : [
67             #pod { "type": "//rec", "rest": "//any", "required": { "foo": "//int" } },
68             #pod { "type": "//rec", "rest": "//any", "required": { "bar": "//int" } },
69             #pod ]
70             #pod }
71             #pod ]
72             #pod
73             #pod This definition will create an C<//all> schema with three entries: the schema
74             #pod found in F<rx.json> and the two schemata given in the array value of C<schema>.
75             #pod
76             #pod =cut
77              
78             has prefix => (
79             is => 'ro',
80             isa => 'HashRef',
81             default => sub { {} },
82             );
83              
84             has type_plugins => (
85             is => 'ro',
86             isa => 'ArrayRef[Str]',
87             default => sub { [] },
88             );
89              
90             has rx => (
91             is => 'ro',
92             isa => class_type('Data::Rx'),
93             lazy => 1,
94             init_arg => undef,
95             builder => 'build_default_rx_object',
96             );
97              
98             my ($self) = @_;
99             my $rx = Data::Rx->new({
100 3     3 0 10 prefix => $self->prefix,
101 3         105 });
102              
103             for my $plugin ($self->all_default_type_plugins, @{ $self->type_plugins }) {
104             eval "require $plugin; 1" or die;
105 3         13563 $rx->register_type_plugin($plugin);
  3         120  
106 3 50       201 }
107 3         16  
108             my $prefix = $self->prefix;
109             for my $key (keys %$prefix) {
110 3         359 $rx->add_prefix($key, $prefix->{ $key });
111 3         12 }
112 0         0  
113             return $rx;
114             }
115 3         93  
116             # shamlessly stolen from Moose::Object::BUILDALL -- rjbs, 2009-03-06
117             my ($self) = @_;
118             my @plugins;
119             for my $method (
120 3     3 0 10 reverse
121 3         5 $self->meta->find_all_methods_by_name('accumulate_default_type_plugins')
122 3         14 ) {
123             push @plugins, $method->{code}->execute($self);
124             }
125              
126 3         1049 return @plugins;
127             }
128              
129 3         11 return ('Data::Rx::TypeBundle::Perl');
130             }
131              
132             has schema => (
133 3     3 0 28 reader => 'schema',
134             writer => '_set_schema',
135             isa => 'Object', # It'd be nice to have a better TC -- rjbs, 2009-03-06
136             init_arg => undef,
137             );
138              
139             has schema_struct => (
140             reader => '_schema_struct',
141             init_arg => 'schema',
142             );
143              
144             has schema_path => (
145             reader => '_schema_path',
146             writer => '_set_schema_path',
147             isa => 'Str',
148             init_arg => 'path',
149             );
150              
151             has combine => (
152             is => 'ro',
153             initializer => sub {
154             my ($self, $value, $set) = @_;
155             confess "invalid combine logic: $value"
156             unless defined $value and $value eq 'all';
157             $set->($value);
158             },
159             );
160              
161             my ($self) = @_;
162              
163             $self->_do_goofy_schema_initialization;
164             }
165              
166 3     3 0 10 my ($self) = @_;
167              
168 3         9 my @paths = grep { defined } ref $self->_schema_path
169             ? @{ $self->_schema_path }
170             : $self->_schema_path;
171              
172 3     3   9 my @structs = grep { defined } (ref $self->_schema_struct eq 'ARRAY')
173             ? @{ $self->_schema_struct }
174 3         12 : $self->_schema_struct;
175 3 50       117  
  0         0  
176             confess("multiple schemata provided but no combine logic given")
177             if @paths + @structs > 1 and ! $self->combine;
178 4         13  
179 3 100       116 @paths = ('rx.json') unless @paths or @structs;
  1         37  
180              
181             for my $path (@paths) {
182 3 50 66     48 # Sure, someday we can add another decoder layer here to allow schemata in
183             # YAML. Whatever. -- rjbs, 2009-03-06
184             my $rx_json_ref = $self->kit->get_kit_entry($path);
185 3 100 100     16 my $rx_data = JSON->new->decode($$rx_json_ref);
186             push @structs, $rx_data;
187 3         9 }
188              
189             my $schema = @structs > 1
190 2         61 ? $self->rx->make_schema({ type => '//all', of => \@structs })
191 2         555 : $self->rx->make_schema($structs[0]);
192 2         14  
193             $self->_set_schema($schema);
194             }
195 3 100       121  
196             my ($self, $stash) = @_;
197              
198             try {
199 3         1271 $self->schema->assert_valid($stash);
200             } catch {
201             Carp::confess("assembly parameters don't pass validation: $_");
202             };
203 9     9 0 95772  
204             return 1;
205             }
206 9     9   687  
207             no Moose;
208 3     3   989 no Moose::Util::TypeConstraints;
209 9         75 __PACKAGE__->meta->make_immutable;
210             1;
211 6         941  
212              
213             =pod
214 1     1   9  
  1         2  
  1         8  
215 1     1   268 =encoding UTF-8
  1         2  
  1         6  
216              
217             =head1 NAME
218              
219             Email::MIME::Kit::Validator::Rx - validate assembly stash with Rx (from JSON in kit)
220              
221             =head1 VERSION
222              
223             version 0.200002
224              
225             =head1 SYNOPSIS
226              
227             Email::MIME::Kit::Validator::Rx is a Validator plugin for Email::MIME::Kit that
228             allows an Rx schema to be used to validate kit assembly data.
229              
230             A simple mkit's manifest might include the following:
231              
232             {
233             "renderer" : "TT",
234             "validator": "Rx",
235             "header" : [ ... mail headers ... ],
236             "type" : "text/plain",
237             "path" : "path/to/template.txt"
238             }
239              
240             In this simple configuration, the use of "Rx" as the validator will load the
241             plugin in its simplest configuration. It will look for a file called
242             F<rx.json> in the kit and will load its contents (as JSON) and use them as a
243             schema to validate the data passed to the it's C<assemble> method.
244              
245             More complex configurations are simple.
246              
247             This configuration supplies an alternate filename for the JSON file:
248              
249             "validator": [ "Rx", { "path": "rx-schema.json" } ],
250              
251             This configuration supplies the schema definition inline:
252              
253             "validator": [
254             "Rx",
255             {
256             "schema": {
257             "type" : "//rec",
258             "required": {
259             "subject": "//str",
260             "rcpt" : { "type": "/perl/obj", "isa": "Email::Address" }
261             }
262             }
263             }
264             ]
265              
266             Notice, above, the C</perl/> prefix. By default,
267             L<Data::Rx::TypeBundle::Perl|Data::Rx::TypeBundle::Perl> is loaded along with
268             the core types.
269              
270             If a C<combine> argument is given, multiple schema definitions may be provided.
271             They will be combined with the logic named by the combine argument. In this
272             release, only "all" is valid, and will require all schemata to match. Here is
273             an example:
274              
275             "validator": [
276             "Rx",
277             {
278             "combine": "all",
279             "path" : "rx.json",
280             "schema" : [
281             { "type": "//rec", "rest": "//any", "required": { "foo": "//int" } },
282             { "type": "//rec", "rest": "//any", "required": { "bar": "//int" } },
283             ]
284             }
285             ]
286              
287             This definition will create an C<//all> schema with three entries: the schema
288             found in F<rx.json> and the two schemata given in the array value of C<schema>.
289              
290             =head1 PERL VERSION
291              
292             This module should work on any version of perl still receiving updates from
293             the Perl 5 Porters. This means it should work on any version of perl released
294             in the last two to three years. (That is, if the most recently released
295             version is v5.40, then this module should work on both v5.40 and v5.38.)
296              
297             Although it may work on older versions of perl, no guarantee is made that the
298             minimum required version will not be increased. The version may be increased
299             for any reason, and there is no promise that patches will be accepted to lower
300             the minimum required perl.
301              
302             =head1 AUTHOR
303              
304             Ricardo SIGNES <cpan@semiotic.systems>
305              
306             =head1 CONTRIBUTOR
307              
308             =for stopwords Ricardo Signes
309              
310             Ricardo Signes <rjbs@semiotic.systems>
311              
312             =head1 COPYRIGHT AND LICENSE
313              
314             This software is copyright (c) 2022 by Ricardo SIGNES.
315              
316             This is free software; you can redistribute it and/or modify it under
317             the same terms as the Perl 5 programming language system itself.
318              
319             =cut