File Coverage

blib/lib/Whelk/Formatter.pm
Criterion Covered Total %
statement 41 41 100.0
branch 11 16 68.7
condition 6 12 50.0
subroutine 9 9 100.0
pod 5 6 83.3
total 72 84 85.7


line stmt bran cond sub pod time code
1             package Whelk::Formatter;
2             $Whelk::Formatter::VERSION = '1.04';
3 20     20   12522 use Kelp::Base;
  20         116  
  20         162  
4 20     20   5232 use Carp;
  20         39  
  20         1580  
5 20     20   147 use Whelk::Exception;
  20         43  
  20         182  
6              
7             attr response_format => sub { ... };
8             attr full_response_format => sub { $_[0]->supported_format($_[0]->response_format) };
9             attr supported_formats => sub { {} };
10              
11             sub load_formats
12             {
13 29     29 1 66 my ($self, $app) = @_;
14 29         128 my $app_encoders = $app->encoder_modules;
15              
16 29         368 my %supported = (
17             json => 'application/json',
18             yaml => 'text/yaml',
19             );
20              
21 29         130 foreach my $encoder (keys %supported) {
22             delete $supported{$encoder}
23 58 100       222 if !exists $app_encoders->{$encoder};
24             }
25              
26 29         172 $self->supported_formats(\%supported);
27 29         229 return $self;
28             }
29              
30             sub supported_format
31             {
32 26     26 1 77 my ($self, $format) = @_;
33 26         107 my $formats = $self->supported_formats;
34              
35             croak "Format $format is not supported"
36 26 50       283 unless exists $formats->{$format};
37              
38 26         226 return $formats->{$format};
39             }
40              
41             sub match_format
42             {
43 20     20 1 45 my ($self, $app) = @_;
44 20         58 my $formats = $self->supported_formats;
45              
46 20         196 foreach my $format (keys %$formats) {
47             return $format
48 32 100       1200 if $app->req->content_type_is($formats->{$format});
49             }
50              
51             # Unsupported Media Type
52 2         86 my @accepted = sort values %$formats;
53 2         38 Whelk::Exception->throw(415, hint => 'Supported Content-Types are ' . join(', ', @accepted));
54             }
55              
56             sub get_request_body
57             {
58 20     20 1 268 my ($self, $app) = @_;
59 20         78 my $format = $self->match_format($app);
60              
61             return
62 18 50       854 $format eq 'json' ? $app->req->json_content :
    100          
63             $format eq 'yaml' ? $app->req->yaml_content :
64             undef;
65             }
66              
67             sub format_response
68             {
69 80     80 1 908 my ($self, $app, $data, $special_encoder) = @_;
70 80         394 my $res = $app->res;
71 80         1266 my $ct = $res->content_type;
72              
73             # ensure proper content-type
74 80 50 33     3385 $res->set_content_type($self->full_response_format, $res->charset // $app->charset)
75             if !$ct;
76              
77             # only encode manually if we have a special encoder requested
78 80 50 66     5077 return $app->get_encoder($self->response_format => $special_encoder)->encode($data)
      33        
      66        
79             if $special_encoder && ref $data && (!$ct || $ct eq $self->full_response_format);
80              
81             # otherwise, let Kelp try to handle this
82 76         432 return $data;
83             }
84              
85             sub new
86             {
87 29     29 0 717 my ($class, %args) = @_;
88              
89 29         74 my $app = delete $args{app};
90 29 50       114 croak 'app is required in new'
91             if !$app;
92              
93 29         139 my $self = $class->SUPER::new(%args);
94 29         250 $self->load_formats($app);
95              
96 29         307 return $self;
97             }
98              
99             1;
100              
101             __END__
102              
103             =pod
104              
105             =head1 NAME
106              
107             Whelk::Formatter - Base class for formatters
108              
109             =head1 SYNOPSIS
110              
111             package Whelk::Formatter::MyFormatter;
112              
113             use Kelp::Base 'Whelk::Formatter';
114              
115             # at the very least, this attribute must be given a default
116             attr response_format => 'json';
117              
118             =head1 DESCRIPTION
119              
120             Whelk::Formatter is a base class for formatters. Formatter's job is to
121             implement logic necessary decode content from requests and encode content for
122             responses. Whelk assumes that while a range of content types can be supported
123             from the request, endpoints will always have just one response format, for
124             example C<JSON>.
125              
126             Whelk implements two basic formatters which can be used out of the box:
127             L<Whelk::Formatter::JSON> (the default) and L<Whelk::Formatter::YAML>. All they
128             do is have different L</response_format> values.
129              
130             The base implementation uses Kelp modules L<Kelp::Module::JSON> and
131             L<Kelp::Module::YAML> to get the encoders for each of those formats. If one of
132             these modules is not loaded, the application will not support that format.
133              
134             =head1 ATTRIBUTES
135              
136             This class defines a couple attributes, which generally are loaded once and
137             then reused as long as the app is running.
138              
139             =head2 response_format
140              
141             A response format in short form, for example C<'json'>.
142              
143             =head2 full_response_format
144              
145             A full response format in content type form, for example C<application/json>.
146             It will be loaded from L</response_format> using L</supported_format> method.
147              
148             =head2 supported_formats
149              
150             A cache of all formats supported by this formatter. It is created by a call to
151             L</load_formats>.
152              
153             It is in form of a hash reference, where keys are short format names like
154             C<json> and values are full format content types like C<application/json>.
155              
156             =head1 METHODS
157              
158             =head2 load_formats
159              
160             $formatter->load_formats($app);
161              
162             Called by the constructor to load formats into L</supported_formats>. Uses the
163             application instance to see if the formats are actually supported by Kelp.
164              
165             =head2 supported_format
166              
167             my $full_format = $formatter->supported_format($short_format);
168              
169             Checks if the format is supported by the formatter according to
170             L</supported_formats> and returns the long form of the format. If it is not
171             supported then an exception is raised.
172              
173             =head2 match_format
174              
175             my $short_format = $formatter->match_format($app);
176              
177             Tries to match the application's current request's content type and returns one
178             of the formats in the short form if request content type is supported. Throws
179             L<Whelk::Exception> with code 400 if request's content type is unsupported.
180              
181             =head2 get_request_body
182              
183             my $decoded = $formatter->get_request_body($app);
184              
185             Tries to decode the request body and returns a perl structure containing
186             decoded data. May return undef if the request data isn't well formed. Uses
187             L</match_format> to decide the format, so may also throw an exception.
188              
189             =head2 format_response
190              
191             my $maybe_encoded = $formatter->format_response($app, $data, $special_encoder = undef);
192              
193             Encodes C<$data> to L</response_format> and sets the proper content type on the
194             response (unless it was set manually). Will only do the actual encoding if
195             C<$special_encoder> was specified (a name of the encoder to use, see
196             L<Kelp/get_encoder>. Otherwise, will return the structure unchanged to let Kelp
197             handle it internally.
198