File Coverage

blib/lib/Plack/Middleware/Negotiate.pm
Criterion Covered Total %
statement 96 99 96.9
branch 30 36 83.3
condition 32 47 68.0
subroutine 18 21 85.7
pod 7 7 100.0
total 183 210 87.1


line stmt bran cond sub pod time code
1             package Plack::Middleware::Negotiate;
2 6     6   44007 use strict;
  6         14  
  6         239  
3 6     6   26 use warnings;
  6         8  
  6         187  
4 6     6   68 use v5.10.1;
  6         17  
  6         393  
5              
6             our $VERSION = '0.20';
7              
8 6     6   424 use parent 'Plack::Middleware';
  6         306  
  6         44  
9              
10 6     6   20804 use Plack::Util::Accessor qw(formats parameter extension explicit);
  6         11  
  6         40  
11 6     6   4341 use Plack::Request;
  6         198728  
  6         252  
12 6     6   12080 use HTTP::Negotiate qw(choose);
  6         177  
  6         445  
13 6     6   34 use Carp qw(croak);
  6         8  
  6         269  
14              
15 6     6   2774 use Log::Contextual::Easy::Default;
  6         187242  
  6         61  
16              
17             sub prepare_app {
18 9     9 1 25072 my $self = shift;
19              
20 8         411 croak __PACKAGE__ . ' requires formats'
21 9 100 100     32 unless $self->formats and %{$self->formats};
22              
23 7   100     72 $self->formats->{_} //= { };
24              
25 7 100       66 unless ($self->formats->{_}->{type}) {
26 6         30 foreach (grep { $_ ne '_' } keys %{$self->formats}) {
  16         45  
  6         15  
27 10 100       37 croak __PACKAGE__ . " format requires type: $_"
28             unless $self->formats->{$_}->{type};
29             }
30             }
31              
32 6 100       53 if (!$self->app) {
33             $self->app( sub {
34 1     1   9 [ 406, ['Content-Type'=>'text/plain'], ['Not Acceptable']];
35 1         9 } );
36             }
37             }
38              
39             sub call {
40 17     17 1 69928 my ($self, $env) = @_;
41              
42 17         36 my $orig_path = $env->{PATH_INFO};
43              
44 17         41 my $format = $self->negotiate($env);
45 17         57 $env->{'negotiate.format'} = $format;
46              
47 17         36 my $app;
48 17 50 66     117 if ( $format and $format ne '_' and $self->known($format) ) {
      66        
49 16         127 $app = $self->formats->{$format}->{app};
50             }
51 17   66     119 $app //= $self->app;
52              
53             Plack::Util::response_cb( $app->($env), sub {
54 17     17   394 my $res = shift;
55 17         56 $self->add_headers( $res->[1], $env->{'negotiate.format'} );
56 17         77 $env->{PATH_INFO} = $orig_path;
57 17         32 $res;
58 17         82 });
59             }
60              
61             sub add_headers {
62 17     17 1 21 my ($self, $headers, $name) = @_;
63              
64 17   100     35 my $format = $self->about($name) || return;
65              
66 16 50       41 if (!Plack::Util::header_exists($headers,'Content-Type')) {
67 16         229 my $type = $format->{type};
68 16 100       44 $type .= "; charset=". $format->{charset}
69             if $format->{charset};
70 16         42 Plack::Util::header_set($headers,'Content-Type',$type);
71             }
72              
73 16 50       228 if (!Plack::Util::header_exists($headers,'Content-Language')) {
74 16 100       219 Plack::Util::header_set($headers,'Content-Language',$format->{language})
75             if $format->{language};
76             }
77            
78 16         84 Plack::Util::header_push($headers,'Vary','Accept');
79              
80             }
81              
82             sub negotiate {
83 17     17 1 24 my ($self, $env) = @_;
84 17         88 my $req = Plack::Request->new($env);
85              
86 17 100       142 if (defined $self->parameter) {
87 11         79 my $param = $self->parameter;
88              
89 11 100       137 my $format = $env->{QUERY_STRING} =~ /(^|&)$param=([^&]+)/ ? $2 : undef;
90              
91 11 100       23 if (!$self->known($format)) { # no GET parameter or unknown format
92 5         24 $format = $req->body_parameters->{$param};
93             }
94              
95 11 100       793 if ($self->known($format)) {
96 7     0   63 log_trace { "format $format chosen based on query parameter" };
  0         0  
97 7 100       357 unless ( $env->{QUERY_STRING} =~ s/&$param=([^&]+)//) {
98 5         64 $env->{QUERY_STRING} =~ s/^$param=([^&]+)&?//;
99             }
100 7         39 return $format;
101             }
102             }
103              
104 10 50 66     62 if ($self->extension and $req->path =~ /\.([^.]+)$/ and $self->known($1)) {
      66        
105 2         19 my $format = $1;
106 2 50       5 $env->{PATH_INFO} =~ s/\.$format$//
107             if $self->extension eq 'strip';
108 2     0   34 log_trace { "format $format chosen based on extension" };
  0         0  
109 2         114 return $format;
110             }
111              
112 8 50       59 if (!$self->explicit) {
113 8         129 my $format = choose($self->variants, $req->headers);
114 8     0   2163 log_trace { "format $format chosen based on HTTP content negotiation" };
  0         0  
115 8         471 return $format;
116             }
117             }
118              
119             sub about {
120 31     31 1 33 my ($self, $name) = @_;
121              
122 31 100 66     135 return unless defined $name and $name ne '_';
123              
124 30         54 my $default = $self->formats->{_};
125 30   50     124 my $format = $self->formats->{$name} || return;
126              
127             return {
128 30   33     581 quality => $format->{quality} // $default->{quality} // 1,
      50        
      66        
      33        
      66        
      66        
129             type => $format->{type} // $default->{type},
130             encoding => $format->{encoding} // $default->{encoding},
131             charset => $format->{charset} // $default->{charset},
132             language => $format->{language} // $default->{language},
133             };
134             }
135              
136             sub known {
137 40     40 1 80 my ($self, $name) = @_;
138 40   100     197 return (defined $name and $name ne '_' and exists $self->formats->{$name});
139             }
140              
141             sub variants {
142 8     8 1 12 my $self = shift;
143             return [
144 6         33 sort { $a->[0] cmp $b->[0] }
  14         36  
145             map {
146 22         63 my $format = $self->about($_);
147             [
148 14         77 $_,
149             $format->{quality},
150             $format->{type},
151             $format->{encoding},
152             $format->{charset},
153             $format->{language},
154             0
155             ] }
156 8         12 grep { $_ ne '_' } keys %{$self->formats}
  8         20  
157             ];
158             }
159              
160             1;
161             __END__