File Coverage

blib/lib/Data/Sah/Resolve.pm
Criterion Covered Total %
statement 44 83 53.0
branch 10 28 35.7
condition 1 2 50.0
subroutine 7 7 100.0
pod 1 1 100.0
total 63 121 52.0


line stmt bran cond sub pod time code
1             package Data::Sah::Resolve;
2              
3             our $DATE = '2021-02-27'; # DATE
4             our $VERSION = '0.008'; # VERSION
5              
6 1     1   2664 use 5.010001;
  1         7  
7 1     1   5 use strict;
  1         1  
  1         18  
8 1     1   12 use warnings;
  1         1  
  1         33  
9              
10 1     1   5 use Exporter qw(import);
  1         2  
  1         274  
11             our @EXPORT_OK = qw(resolve_schema);
12              
13             sub _resolve {
14 9     9   17 my ($opts, $type, $clsets, $seen) = @_;
15              
16             die "Recursive schema definition: ".join(" -> ", @$seen, $type)
17 9 100       19 if grep { $type eq $_ } @$seen;
  7         47  
18 6         12 push @$seen, $type;
19              
20 6         23 (my $typemod_pm = "Data/Sah/Type/$type.pm") =~ s!::!/!g;
21 6         11 eval { require $typemod_pm; 1 };
  6         807  
  0         0  
22 6         23 my $err = $@;
23             # already a builtin-type, so just return the schema's type name & clause set
24 6 50       16 return [$type, $clsets] unless $err;
25 6 50       21 die "Can't check whether $type is a builtin Sah type: $err"
26             unless $err =~ /\ACan't locate/;
27              
28             # not a type, try a schema under Sah::Schema
29 6         29 my $schmod = "Sah::Schema::$type";
30 6         23 (my $schmod_pm = "$schmod.pm") =~ s!::!/!g;
31 6         8 eval { require $schmod_pm; 1 };
  6         1498  
  5         56  
32 6 100       28 die "Not a known built-in Sah type '$type' (can't locate ".
33             "Data::Sah::Type::$type) and not a known schema name '$type' ($@)"
34             if $@;
35 1     1   7 no strict 'refs';
  1         1  
  1         577  
36 5         8 my $sch2 = ${"$schmod\::schema"};
  5         14  
37 5 50       13 die "BUG: Schema module $schmod doesn't contain \$schema" unless $sch2;
38 5         11 unshift @$clsets, $sch2->[1];
39 5         15 _resolve($opts, $sch2->[0], $clsets, $seen);
40             }
41              
42             sub resolve_schema {
43 4 50   4 1 10074 my $opts = ref($_[0]) eq 'HASH' ? shift : {};
44 4         8 my $sch = shift;
45              
46 4 50       9 unless ($opts->{schema_is_normalized}) {
47 4         554 require Data::Sah::Normalize;
48 4         1402 $sch = Data::Sah::Normalize::normalize_schema($sch);
49             }
50 4   50     91 $opts->{merge_clause_sets} //= 1;
51              
52 4         5 my $seen = [];
53 4 50       7 my $res = _resolve($opts, $sch->[0], keys(%{$sch->[1]}) ? [$sch->[1]] : [], $seen);
  4         16  
54              
55             MERGE:
56             {
57 0 0         last unless $opts->{merge_clause_sets};
  0            
58 0 0         last if @{ $res->[1] } < 2;
  0            
59              
60 0           my @clsets = (shift @{ $res->[1] });
  0            
61 0           for my $clset (@{ $res->[1] }) {
  0            
62 0           my $has_merge_mode_keys;
63 0           for (keys %$clset) {
64 0 0         if (/\Amerge\./) {
65 0           $has_merge_mode_keys = 1;
66 0           last;
67             }
68             }
69 0 0         if ($has_merge_mode_keys) {
70 0           state $merger = do {
71 0           require Data::ModeMerge;
72 0           my $mm = Data::ModeMerge->new(config => {
73             recurse_array => 1,
74             });
75 0           $mm->modes->{NORMAL} ->prefix ('merge.normal.');
76 0           $mm->modes->{NORMAL} ->prefix_re(qr/\Amerge\.normal\./);
77 0           $mm->modes->{ADD} ->prefix ('merge.add.');
78 0           $mm->modes->{ADD} ->prefix_re(qr/\Amerge\.add\./);
79 0           $mm->modes->{CONCAT} ->prefix ('merge.concat.');
80 0           $mm->modes->{CONCAT} ->prefix_re(qr/\Amerge\.concat\./);
81 0           $mm->modes->{SUBTRACT}->prefix ('merge.subtract.');
82 0           $mm->modes->{SUBTRACT}->prefix_re(qr/\Amerge\.subtract\./);
83 0           $mm->modes->{DELETE} ->prefix ('merge.delete.');
84 0           $mm->modes->{DELETE} ->prefix_re(qr/\Amerge\.delete\./);
85 0           $mm->modes->{KEEP} ->prefix ('merge.keep.');
86 0           $mm->modes->{KEEP} ->prefix_re(qr/\Amerge\.keep\./);
87 0           $mm;
88             };
89 0           my $merge_res = $merger->merge($clsets[-1], $clset);
90 0 0         unless ($merge_res->{success}) {
91 0           die "Can't merge clause set: $merge_res->{error}";
92             }
93 0           $clsets[-1] = $merge_res->{result};
94             } else {
95 0           push @clsets, $clset;
96             }
97             }
98              
99 0           $res->[1] = \@clsets;
100             }
101              
102 0 0         $res->[2] = $seen if $opts->{return_intermediates};
103              
104 0           $res;
105             }
106              
107             1;
108             # ABSTRACT: Resolve Sah schema
109              
110             __END__
111              
112             =pod
113              
114             =encoding UTF-8
115              
116             =head1 NAME
117              
118             Data::Sah::Resolve - Resolve Sah schema
119              
120             =head1 VERSION
121              
122             This document describes version 0.008 of Data::Sah::Resolve (from Perl distribution Data-Sah-Resolve), released on 2021-02-27.
123              
124             =head1 SYNOPSIS
125              
126             use Data::Sah::Resolve qw(resolve_schema);
127              
128             my $sch = resolve_schema("int");
129             # => ["int", []]
130              
131             my $sch = resolve_schema("posint*");
132             # => ["int", [{min=>1}, {req=>1}]
133              
134             my $sch = resolve_schema([posint => div_by => 3]);
135             # => ["int", {min=>1}, {div_by=>3}]
136              
137             my $sch = resolve_schema(["posint", "merge.delete.min"=>undef, div_by => 3]);
138             # => ["int", {div_by=>3}]
139              
140             =head1 DESCRIPTION
141              
142             =head1 FUNCTIONS
143              
144             =head2 resolve_schema([ \%opts, ] $sch) => sch
145              
146             Sah schemas can be defined in terms of other schemas. The resolving process
147             follows the base schema recursively until it finds a builtin type as the base.
148              
149             This routine performs the following steps:
150              
151             =over
152              
153             =item 1. Normalize the schema
154              
155             Unless C<schema_is_normalized> option is true, in which case schema is assumed
156             to be normalized already.
157              
158             =item 2. Check if the schema's type is a builtin type
159              
160             Currently this is done by checking if the module of the name C<<
161             Data::Sah::Type::<type> >> is loadable. If it is a builtin type then we are
162             done.
163              
164             =item 3. Check if the schema's type is the name of another schema
165              
166             This is done by checking if C<< Sah::Schema::<name> >> module exists and is
167             loadable. If this is the case then we retrieve the base schema from the
168             C<$schema> variable in the C<< Sah::Schema::<name> >> package and repeat the
169             process while accumulating and/or merging the clause sets.
170              
171             =item 4. If schema's type is neither, we die.
172              
173             =back
174              
175             Returns C<< [base_type, clause_sets] >>. If C<return_intermediates> option is
176             true, then the third elements will be the list of intermediate schema names.
177              
178             Example 1: C<int>.
179              
180             First we normalize to C<< ["int",{},{}] >>. The type is C<int> and it is a
181             builtin type (L<Data::Sah::Type::int> exists) so the final result is C<< ["int",
182             []] >>.
183              
184             Example 2: C<posint*>.
185              
186             First we normalize to C<< ["posint",{req=>1},{}] >>. The type is C<posint> and
187             it is the name of another schema (L<Sah::Schema::posint>). We retrieve the
188             schema which is C<< ["int", {summary=>"Positive integer (1,2,3,...)", min=>1},
189             {}] >>. We now try to resolve C<int> and find that it's a builtin type. So the
190             final result is: C<< ["int", [ {req=>1}, {summary=>"Positive integer
191             (1,2,3,...)", min=>1} ]] >>.
192              
193             Known options:
194              
195             =over
196              
197             =item * schema_is_normalized => bool (default: 0)
198              
199             When set to true, function will skip normalizing schema and assume input schema
200             is normalized.
201              
202             =item * merge_clause_sets => bool (default: 1)
203              
204             =item * return_intermediates => bool
205              
206             =back
207              
208             =head1 HOMEPAGE
209              
210             Please visit the project's homepage at L<https://metacpan.org/release/Data-Sah-Resolve>.
211              
212             =head1 SOURCE
213              
214             Source repository is at L<https://github.com/perlancar/perl-Data-Sah-Resolve>.
215              
216             =head1 BUGS
217              
218             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Data-Sah-Resolve>
219              
220             When submitting a bug or request, please include a test-file or a
221             patch to an existing test-file that illustrates the bug or desired
222             feature.
223              
224             =head1 SEE ALSO
225              
226             L<Sah>, L<Data::Sah>
227              
228             =head1 AUTHOR
229              
230             perlancar <perlancar@cpan.org>
231              
232             =head1 COPYRIGHT AND LICENSE
233              
234             This software is copyright (c) 2021, 2017, 2016 by perlancar@cpan.org.
235              
236             This is free software; you can redistribute it and/or modify it under
237             the same terms as the Perl 5 programming language system itself.
238              
239             =cut