File Coverage

blib/lib/Web/Machine/FSM.pm
Criterion Covered Total %
statement 84 102 82.3
branch 27 42 64.2
condition 8 14 57.1
subroutine 18 20 90.0
pod 4 5 80.0
total 141 183 77.0


line stmt bran cond sub pod time code
1             package Web::Machine::FSM;
2             # ABSTRACT: The State Machine runner
3              
4 13     13   253136 use strict;
  13         20  
  13         446  
5 13     13   55 use warnings;
  13         15  
  13         558  
6              
7             our $VERSION = '0.16';
8              
9 13     13   7042 use IO::Handle::Util 'io_from_getline';
  13         52375  
  13         76  
10 13     13   5506 use Plack::Util;
  13         14514  
  13         274  
11 13     13   512 use Try::Tiny;
  13         1015  
  13         859  
12 13     13   4834 use HTTP::Status qw[ is_error ];
  13         29402  
  13         1370  
13 13     13   5083 use Web::Machine::I18N;
  13         31  
  13         447  
14 13         132 use Web::Machine::FSM::States qw[
15             start_state
16             is_status_code
17             is_new_state
18             get_state_name
19             get_state_desc
20 13     13   6000 ];
  13         39  
21              
22             sub new {
23 36     36 1 154 my ($class, %args) = @_;
24 36   50     281 bless {
25             tracing => !!$args{'tracing'},
26             tracing_header => $args{'tracing_header'} || 'X-Web-Machine-Trace'
27             } => $class
28             }
29              
30 124     124 1 267 sub tracing { (shift)->{'tracing'} }
31 269     269 1 74139 sub tracing_header { (shift)->{'tracing_header'} }
32              
33             sub run {
34 124     124 1 3431 my ( $self, $resource ) = @_;
35              
36 124         121 my $DEBUG;
37 124 50       336 if ( $ENV{WM_DEBUG} ) {
38             $DEBUG
39             = $ENV{WM_DEBUG} eq 'diag'
40 0     0   0 ? sub { Test::More::diag( $_[0] ) }
41 0 0   0   0 : sub { warn "$_[0]\n" };
  0         0  
42             }
43              
44 124         602 my $request = $resource->request;
45 124         473 my $response = $resource->response;
46 124         148 my $metadata = {};
47 124         312 $request->env->{'web.machine.context'} = $metadata;
48              
49 124         385 my @trace;
50 124         235 my $tracing = $self->tracing;
51              
52 124         356 my $state = start_state;
53              
54             try {
55 124     124   2492 while (1) {
56 2579 50       3389 $DEBUG->( 'entering '
57             . get_state_name($state) . ' ('
58             . get_state_desc($state)
59             . ')' )
60             if $DEBUG;
61 2579 100       4969 push @trace => get_state_name( $state ) if $tracing;
62 2579         5023 my $result = $state->( $resource, $request, $response, $metadata );
63 2575 50       28163 if ( ! ref $result ) {
    100          
    50          
64             # TODO:
65             # We should be I18N this
66             # specific error
67             # - SL
68 0 0 0     0 $DEBUG->( '! ERROR with ' . ( $result || 'undef' ) )
69             if $DEBUG;
70 0         0 $response->status( 500 );
71 0         0 $response->header( 'Content-Type' => 'text/plain' );
72 0   0     0 $response->body( [ "Got bad state: " . ($result || 'undef') ] );
73 0         0 last;
74             }
75             elsif ( is_status_code( $result ) ) {
76 120 50       286 $DEBUG->( '.. terminating with ' . ${$result} ) if $DEBUG;
  0         0  
77 120         325 $response->status( $$result );
78              
79 120 100 66     770 if ( is_error( $$result ) && !$response->body ) {
80             # NOTE:
81             # this will default to en, however I
82             # am not really confident that this
83             # will end up being sufficient.
84             # - SL
85 57 50 100     845 my $lang = Web::Machine::I18N->get_handle( $metadata->{'Language'} || 'en' )
86             or die "Could not get language handle for " . $metadata->{'Language'};
87 57         8905 $response->header( 'Content-Type' => 'text/plain' );
88 57         1537 $response->body([ $lang->maketext( $$result ) ]);
89             }
90              
91 120 50       2040 if ($DEBUG) {
92 0         0 require Data::Dumper;
93 0         0 local $Data::Dumper::Terse = 1;
94 0         0 local $Data::Dumper::Indent = 1;
95 0         0 local $Data::Dumper::Useqq = 1;
96 0         0 local $Data::Dumper::Deparse = 1;
97 0         0 local $Data::Dumper::Quotekeys = 0;
98 0         0 local $Data::Dumper::Sortkeys = 1;
99 0         0 $DEBUG->( Data::Dumper::Dumper( $request->env ) );
100 0         0 $DEBUG->( Data::Dumper::Dumper( $response->finalize ) );
101             }
102              
103 120         254 last;
104             }
105             elsif ( is_new_state( $result ) ) {
106 2455 50       3282 $DEBUG->( '-> transitioning to ' . get_state_name($result) )
107             if $DEBUG;
108 2455         2755 $state = $result;
109             }
110             }
111             } catch {
112             # TODO:
113             # We should be I18N the errors
114             # - SL
115 4 50   4   1159 $DEBUG->($_) if $DEBUG;
116              
117 4 100       15 if ( $request->logger ) {
118 1         11 $request->logger->( { level => 'error', message => $_ } );
119             }
120              
121 4         34 $response->status( 500 );
122              
123             # NOTE:
124             # this way you can handle the
125             # exception if you like via
126             # the finish_request call below
127             # - SL
128 4         32 $metadata->{'exception'} = $_;
129 124         943 };
130              
131 124 100       2645 $self->filter_response( $resource )
132             unless $request->env->{'web.machine.streaming_push'};
133             try {
134 124     124   3294 $resource->finish_request( $metadata );
135             }
136             catch {
137 1 50   1   15 $DEBUG->($_) if $DEBUG;
138              
139 1 50       4 if ( $request->logger ) {
140 1         9 $request->logger->( { level => 'error', message => $_ } );
141             }
142              
143 1         12 $response->status( 500 );
144 124         3843 };
145 124 100       1464 $response->header( $self->tracing_header, (join ',' => @trace) )
146             if $tracing;
147              
148 124         2279 $response;
149             }
150              
151             sub filter_response {
152 119     119 0 627 my $self = shift;
153 119         258 my ($resource) = @_;
154              
155 119         399 my $response = $resource->response;
156 119         250 my $filters = $resource->request->env->{'web.machine.content_filters'};
157              
158             # XXX patch Plack::Response to make _body not private?
159 119         458 my $body = $response->_body;
160              
161 119         1393 for my $filter (@$filters) {
162 31 100       72 if (ref($body) eq 'ARRAY') {
163 18         38 $response->body( [ map { $filter->($_) } @$body ] );
  18         46  
164 18         222 $body = $response->body;
165             }
166             else {
167 13         18 my $old_body = $body;
168 13     127   88 $body = io_from_getline sub { $filter->($old_body->getline) };
  127         5860  
169 13         11654 $response->body($body);
170             }
171             }
172              
173 119 100 100     533 if (ref($body) eq 'ARRAY'
174             && !Plack::Util::status_with_no_entity_body($response->status)) {
175 96         1032 $response->header(
176             'Content-Length' => Plack::Util::content_length($body)
177             );
178             }
179             }
180              
181             1;
182              
183             __END__