File Coverage

blib/lib/Plack/Middleware/Negotiate.pm
Criterion Covered Total %
statement 92 95 96.8
branch 28 34 82.3
condition 32 50 64.0
subroutine 17 20 85.0
pod 7 7 100.0
total 176 206 85.4


line stmt bran cond sub pod time code
1             package Plack::Middleware::Negotiate;
2             #ABSTRACT: Apply HTTP content negotiation as Plack middleware
3             our $VERSION = '0.10'; #VERSION
4              
5 6     6   51981 use strict;
  6         11  
  6         205  
6 6     6   82 use v5.10.1;
  6         25  
  6         262  
7 6     6   856 use parent 'Plack::Middleware';
  6         370  
  6         44  
8              
9 6     6   31853 use Plack::Util::Accessor qw(formats parameter extension explicit);
  6         13  
  6         1823  
10 6     6   6033 use Plack::Request;
  6         312822  
  6         224  
11 6     6   18152 use HTTP::Negotiate qw(choose);
  6         199  
  6         786  
12 6     6   35 use Carp qw(croak);
  6         16  
  6         272  
13              
14 6     6   5059 use Log::Contextual::Easy::Default;
  6         313733  
  6         73  
15              
16             sub prepare_app {
17 9     9 1 32857 my $self = shift;
18              
19 8         518 croak __PACKAGE__ . ' requires formats'
20 9 100 100     42 unless $self->formats and %{$self->formats};
21              
22 7   100     93 $self->formats->{_} //= { };
23              
24 7 100       90 unless ($self->formats->{_}->{type}) {
25 6         36 foreach (grep { $_ ne '_' } keys %{$self->formats}) {
  16         54  
  6         19  
26 10 100       48 croak __PACKAGE__ . " format requires type: $_"
27             unless $self->formats->{$_}->{type};
28             }
29             }
30              
31 6 100       70 if (!$self->app) {
32             $self->app( sub {
33 1     1   10 [ 406, ['Content-Type'=>'text/plain'], ['Not Acceptable']];
34 1         11 } );
35             }
36             }
37              
38             sub call {
39 17     17 1 109101 my ($self, $env) = @_;
40              
41 17         40 my $orig_path = $env->{PATH_INFO};
42              
43 17         55 my $format = $self->negotiate($env);
44 17         51 $env->{'negotiate.format'} = $format;
45              
46 17         27 my $app;
47 17 50 66     134 if ( $format and $format ne '_' and $self->formats->{$format} ) {
      66        
48 16         160 $app = $self->formats->{$format}->{app};
49             }
50 17   66     180 $app //= $self->app;
51              
52             Plack::Util::response_cb( $app->($env), sub {
53 17     17   414 my $res = shift;
54 17         62 $self->add_headers( $res->[1], $env->{'negotiate.format'} );
55 17         27 $env->{PATH_INFO} = $orig_path;
56 17         37 $res;
57 17         100 });
58             }
59              
60             sub add_headers { # TODO: use Plack::Util or P:M:Headers
61 17     17 1 36 my ($self, $headers, $name) = @_;
62              
63 17   100     51 my $format = $self->about($name) || return;
64 16         30 my $fields = { @$headers };
65              
66 16 50       44 if (!$fields->{'Content-Type'}) {
67 16         30 my $type = $format->{type};
68 16 100       45 $type .= "; charset=". $format->{charset}
69             if $format->{charset};
70 16         57 push @$headers, 'Content-Type' => $type;
71             }
72              
73 16 50 33     75 push @$headers, 'Content-Language' => $format->{language}
74             if $format->{language} and !$fields->{'Content-Language'};
75             }
76              
77             sub negotiate {
78 17     17 1 28 my ($self, $env) = @_;
79 17         124 my $req = Plack::Request->new($env);
80              
81 17 100       199 if (defined $self->parameter) {
82 11         99 my $param = $self->parameter;
83              
84 11 100       184 my $format = $env->{QUERY_STRING} =~ /(^|&)$param=([^&]+)/ ? $2 : undef;
85              
86 11 100       40 if (!$self->known($format)) { # no GET parameter or unknown format
87 5         37 $format = $req->body_parameters->{$param};
88             }
89              
90 11 100       1024 if ($self->known($format)) {
91 7     0   87 log_trace { "format $format chosen based on query parameter" };
  0         0  
92 7 100       434 unless ( $env->{QUERY_STRING} =~ s/&$param=([^&]+)//) {
93 5         67 $env->{QUERY_STRING} =~ s/^$param=([^&]+)&?//;
94             }
95 7         30 return $format;
96             }
97             }
98              
99 10 50 66     72 if ($self->extension and $req->path =~ /\.([^.]+)$/ and $self->formats->{$1}) {
      66        
100 2         83 my $format = $1;
101 2 50       8 $env->{PATH_INFO} =~ s/\.$format$//
102             if $self->extension eq 'strip';
103 2     0   51 log_trace { "format $format chosen based on extension" };
  0         0  
104 2         160 return $format;
105             }
106              
107 8 50       78 if (!$self->explicit) {
108 8         68 my $format = choose($self->variants, $req->headers);
109 8     0   2585 log_trace { "format $format chosen based on HTTP content negotiation" };
  0         0  
110 8         687 return $format;
111             }
112             }
113              
114             sub about {
115 31     31 1 48 my ($self, $name) = @_;
116              
117 31 100 66     182 return unless defined $name and $name ne '_';
118              
119 30         85 my $default = $self->formats->{_};
120 30   50     175 my $format = $self->formats->{$name} || return;
121              
122             return {
123 30   33     737 quality => $format->{quality} // $default->{quality} // 1,
      50        
      66        
      33        
      66        
      33        
124             type => $format->{type} // $default->{type},
125             encoding => $format->{encoding} // $default->{encoding},
126             charset => $format->{charset} // $default->{charset},
127             language => $format->{language} // $default->{language},
128             };
129             }
130              
131             sub known {
132 22     22 1 38 my ($self, $name) = @_;
133 22   100     158 return (defined $name and $name ne '_' and exists $self->formats->{$name});
134             }
135              
136             sub variants {
137 8     8 1 14 my $self = shift;
138             return [
139 6         41 sort { $a->[0] cmp $b->[0] }
  14         40  
140             map {
141 22         72 my $format = $self->about($_);
142             [
143 14         94 $_,
144             $format->{quality},
145             $format->{type},
146             $format->{encoding},
147             $format->{charset},
148             $format->{language},
149             0
150             ] }
151 8         12 grep { $_ ne '_' } keys %{$self->formats}
  8         29  
152             ];
153             }
154              
155             1;
156              
157              
158             __END__