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   219713 use 5.010001;
  5         55  
4 5     5   30 use strict;
  5         10  
  5         133  
5 5     5   41 use warnings;
  5         57  
  5         186  
6 5     5   7368 use Log::ger;
  5         246  
  5         29  
7              
8 5     5   3188 use Data::Sah::CoerceCommon;
  5         15  
  5         179  
9 5     5   3028 use IPC::System::Options;
  5         21438  
  5         42  
10 5     5   3027 use Nodejs::Util qw(get_nodejs_path);
  5         8833  
  5         380  
11              
12 5     5   42 use Exporter qw(import);
  5         11  
  5         7088  
13              
14             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
15             our $DATE = '2021-10-18'; # DATE
16             our $DIST = 'Data-Sah-Coerce'; # DIST
17             our $VERSION = '0.051'; # 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.051 of Data::Sah::CoerceJS (from Perl distribution Data-Sah-Coerce), released on 2021-10-18.
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() -> 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             No arguments.
203              
204             Return value: (any)
205              
206             =head1 ENVIRONMENT
207              
208             =head2 LOG_SAH_COERCER_CODE => bool
209              
210             Set default for C<$Log_Coercer_Code>.
211              
212             =head1 HOMEPAGE
213              
214             Please visit the project's homepage at L<https://metacpan.org/release/Data-Sah-Coerce>.
215              
216             =head1 SOURCE
217              
218             Source repository is at L<https://github.com/perlancar/perl-Data-Sah-Coerce>.
219              
220             =head1 SEE ALSO
221              
222             L<Data::Sah::Coerce>
223              
224             L<App::SahUtils>, including L<coerce-with-sah> to conveniently test coercion
225             from the command-line.
226              
227             =head1 AUTHOR
228              
229             perlancar <perlancar@cpan.org>
230              
231             =head1 CONTRIBUTING
232              
233              
234             To contribute, you can send patches by email/via RT, or send pull requests on
235             GitHub.
236              
237             Most of the time, you don't need to build the distribution yourself. You can
238             simply modify the code, then test via:
239              
240             % prove -l
241              
242             If you want to build the distribution (e.g. to try to install it locally on your
243             system), you can install L<Dist::Zilla>,
244             L<Dist::Zilla::PluginBundle::Author::PERLANCAR>, and sometimes one or two other
245             Dist::Zilla plugin and/or Pod::Weaver::Plugin. Any additional steps required
246             beyond that are considered a bug and can be reported to me.
247              
248             =head1 COPYRIGHT AND LICENSE
249              
250             This software is copyright (c) 2021, 2020, 2019, 2018, 2017, 2016 by perlancar <perlancar@cpan.org>.
251              
252             This is free software; you can redistribute it and/or modify it under
253             the same terms as the Perl 5 programming language system itself.
254              
255             =head1 BUGS
256              
257             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Data-Sah-Coerce>
258              
259             When submitting a bug or request, please include a test-file or a
260             patch to an existing test-file that illustrates the bug or desired
261             feature.
262              
263             =cut