File Coverage

blib/lib/Web/Machine/FSM.pm
Criterion Covered Total %
statement 84 94 89.3
branch 26 38 68.4
condition 8 14 57.1
subroutine 19 19 100.0
pod 4 5 80.0
total 141 170 82.9


line stmt bran cond sub pod time code
1             package Web::Machine::FSM;
2             BEGIN {
3 13     13   478058 $Web::Machine::FSM::AUTHORITY = 'cpan:STEVAN';
4             }
5             # ABSTRACT: The State Machine runner
6             $Web::Machine::FSM::VERSION = '0.15';
7 13     13   87 use strict;
  13         26  
  13         464  
8 13     13   72 use warnings;
  13         22  
  13         1285  
9              
10 13     13   17835 use IO::Handle::Util 'io_from_getline';
  13         101988  
  13         235  
11 13     13   11224 use Plack::Util;
  13         22949  
  13         370  
12 13     13   1238 use Try::Tiny;
  13         2588  
  13         1200  
13 13     13   11411 use HTTP::Status qw[ is_error ];
  13         51460  
  13         1965  
14 13     13   8982 use Web::Machine::I18N;
  13         173  
  13         672  
15 13         193 use Web::Machine::FSM::States qw[
16             start_state
17             is_status_code
18             is_new_state
19             get_state_name
20             get_state_desc
21 13     13   11135 ];
  13         353  
22              
23             sub new {
24 35     35 1 224 my ($class, %args) = @_;
25 35   50     445 bless {
26             tracing => !!$args{'tracing'},
27             tracing_header => $args{'tracing_header'} || 'X-Web-Machine-Trace'
28             } => $class
29             }
30              
31 123     123 1 473 sub tracing { (shift)->{'tracing'} }
32 269     269 1 179763 sub tracing_header { (shift)->{'tracing_header'} }
33              
34             sub run {
35 123     123 1 7135 my ( $self, $resource ) = @_;
36              
37 123         422 my $DEBUG = $ENV{'WM_DEBUG'};
38              
39 123         844 my $request = $resource->request;
40 123         958 my $response = $resource->response;
41 123         305 my $metadata = {};
42 123         501 $request->env->{'web.machine.context'} = $metadata;
43              
44 123         655 my @trace;
45 123         602 my $tracing = $self->tracing;
46              
47 123         736 my $state = start_state;
48              
49             try {
50 123     123   3467 while (1) {
51 2556 50       6921 warn "entering " . get_state_name( $state ) . " (" . get_state_desc( $state ) . ")\n" if $DEBUG;
52 2556 100       7885 push @trace => get_state_name( $state ) if $tracing;
53 2556         25097 my $result = $state->( $resource, $request, $response, $metadata );
54 2552 50       52392 if ( ! ref $result ) {
    100          
    50          
55             # TODO:
56             # We should be I18N this
57             # specific error
58             # - SL
59 0 0 0     0 warn "! ERROR with " . ($result || 'undef') . "\n" if $DEBUG;
60 0         0 $response->status( 500 );
61 0         0 $response->header( 'Content-Type' => 'text/plain' );
62 0   0     0 $response->body( [ "Got bad state: " . ($result || 'undef') ] );
63 0         0 last;
64             }
65             elsif ( is_status_code( $result ) ) {
66 119 50       341 warn ".. terminating with " . ${ $result } . "\n" if $DEBUG;
  0         0  
67 119         1068 $response->status( $$result );
68              
69 119 100 66     1846 if ( is_error( $$result ) && !$response->body ) {
70             # NOTE:
71             # this will default to en, however I
72             # am not really confident that this
73             # will end up being sufficient.
74             # - SL
75 57 50 100     2072 my $lang = Web::Machine::I18N->get_handle( $metadata->{'Language'} || 'en' )
76             or die "Could not get language handle for " . $metadata->{'Language'};
77 57         20014 $response->header( 'Content-Type' => 'text/plain' );
78 57         3485 $response->body([ $lang->maketext( $$result ) ]);
79             }
80              
81 119 50       3218 if ( $DEBUG ) {
82 0         0 require Data::Dumper;
83 0         0 local $Data::Dumper::Useqq = 1;
84 0         0 warn Data::Dumper::Dumper( $request->env );
85 0         0 warn Data::Dumper::Dumper( $response->finalize );
86             }
87              
88 119         541 last;
89             }
90             elsif ( is_new_state( $result ) ) {
91 2433 50       7086 warn "-> transitioning to " . get_state_name( $result ) . "\n" if $DEBUG;
92 2433         4785 $state = $result;
93             }
94             }
95             } catch {
96             # TODO:
97             # We should be I18N the errors
98             # - SL
99 4 50   4   1829 warn $_ if $DEBUG;
100              
101 4 100       21 if ( $request->logger ) {
102 1         16 $request->logger->( { level => 'error', message => $_ } );
103             }
104              
105 4         50 $response->status( 500 );
106              
107             # NOTE:
108             # this way you can handle the
109             # exception if you like via
110             # the finish_request call below
111             # - SL
112 4         46 $metadata->{'exception'} = $_;
113 123         1741 };
114              
115 123 100       4706 $self->filter_response( $resource )
116             unless $request->env->{'web.machine.streaming_push'};
117             try {
118 123     123   4628 $resource->finish_request( $metadata );
119             }
120             catch {
121 1 50   1   17 warn $_ if $DEBUG;
122              
123 1 50       5 if ( $request->logger ) {
124 1         12 $request->logger->( { level => 'error', message => $_ } );
125             }
126              
127 1         11 $response->status( 500 );
128 123         6448 };
129 123 100       2237 $response->header( $self->tracing_header, (join ',' => @trace) )
130             if $tracing;
131              
132 123         6140 $response;
133             }
134              
135             sub filter_response {
136 118     118 0 1071 my $self = shift;
137 118         474 my ($resource) = @_;
138              
139 118         483 my $response = $resource->response;
140 118         436 my $filters = $resource->request->env->{'web.machine.content_filters'};
141              
142             # XXX patch Plack::Response to make _body not private?
143 118         897 my $body = $response->_body;
144              
145 118         2246 for my $filter (@$filters) {
146 31 100       112 if (ref($body) eq 'ARRAY') {
147 18         44 $response->body( [ map { $filter->($_) } @$body ] );
  18         71  
148 18         370 $body = $response->body;
149             }
150             else {
151 13         31 my $old_body = $body;
152 13     127   105 $body = io_from_getline sub { $filter->($old_body->getline) };
  127         16671  
153 13         22616 $response->body($body);
154             }
155             }
156              
157 118 100 100     902 if (ref($body) eq 'ARRAY'
158             && !Plack::Util::status_with_no_entity_body($response->status)) {
159 95         8226 $response->header(
160             'Content-Length' => Plack::Util::content_length($body)
161             );
162             }
163             }
164              
165             1;
166              
167             __END__