File Coverage

blib/lib/Dancer2/Serializer/Mutable.pm
Criterion Covered Total %
statement 24 24 100.0
branch 5 6 83.3
condition n/a
subroutine 6 6 100.0
pod n/a
total 35 36 97.2


line stmt bran cond sub pod time code
1             # ABSTRACT: Serialize and deserialize content based on HTTP header
2             $Dancer2::Serializer::Mutable::VERSION = '0.400000';
3             use Moo;
4 2     2   120917 use Carp 'croak';
  2         13644  
  2         10  
5 2     2   2609 use Encode;
  2         5  
  2         87  
6 2     2   908 use Module::Runtime 'require_module';
  2         27888  
  2         171  
7 2     2   873 with 'Dancer2::Core::Role::Serializer';
  2         3141  
  2         13  
8              
9             use constant DEFAULT_CONTENT_TYPE => 'application/json';
10 2     2   124  
  2         4  
  2         1093  
11             has '+content_type' => ( default => DEFAULT_CONTENT_TYPE() );
12              
13             my $serializer = {
14             'YAML' => {
15             to => sub { Dancer2::Core::DSL::to_yaml(@_) },
16             from => sub { Dancer2::Core::DSL::from_yaml(@_) },
17             },
18             'Dumper' => {
19             to => sub { Dancer2::Core::DSL::to_dumper(@_) },
20             from => sub { Dancer2::Core::DSL::from_dumper(@_) },
21             },
22             'JSON' => {
23             to => sub { Dancer2::Core::DSL::to_json(@_) },
24             from => sub { Dancer2::Core::DSL::from_json(@_) },
25             },
26             };
27              
28             has mapping => (
29             is => 'ro',
30             lazy => 1,
31             default => sub {
32             my $self = shift;
33              
34             if ( my $mapping = $self->config->{mapping} ) {
35              
36             # initialize non-default serializers
37             for my $s ( values %$mapping ) {
38             # TODO allow for arguments via the config
39             next if $serializer->{$s};
40             my $serializer_class = "Dancer2::Serializer::$s";
41             require_module($serializer_class);
42             my $serializer_object = $serializer_class->new;
43             $serializer->{$s} = {
44             from => sub { shift; $serializer_object->deserialize(@_) },
45             to => sub { shift; $serializer_object->serialize(@_) },
46             };
47             }
48              
49             return $mapping;
50             }
51              
52              
53             return {
54             'text/x-yaml' => 'YAML',
55             'text/html' => 'YAML',
56             'text/x-data-dumper' => 'Dumper',
57             'text/x-json' => 'JSON',
58             'application/json' => 'JSON',
59             }
60             },
61             );
62              
63             my ( $self, $entity ) = @_;
64              
65             # Look for valid format in the headers
66             my $format = $self->_get_content_type('accept');
67              
68             # Match format with a serializer and return
69             $format and return $serializer->{$format}{'to'}->(
70             $self, $entity
71             );
72              
73             # If none is found then just return the entity without change
74             return $entity;
75             }
76              
77             my ( $self, $content ) = @_;
78              
79             my $format = $self->_get_content_type('content_type');
80             $format and return $serializer->{$format}{'from'}->($self, $content);
81              
82             return $content;
83             }
84              
85             my ($self, $header) = @_;
86              
87             if ( $self->has_request ) {
88             # Search for the first HTTP header variable which specifies
89 51     51   86 # supported content. Both content_type and accept are checked
90             # for backwards compatibility.
91 51 50       119 foreach my $method ( $header, qw<content_type accept> ) {
92             if ( my $value = $self->request->header($method) ) {
93             if ( my $serializer = $self->mapping->{$value} ) {
94             $self->set_content_type($value);
95 51         80 return $serializer;
96 91 100       3260 }
97 50 100       5314 }
98 41         795 }
99 41         890 }
100              
101             # If none if found, return the default, 'JSON'.
102             $self->set_content_type( DEFAULT_CONTENT_TYPE() );
103             return 'JSON';
104             }
105              
106 10         370 1;
107 10         231  
108              
109             =pod
110              
111             =encoding UTF-8
112              
113             =head1 NAME
114              
115             Dancer2::Serializer::Mutable - Serialize and deserialize content based on HTTP header
116              
117             =head1 VERSION
118              
119             version 0.400000
120              
121             =head1 SYNOPSIS
122              
123             # in config.yml
124             serializer: Mutable
125              
126             engines:
127             serializer:
128             Mutable:
129             mapping:
130             'text/x-yaml' : YAML
131             'text/html' : YAML
132             'text/x-data-dumper' : Dumper
133             'text/x-json' : JSON
134             'application/json' : JSON
135              
136             # in the app
137             put '/something' => sub {
138             # deserialized from request
139             my $name = param( 'name' );
140              
141             ...
142              
143             # will be serialized to the most
144             # fitting format
145             return { message => "user $name added" };
146             };
147              
148             =head1 DESCRIPTION
149              
150             This serializer will try find the best (de)serializer for a given request.
151             For this, it will pick the first valid content type found from the following list
152             and use its related serializer.
153              
154             =over
155              
156             =item
157              
158             The B<content_type> from the request headers
159              
160             =item
161              
162             the B<accept> from the request headers
163              
164             =item
165              
166             The default is B<application/json>
167              
168             =back
169              
170             The content-type/serializer mapping that C<Dancer2::Serializer::Mutable>
171             uses is
172              
173             serializer | content types
174             ----------------------------------------------------------
175             Dancer2::Serializer::YAML | text/x-yaml, text/html
176             Dancer2::Serializer::Dumper | text/x-data-dumper
177             Dancer2::Serializer::JSON | text/x-json, application/json
178              
179             A different mapping can be provided via the config file. For example,
180             the default mapping would be configured as
181              
182             engines:
183             serializer:
184             Mutable:
185             mapping:
186             'text/x-yaml' : YAML
187             'text/html' : YAML
188             'text/x-data-dumper' : Dumper
189             'text/x-json' : JSON
190             'application/json' : JSON
191              
192             The keys of the mapping are the content-types to serialize,
193             and the values the serializers to use. Serialization for C<YAML>, C<Dumper>
194             and C<JSON> are done using internal Dancer mechanisms. Any other serializer will
195             be taken to be as Dancer2 serialization class (minus the C<Dancer2::Serializer::> prefix)
196             and an instance of it will be used
197             to serialize/deserialize data. For example, adding L<Dancer2::Serializer::XML>
198             to the mapping would be:
199              
200             engines:
201             serializer:
202             Mutable:
203             mapping:
204             'text/x-yaml' : YAML
205             'text/html' : YAML
206             'text/x-data-dumper' : Dumper
207             'text/x-json' : JSON
208             'text/xml' : XML
209              
210             =head2 INTERNAL METHODS
211              
212             The following methods are used internally by C<Dancer2> and are not made
213             accessible via the DSL.
214              
215             =head2 serialize
216              
217             Serialize a data structure. The format it is serialized to is determined
218             automatically as described above. It can be one of YAML, Dumper, JSON, defaulting
219             to JSON if there's no clear preference from the request.
220              
221             =head2 deserialize
222              
223             Deserialize the provided serialized data to a data structure. The type of
224             serialization format depends on the request's content-type. For now, it can
225             be one of YAML, Dumper, JSON.
226              
227             =head2 content_type
228              
229             Returns the content-type that was used during the last C<serialize> /
230             C<deserialize> call. B<WARNING> : you must call C<serialize> / C<deserialize>
231             before calling C<content_type>. Otherwise the return value will be C<undef>.
232              
233             =head1 AUTHOR
234              
235             Dancer Core Developers
236              
237             =head1 COPYRIGHT AND LICENSE
238              
239             This software is copyright (c) 2022 by Alexis Sukrieh.
240              
241             This is free software; you can redistribute it and/or modify it under
242             the same terms as the Perl 5 programming language system itself.
243              
244             =cut