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   201698 use strict;
  13         14  
  13         288  
5 13     13   38 use warnings;
  13         13  
  13         409  
6              
7             our $VERSION = '0.17';
8              
9 13     13   5094 use IO::Handle::Util 'io_from_getline';
  13         42067  
  13         67  
10 13     13   4283 use Plack::Util;
  13         11566  
  13         243  
11 13     13   420 use Try::Tiny;
  13         876  
  13         586  
12 13     13   3738 use HTTP::Status qw[ is_error ];
  13         25786  
  13         1001  
13 13     13   4119 use Web::Machine::I18N;
  13         25  
  13         366  
14 13         100 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   5228 ];
  13         26  
21              
22             sub new {
23 36     36 1 168 my ($class, %args) = @_;
24             bless {
25             tracing => !!$args{'tracing'},
26 36   50     243 tracing_header => $args{'tracing_header'} || 'X-Web-Machine-Trace'
27             } => $class
28             }
29              
30 124     124 1 207 sub tracing { (shift)->{'tracing'} }
31 269     269 1 66211 sub tracing_header { (shift)->{'tracing_header'} }
32              
33             sub run {
34 124     124 1 2942 my ( $self, $resource ) = @_;
35              
36 124         111 my $DEBUG;
37 124 50       252 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         454 my $request = $resource->request;
45 124         370 my $response = $resource->response;
46 124         134 my $metadata = {};
47 124         259 $request->env->{'web.machine.context'} = $metadata;
48              
49 124         316 my @trace;
50 124         195 my $tracing = $self->tracing;
51              
52 124         347 my $state = start_state;
53              
54             try {
55 124     124   2102 while (1) {
56 2579 50       2922 $DEBUG->( 'entering '
57             . get_state_name($state) . ' ('
58             . get_state_desc($state)
59             . ')' )
60             if $DEBUG;
61 2579 100       4152 push @trace => get_state_name( $state ) if $tracing;
62 2579         4353 my $result = $state->( $resource, $request, $response, $metadata );
63 2575 50       23268 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       184 $DEBUG->( '.. terminating with ' . ${$result} ) if $DEBUG;
  0         0  
77 120         282 $response->status( $$result );
78              
79 120 100 66     577 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             my $lang = Web::Machine::I18N->get_handle( $metadata->{'Language'} || 'en' )
86 57 50 100     670 or die "Could not get language handle for " . $metadata->{'Language'};
87 57         8117 $response->header( 'Content-Type' => 'text/plain' );
88 57         1330 $response->body([ $lang->maketext( $$result ) ]);
89             }
90              
91 120 50       1654 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         222 last;
104             }
105             elsif ( is_new_state( $result ) ) {
106 2455 50       2814 $DEBUG->( '-> transitioning to ' . get_state_name($result) )
107             if $DEBUG;
108 2455         2381 $state = $result;
109             }
110             }
111             } catch {
112             # TODO:
113             # We should be I18N the errors
114             # - SL
115 4 50   4   1047 $DEBUG->($_) if $DEBUG;
116              
117 4 100       14 if ( $request->logger ) {
118 1         8 $request->logger->( { level => 'error', message => $_ } );
119             }
120              
121 4         27 $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         26 $metadata->{'exception'} = $_;
129 124         681 };
130              
131             $self->filter_response( $resource )
132 124 100       2140 unless $request->env->{'web.machine.streaming_push'};
133             try {
134 124     124   2670 $resource->finish_request( $metadata );
135             }
136             catch {
137 1 50   1   16 $DEBUG->($_) if $DEBUG;
138              
139 1 50       4 if ( $request->logger ) {
140 1         9 $request->logger->( { level => 'error', message => $_ } );
141             }
142              
143 1         7 $response->status( 500 );
144 124         3138 };
145 124 100       1181 $response->header( $self->tracing_header, (join ',' => @trace) )
146             if $tracing;
147              
148 124         1941 $response;
149             }
150              
151             sub filter_response {
152 119     119 0 493 my $self = shift;
153 119         209 my ($resource) = @_;
154              
155 119         298 my $response = $resource->response;
156 119         198 my $filters = $resource->request->env->{'web.machine.content_filters'};
157              
158             # XXX patch Plack::Response to make _body not private?
159 119         345 my $body = $response->_body;
160              
161 119         1129 for my $filter (@$filters) {
162 31 100       62 if (ref($body) eq 'ARRAY') {
163 18         30 $response->body( [ map { $filter->($_) } @$body ] );
  18         39  
164 18         187 $body = $response->body;
165             }
166             else {
167 13         11 my $old_body = $body;
168 13     127   55 $body = io_from_getline sub { $filter->($old_body->getline) };
  127         4626  
169 13         9676 $response->body($body);
170             }
171             }
172              
173 119 100 100     448 if (ref($body) eq 'ARRAY'
174             && !Plack::Util::status_with_no_entity_body($response->status)) {
175 96         819 $response->header(
176             'Content-Length' => Plack::Util::content_length($body)
177             );
178             }
179             }
180              
181             1;
182              
183             __END__