| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package WebService::Fake; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 2 |  |  | 2 |  | 3448 | use strict; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 115 |  | 
| 4 |  |  |  |  |  |  | { our $VERSION = '0.006'; } | 
| 5 |  |  |  |  |  |  |  | 
| 6 | 2 |  |  | 2 |  | 574 | use Mojo::Base 'Mojolicious'; | 
|  | 2 |  |  |  |  | 217418 |  | 
|  | 2 |  |  |  |  | 18 |  | 
| 7 | 2 |  |  | 2 |  | 466278 | use Log::Any qw< $log >; | 
|  | 2 |  |  |  |  | 19626 |  | 
|  | 2 |  |  |  |  | 15 |  | 
| 8 | 2 |  |  | 2 |  | 6661 | use YAML::XS qw< LoadFile >; | 
|  | 2 |  |  |  |  | 6449 |  | 
|  | 2 |  |  |  |  | 138 |  | 
| 9 | 2 |  |  | 2 |  | 1367 | use Try::Tiny; | 
|  | 2 |  |  |  |  | 2952 |  | 
|  | 2 |  |  |  |  | 141 |  | 
| 10 | 2 |  |  | 2 |  | 17 | use Scalar::Util qw< blessed >; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 100 |  | 
| 11 | 2 |  |  | 2 |  | 1328 | use Template::Perlish; | 
|  | 2 |  |  |  |  | 12424 |  | 
|  | 2 |  |  |  |  | 19 |  | 
| 12 | 2 |  |  | 2 |  | 182 | use 5.010; | 
|  | 2 |  |  |  |  | 7 |  | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | sub load_config { | 
| 15 | 1 |  |  | 1 | 1 | 3 | my $config; | 
| 16 |  |  |  |  |  |  | try { | 
| 17 | 1 |  | 50 | 1 |  | 77 | my $config_file = $ENV{WEBSERVICE_FAKE} // 'webservice-fake.yml'; | 
| 18 | 1 |  |  |  |  | 7 | $config = LoadFile($config_file); | 
| 19 |  |  |  |  |  |  |  | 
| 20 | 1 |  |  |  |  | 460 | my $custom = delete $config->{custom}; | 
| 21 | 1 | 50 | 33 |  |  | 8 | if ($custom && !blessed($custom)) { | 
| 22 | 0 | 0 |  |  |  | 0 | $custom = {class => $custom} unless ref $custom; | 
| 23 | 0 |  |  |  |  | 0 | local @INC = @INC; | 
| 24 | 0 |  | 0 |  |  | 0 | unshift @INC, @{$custom->{include} // []}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 25 | 0 |  |  |  |  | 0 | (my $path = "$custom->{class}.pm") =~ s{::}{/}gmxs; | 
| 26 | 0 |  |  |  |  | 0 | require $path; | 
| 27 |  |  |  |  |  |  | } ## end if ($custom && !blessed...) | 
| 28 | 1 | 50 |  |  |  | 4 | $config->{custom} = $custom->new($config) | 
| 29 |  |  |  |  |  |  | if defined $custom; | 
| 30 |  |  |  |  |  |  |  | 
| 31 | 1 |  | 50 |  |  | 10 | $config->{defaults}{template_start} //= '[%'; | 
| 32 | 1 |  | 50 |  |  | 23 | $config->{defaults}{template_stop}  //= '%]'; | 
| 33 | 1 |  | 50 |  |  | 8 | $config->{defaults}{code}           //= 200; | 
| 34 | 1 |  | 50 |  |  | 5 | $config->{v} //= {}; | 
| 35 |  |  |  |  |  |  | } ## end try | 
| 36 |  |  |  |  |  |  | catch { | 
| 37 | 0 |  |  | 0 |  | 0 | my $msg = $_; | 
| 38 | 0 | 0 |  |  |  | 0 | if (ref $_) { | 
| 39 | 0 |  |  |  |  | 0 | require Data::Dumper; | 
| 40 | 0 |  |  |  |  | 0 | local $Data::Dumper::Indent = 1; | 
| 41 | 0 |  |  |  |  | 0 | $msg = Data::Dumper::Dumper($_); | 
| 42 |  |  |  |  |  |  | } | 
| 43 | 0 |  |  |  |  | 0 | $log->error($msg); | 
| 44 | 0 |  |  |  |  | 0 | die $_; | 
| 45 | 1 |  |  |  |  | 12 | }; | 
| 46 | 1 |  |  |  |  | 38 | return $config; | 
| 47 |  |  |  |  |  |  | } ## end sub load_config | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | sub startup { | 
| 50 | 1 |  |  | 1 | 1 | 28479 | my $self = shift; | 
| 51 |  |  |  |  |  |  |  | 
| 52 | 1 |  |  |  |  | 9 | my $config = $self->load_config; | 
| 53 | 1 |  |  | 0 |  | 13 | $self->helper(config => sub { $config }); | 
|  | 0 |  |  |  |  | 0 |  | 
| 54 | 1 |  | 50 |  |  | 98 | $self->secrets($config->{secrets} // ['Fake off!']); | 
| 55 |  |  |  |  |  |  |  | 
| 56 | 1 |  |  |  |  | 11 | my $r = $self->routes; | 
| 57 | 1 |  |  |  |  | 7 | for my $spec (@{$config->{routes}}) { | 
|  | 1 |  |  |  |  | 3 |  | 
| 58 | 8 |  |  |  |  | 474 | my $route = $r->any($spec->{path}); | 
| 59 |  |  |  |  |  |  | my @methods = | 
| 60 | 1 |  |  |  |  | 4 | exists($spec->{methods}) ? @{$spec->{methods}} | 
| 61 |  |  |  |  |  |  | : exists($spec->{method})  ? $spec->{method} | 
| 62 | 8 | 100 |  |  |  | 3599 | :                            (); | 
|  |  | 100 |  |  |  |  |  | 
| 63 | 8 | 100 |  |  |  | 37 | $route->methods(map { uc($_) } @methods) if @methods; | 
|  | 7 |  |  |  |  | 40 |  | 
| 64 | 8 |  |  |  |  | 106 | $route->to(cb => $self->callback($spec, $config)); | 
| 65 |  |  |  |  |  |  | } ## end for my $spec (@{$config...}) | 
| 66 |  |  |  |  |  |  |  | 
| 67 | 1 |  |  |  |  | 60 | return $self; | 
| 68 |  |  |  |  |  |  | } ## end sub startup | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | sub callback { | 
| 71 | 8 |  |  | 8 | 1 | 22 | my ($self, $spec, $config) = @_; | 
| 72 | 8 |  |  |  |  | 22 | my $defaults = $config->{defaults}; | 
| 73 |  |  |  |  |  |  |  | 
| 74 | 8 |  |  |  |  | 32 | my $body_expander = $self->body_expander($spec, $config); | 
| 75 | 8 |  |  |  |  | 35 | my $headers_expander = $self->headers_expander($spec, $config); | 
| 76 | 8 |  |  |  |  | 45 | my $body_wrapper = $self->body_wrapper($spec, $config); | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | return sub { | 
| 79 | 12 |  |  | 12 |  | 159321 | my $c = shift; | 
| 80 |  |  |  |  |  |  |  | 
| 81 | 12 |  |  |  |  | 74 | my $variables = { | 
| 82 |  |  |  |  |  |  | body_params  => $c->req->body_params->to_hash, | 
| 83 |  |  |  |  |  |  | controller   => $c, | 
| 84 |  |  |  |  |  |  | headers      => $c->req->headers->to_hash, | 
| 85 |  |  |  |  |  |  | params       => $c->req->params->to_hash, | 
| 86 |  |  |  |  |  |  | query_params => $c->req->query_params->to_hash, | 
| 87 |  |  |  |  |  |  | stash        => scalar($c->stash()), | 
| 88 |  |  |  |  |  |  | }; | 
| 89 | 12 |  |  |  |  | 4192 | $log->debug($c->req->to_string()); | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | # body, with exception handling for empty one, and wrapping | 
| 92 | 12 |  |  |  |  | 8187 | my $body = $body_expander->($variables); | 
| 93 | 12 | 100 |  |  |  | 8854 | if (!length $body) { | 
| 94 | 2 | 50 |  |  |  | 11 | if ($spec->{on_empty}) { | 
| 95 | 0 |  |  |  |  | 0 | my $r = $c->match()->root(); | 
| 96 | 0 |  |  |  |  | 0 | my $match = Mojolicious::Routes::Match->new(root => $r); | 
| 97 | 0 |  |  |  |  | 0 | $match->match($c => $spec->{on_empty}); | 
| 98 | 0 |  |  |  |  | 0 | my $frame = $match->stack()->[0]; | 
| 99 | 0 |  |  |  |  | 0 | $c->stash($_ => $frame->{$_}) for keys %$frame; | 
| 100 | 0 |  |  |  |  | 0 | return $frame->{cb}->($c); | 
| 101 |  |  |  |  |  |  | } ## end if ($spec->{on_empty}) | 
| 102 |  |  |  |  |  |  | return $c->render_not_found() | 
| 103 | 2 | 50 |  |  |  | 10 | if $spec->{not_found_on_empty}; | 
| 104 |  |  |  |  |  |  | } ## end if (!length $body) | 
| 105 | 12 | 100 |  |  |  | 182 | $body = $body_wrapper->({%$variables, content => $body}) | 
| 106 |  |  |  |  |  |  | if $body_wrapper; | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | # headers | 
| 109 | 12 |  |  |  |  | 2089 | my $headers = $headers_expander->($variables); | 
| 110 |  |  |  |  |  |  |  | 
| 111 | 12 |  |  |  |  | 3208 | my $response = $c->res; | 
| 112 | 12 |  |  |  |  | 294 | $response->body($body); | 
| 113 | 12 |  |  |  |  | 692 | my $rhs = $response->headers(); | 
| 114 | 12 |  |  |  |  | 154 | $rhs->header($_, @{$headers->{$_}}) for keys %$headers; | 
|  | 38 |  |  |  |  | 848 |  | 
| 115 | 12 |  |  |  |  | 319 | $response->fix_headers(); | 
| 116 |  |  |  |  |  |  |  | 
| 117 | 12 |  | 66 |  |  | 2346 | $c->rendered($spec->{code} // $defaults->{code}); | 
| 118 | 8 |  |  |  |  | 4959 | }; | 
| 119 |  |  |  |  |  |  |  | 
| 120 |  |  |  |  |  |  | } ## end sub callback | 
| 121 |  |  |  |  |  |  |  | 
| 122 |  |  |  |  |  |  | sub headers_expander { | 
| 123 | 8 |  |  | 8 | 1 | 27 | my ($self, $spec, $config) = @_; | 
| 124 | 8 |  |  |  |  | 19 | my $defaults = $config->{defaults}; | 
| 125 |  |  |  |  |  |  |  | 
| 126 | 8 |  | 33 |  |  | 49 | my $start = $spec->{template_start} // $defaults->{template_start}; | 
| 127 | 8 |  | 33 |  |  | 38 | my $stop  = $spec->{template_stop}  // $defaults->{template_stop}; | 
| 128 |  |  |  |  |  |  |  | 
| 129 | 8 |  |  |  |  | 18 | my %hef; | 
| 130 | 8 |  |  |  |  | 16 | for my $hs ( | 
| 131 | 8 |  | 50 |  |  | 41 | @{$defaults->{headers} // []},    # take them | 
| 132 | 8 |  | 100 |  |  | 42 | @{$spec->{headers}     // []},    # all | 
| 133 |  |  |  |  |  |  | ) | 
| 134 |  |  |  |  |  |  | { | 
| 135 | 12 |  |  |  |  | 51 | for my $name (keys %$hs) { | 
| 136 | 20 |  |  |  |  | 49 | my $template = $hs->{$name}; | 
| 137 |  |  |  |  |  |  | my $expander = Template::Perlish->new( | 
| 138 |  |  |  |  |  |  | start     => $start, | 
| 139 |  |  |  |  |  |  | stop      => $stop, | 
| 140 |  |  |  |  |  |  | variables => { | 
| 141 |  |  |  |  |  |  | spec   => $spec, | 
| 142 |  |  |  |  |  |  | config => $config, | 
| 143 |  |  |  |  |  |  | v      => $config->{v}, | 
| 144 |  |  |  |  |  |  | } | 
| 145 | 20 |  |  |  |  | 122 | )->compile_as_sub($template); | 
| 146 | 20 |  | 50 |  |  | 19184 | push @{$hef{$name} //= []}, $expander; | 
|  | 20 |  |  |  |  | 178 |  | 
| 147 |  |  |  |  |  |  | } ## end for my $name (keys %$hs) | 
| 148 |  |  |  |  |  |  | } ## end for my $hs (@{$defaults...}) | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | # Ensure there will be a Content-Type | 
| 151 |  |  |  |  |  |  | $hef{'Content-Type'} //= | 
| 152 | 8 |  | 100 | 9 |  | 71 | [sub { return 'application/json' }]; | 
|  | 9 |  |  |  |  | 40 |  | 
| 153 |  |  |  |  |  |  |  | 
| 154 |  |  |  |  |  |  | return sub { | 
| 155 | 12 |  |  | 12 |  | 44 | my ($variables) = @_; | 
| 156 |  |  |  |  |  |  | return { | 
| 157 |  |  |  |  |  |  | map { | 
| 158 | 12 |  |  |  |  | 67 | $_ => [map { $_->($variables) } @{$hef{$_}}]; | 
|  | 38 |  |  |  |  | 7071 |  | 
|  | 38 |  |  |  |  | 669 |  | 
|  | 38 |  |  |  |  | 101 |  | 
| 159 |  |  |  |  |  |  | } keys %hef | 
| 160 |  |  |  |  |  |  | }; | 
| 161 | 8 |  |  |  |  | 54 | }; | 
| 162 |  |  |  |  |  |  | } ## end sub headers_expander | 
| 163 |  |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  | sub body_expander { | 
| 165 | 8 |  |  | 8 | 1 | 22 | my ($self, $spec, $config) = @_; | 
| 166 | 8 |  |  |  |  | 21 | my $defaults = $config->{defaults}; | 
| 167 |  |  |  |  |  |  |  | 
| 168 | 8 |  | 50 |  |  | 35 | my $body  = $spec->{body}           // '[%%]'; | 
| 169 | 8 |  | 33 |  |  | 51 | my $start = $spec->{template_start} // $defaults->{template_start}; | 
| 170 | 8 |  | 33 |  |  | 43 | my $stop  = $spec->{template_stop}  // $defaults->{template_stop}; | 
| 171 |  |  |  |  |  |  |  | 
| 172 |  |  |  |  |  |  | my $be = Template::Perlish->new( | 
| 173 |  |  |  |  |  |  | start     => $start, | 
| 174 |  |  |  |  |  |  | stop      => $stop, | 
| 175 |  |  |  |  |  |  | variables => { | 
| 176 |  |  |  |  |  |  | spec   => $spec, | 
| 177 |  |  |  |  |  |  | config => $config, | 
| 178 |  |  |  |  |  |  | v      => $config->{v}, | 
| 179 |  |  |  |  |  |  | } | 
| 180 | 8 |  |  |  |  | 56 | )->compile_as_sub($body); | 
| 181 |  |  |  |  |  |  |  | 
| 182 | 8 |  | 50 |  |  | 9501 | my $trim = $spec->{trim} //= ''; | 
| 183 |  |  |  |  |  |  | return sub { | 
| 184 | 0 |  |  | 0 |  | 0 | (my $body = $be->(@_)) =~ s{^\s+|\s+$}{}gmxs; | 
| 185 | 0 |  |  |  |  | 0 | return $body; | 
| 186 |  |  |  |  |  |  | } | 
| 187 | 8 | 50 |  |  |  | 31 | if $trim eq 'lines'; | 
| 188 |  |  |  |  |  |  | return sub { | 
| 189 | 0 |  |  | 0 |  | 0 | (my $body = $be->(@_)) =~ s{\A\s+|\s+\z}{}gmxs; | 
| 190 | 0 |  |  |  |  | 0 | return $body; | 
| 191 |  |  |  |  |  |  | } | 
| 192 | 8 | 50 |  |  |  | 25 | if $trim eq 'ends'; | 
| 193 | 8 |  |  |  |  | 21 | return $be; | 
| 194 |  |  |  |  |  |  | } ## end sub body_expander | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | sub body_wrapper { | 
| 197 | 8 |  |  | 8 | 1 | 29 | my ($self, $spec, $config) = @_; | 
| 198 | 8 |  |  |  |  | 22 | my $defaults = $config->{defaults}; | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | my $wrapper = | 
| 201 |  |  |  |  |  |  | exists($spec->{body_wrapper})     ? $spec->{body_wrapper} | 
| 202 |  |  |  |  |  |  | : exists($defaults->{body_wrapper}) ? $defaults->{body_wrapper} | 
| 203 | 8 | 50 |  |  |  | 40 | :                                     undef; | 
|  |  | 100 |  |  |  |  |  | 
| 204 | 8 | 100 |  |  |  | 58 | return unless defined $wrapper; | 
| 205 |  |  |  |  |  |  |  | 
| 206 | 4 |  | 33 |  |  | 25 | my $start = $spec->{template_start} // $defaults->{template_start}; | 
| 207 | 4 |  | 33 |  |  | 26 | my $stop  = $spec->{template_stop}  // $defaults->{template_stop}; | 
| 208 |  |  |  |  |  |  | return Template::Perlish->new( | 
| 209 |  |  |  |  |  |  | start     => $start, | 
| 210 |  |  |  |  |  |  | stop      => $stop, | 
| 211 |  |  |  |  |  |  | variables => { | 
| 212 |  |  |  |  |  |  | spec   => $spec, | 
| 213 |  |  |  |  |  |  | config => $config, | 
| 214 |  |  |  |  |  |  | v      => $config->{v}, | 
| 215 |  |  |  |  |  |  | } | 
| 216 | 4 |  |  |  |  | 29 | )->compile_as_sub($wrapper); | 
| 217 |  |  |  |  |  |  | } ## end sub body_wrapper | 
| 218 |  |  |  |  |  |  |  | 
| 219 |  |  |  |  |  |  | 1; | 
| 220 |  |  |  |  |  |  | __END__ |