File Coverage

blib/lib/Data/Sah/CoerceJS.pm
Criterion Covered Total %
statement 23 73 31.5
branch 0 38 0.0
condition 0 2 0.0
subroutine 8 10 80.0
pod 1 1 100.0
total 32 124 25.8


line stmt bran cond sub pod time code
1             package Data::Sah::CoerceJS;
2              
3 5     5   213791 use 5.010001;
  5         57  
4 5     5   29 use strict;
  5         9  
  5         145  
5 5     5   28 use warnings;
  5         10  
  5         158  
6 5     5   7221 use Log::ger;
  5         235  
  5         25  
7              
8 5     5   2909 use Data::Sah::CoerceCommon;
  5         13  
  5         158  
9 5     5   3101 use IPC::System::Options;
  5         20997  
  5         35  
10 5     5   2698 use Nodejs::Util qw(get_nodejs_path);
  5         11714  
  5         326  
11              
12 5     5   38 use Exporter qw(import);
  5         18  
  5         3719  
13              
14             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
15             our $DATE = '2021-11-28'; # DATE
16             our $DIST = 'Data-Sah-Coerce'; # DIST
17             our $VERSION = '0.052'; # VERSION
18              
19             our @EXPORT_OK = qw(gen_coercer);
20              
21             our %SPEC;
22              
23             our $Log_Coercer_Code = $ENV{LOG_SAH_COERCER_CODE} // 0;
24              
25             $SPEC{gen_coercer} = {
26             v => 1.1,
27             summary => 'Generate coercer code',
28             description => <<'_',
29              
30             This is mostly for testing. Normally the coercion rules will be used from
31             <pm:Data::Sah>.
32              
33             _
34             args => {
35             %Data::Sah::CoerceCommon::gen_coercer_args,
36             },
37             result_naked => 1,
38             };
39             sub gen_coercer {
40 0     0 1   my %args = @_;
41              
42 0   0       my $rt = $args{return_type} // 'val';
43             # old values still supported but deprecated
44 0 0         $rt = 'bool_coerced+val' if $rt eq 'status+val';
45 0 0         $rt = 'bool_coerced+str_errmsg+val' if $rt eq 'status+err+val';
46              
47 0           my $rules = Data::Sah::CoerceCommon::get_coerce_rules(
48             %args,
49             compiler=>'js',
50             data_term=>'data',
51             );
52              
53 0           my $code;
54 0 0         if (@$rules) {
55 0           my $expr;
56 0           for my $i (reverse 0..$#{$rules}) {
  0            
57 0           my $rule = $rules->[$i];
58              
59 0           my $prev_term;
60 0 0         if ($i == $#{$rules}) {
  0            
61 0 0         if ($rt eq 'val') {
    0          
62 0           $prev_term = 'data';
63             } elsif ($rt eq 'bool_coerced+val') {
64 0           $prev_term = '[null, data]';
65             } else { # bool_coerced+str_errmsg+val
66 0           $prev_term = '[null, null, data]';
67             }
68             } else {
69 0           $prev_term = $expr;
70             }
71              
72 0 0         if ($rt eq 'val') {
    0          
73 0 0         if ($rule->{meta}{might_fail}) {
74 0           $expr = "(function() { if ($rule->{expr_match}) { var _tmp1 = $rule->{expr_coerce}; if (_tmp1[0]) { return null } else { return _tmp1[1] } } else { return $prev_term } })()";
75             } else {
76 0           $expr = "($rule->{expr_match}) ? ($rule->{expr_coerce}) : $prev_term";
77             }
78             } elsif ($rt eq 'bool_coerced+val') {
79 0 0         if ($rule->{meta}{might_fail}) {
80 0           $expr = "(function() { if ($rule->{expr_match}) { var _tmp1 = $rule->{expr_coerce}; if (_tmp1[0]) { return [true, null] } else { return [true, _tmp1[1]] } } else { return $prev_term } })()";
81             } else {
82 0           $expr = "($rule->{expr_match}) ? [true, $rule->{expr_coerce}] : $prev_term";
83             }
84             } else { # bool_coerced+str_errmsg+val
85 0 0         if ($rule->{meta}{might_fail}) {
86 0           $expr = "(function() { if ($rule->{expr_match}) { var _tmp1 = $rule->{expr_coerce}; if (_tmp1[0]) { return [true, _tmp1[0], null] } else { return [true, null, _tmp1[1]] } } else { return $prev_term } })()";
87             } else {
88 0           $expr = "($rule->{expr_match}) ? [true, null, $rule->{expr_coerce}] : $prev_term";
89             }
90             }
91             }
92              
93 0 0         $code = join(
    0          
94             "",
95             "function (data) {\n",
96             " if (data === undefined || data === null) {\n",
97             " ", ($rt eq 'val' ? "return null;" :
98             $rt eq 'bool_coerced+val' ? "return [null, null];" :
99             "return [null, null, null];" # bool_coerced+str_errmsg+val
100             ), "\n",
101             " }\n",
102             " return ($expr);\n",
103             "}",
104             );
105             } else {
106 0 0         if ($rt eq 'val') {
    0          
107 0           $code = 'function (data) { return data }';
108             } elsif ($rt eq 'bool_coerced+val') {
109 0           $code = 'function (data) { return [null, data] }';
110             } else { # bool_coerced+str_errmsg+val
111 0           $code = 'function (data) { return [null, null, data] }';
112             }
113             }
114              
115 0 0         if ($Log_Coercer_Code) {
116 0           log_trace("Coercer code (gen args: %s): %s", \%args, $code);
117             }
118              
119 0 0         return $code if $args{source};
120              
121 0           state $nodejs_path = get_nodejs_path();
122 0 0         die "Can't find node.js in PATH" unless $nodejs_path;
123              
124             sub {
125 0     0     require File::Temp;
126 0           require JSON;
127             #require String::ShellQuote;
128              
129 0           my $data = shift;
130              
131 0           state $json = JSON->new->allow_nonref;
132              
133             # code to be sent to nodejs
134 0           my $src = "var coercer = $code;\n\n".
135             "console.log(JSON.stringify(coercer(".
136             $json->encode($data).")))";
137              
138 0           my ($jsh, $jsfn) = File::Temp::tempfile();
139 0           print $jsh $src;
140 0 0         close($jsh) or die "Can't write JS code to file $jsfn: $!";
141              
142 0           my $out = IPC::System::Options::readpipe($nodejs_path, $jsfn);
143 0           $json->decode($out);
144 0           };
145             }
146              
147             1;
148             # ABSTRACT: Generate coercer code
149              
150             __END__
151              
152             =pod
153              
154             =encoding UTF-8
155              
156             =head1 NAME
157              
158             Data::Sah::CoerceJS - Generate coercer code
159              
160             =head1 VERSION
161              
162             This document describes version 0.052 of Data::Sah::CoerceJS (from Perl distribution Data-Sah-Coerce), released on 2021-11-28.
163              
164             =head1 SYNOPSIS
165              
166             use Data::Sah::CoerceJS qw(gen_coercer);
167              
168             # use as you would use Data::Sah::Coerce
169              
170             =head1 DESCRIPTION
171              
172             This module is just like L<Data::Sah::Coerce> except that it uses JavaScript
173             coercion rule modules.
174              
175             =head1 VARIABLES
176              
177             =head2 $Log_Coercer_Code => bool (default: from ENV or 0)
178              
179             If set to true, will log the generated coercer code (currently using L<Log::ger>
180             at trace level). To see the log message, e.g. to the screen, you can use
181             something like:
182              
183             % TRACE=1 perl -MLog::ger::LevelFromEnv -MLog::ger::Output=Screen \
184             -MData::Sah::CoerceJS=gen_coercer -E'my $c = gen_coercer(...)'
185              
186             =head1 FUNCTIONS
187              
188              
189             =head2 gen_coercer
190              
191             Usage:
192              
193             gen_coercer(%args) -> any
194              
195             Generate coercer code.
196              
197             This is mostly for testing. Normally the coercion rules will be used from
198             L<Data::Sah>.
199              
200             This function is not exported by default, but exportable.
201              
202             Arguments ('*' denotes required arguments):
203              
204             =over 4
205              
206             =item * B<coerce_rules> => I<array[str]>
207              
208             A specification of coercion rules to use (or avoid).
209              
210             This setting is used to specify which coercion rules to use (or avoid) in a
211             flexible way. Each element is a string, in the form of either C<NAME> to mean
212             specifically include a rule, or C<!NAME> to exclude a rule.
213              
214             Some coercion modules are used by default, unless explicitly avoided using the
215             '!NAME' rule.
216              
217             To not use any rules:
218              
219             To use the default rules plus R1 and R2:
220              
221             ['R1', 'R2']
222              
223             To use the default rules but not R1 and R2:
224              
225             ['!R1', '!R2']
226              
227             =item * B<coerce_to> => I<str>
228              
229             Some Sah types, like C<date>, can be represented in a choice of types in the
230             target language. For example, in Perl you can store it as a floating number
231             a.k.a. C<float(epoch)>, or as a L<DateTime> object, or L<Time::Moment>
232             object. Storing in DateTime can be convenient for date manipulation but requires
233             an overhead of loading the module and storing in a bulky format. The choice is
234             yours to make, via this setting.
235              
236             =item * B<return_type> => I<str> (default: "val")
237              
238             C<val> means the coercer will return the input (possibly) coerced or undef if
239             coercion fails.
240              
241             C<bool_coerced+val> means the coercer will return a 2-element array. The first
242             element is a bool value set to 1 if coercion has been performed or 0 if
243             otherwise. The second element is the (possibly) coerced input.
244              
245             C<bool_coerced+str_errmsg+val> means the coercer will return a 3-element array.
246             The first element is a bool value set to 1 if coercion has been performed or 0
247             if otherwise. The second element is the error message string which will be set
248             if there is a failure in coercion (or undef if coercion is successful). The
249             third element is the (possibly) coerced input.
250              
251             =item * B<source> => I<bool>
252              
253             If set to true, will return coercer source code string instead of compiled code.
254              
255             =item * B<type>* => I<sah::type_name>
256              
257              
258             =back
259              
260             Return value: (any)
261              
262             =head1 ENVIRONMENT
263              
264             =head2 LOG_SAH_COERCER_CODE => bool
265              
266             Set default for C<$Log_Coercer_Code>.
267              
268             =head1 HOMEPAGE
269              
270             Please visit the project's homepage at L<https://metacpan.org/release/Data-Sah-Coerce>.
271              
272             =head1 SOURCE
273              
274             Source repository is at L<https://github.com/perlancar/perl-Data-Sah-Coerce>.
275              
276             =head1 SEE ALSO
277              
278             L<Data::Sah::Coerce>
279              
280             L<App::SahUtils>, including L<coerce-with-sah> to conveniently test coercion
281             from the command-line.
282              
283             =head1 AUTHOR
284              
285             perlancar <perlancar@cpan.org>
286              
287             =head1 CONTRIBUTING
288              
289              
290             To contribute, you can send patches by email/via RT, or send pull requests on
291             GitHub.
292              
293             Most of the time, you don't need to build the distribution yourself. You can
294             simply modify the code, then test via:
295              
296             % prove -l
297              
298             If you want to build the distribution (e.g. to try to install it locally on your
299             system), you can install L<Dist::Zilla>,
300             L<Dist::Zilla::PluginBundle::Author::PERLANCAR>, and sometimes one or two other
301             Dist::Zilla plugin and/or Pod::Weaver::Plugin. Any additional steps required
302             beyond that are considered a bug and can be reported to me.
303              
304             =head1 COPYRIGHT AND LICENSE
305              
306             This software is copyright (c) 2021, 2020, 2019, 2018, 2017, 2016 by perlancar <perlancar@cpan.org>.
307              
308             This is free software; you can redistribute it and/or modify it under
309             the same terms as the Perl 5 programming language system itself.
310              
311             =head1 BUGS
312              
313             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Data-Sah-Coerce>
314              
315             When submitting a bug or request, please include a test-file or a
316             patch to an existing test-file that illustrates the bug or desired
317             feature.
318              
319             =cut