File Coverage

blib/lib/Dancer2/Plugin/JsonApi/Schema.pm
Criterion Covered Total %
statement 131 131 100.0
branch 47 52 90.3
condition 14 15 93.3
subroutine 19 19 100.0
pod 1 10 10.0
total 212 227 93.3


line stmt bran cond sub pod time code
1 11     11   436331 use 5.32.0;
  11         45  
2              
3             package Dancer2::Plugin::JsonApi::Schema;
4             our $AUTHORITY = 'cpan:YANICK';
5             $Dancer2::Plugin::JsonApi::Schema::VERSION = '0.0.1';
6 11     11   5954 use Moo;
  11         96163  
  11         75  
7              
8 11     11   23498 use experimental qw/ signatures /;
  11         32063  
  11         69  
9 11     11   10486 use List::AllUtils qw/ pairmap pairgrep /;
  11         230487  
  11         1633  
10              
11 11     11   9374 use Set::Object qw/set/;
  11         112073  
  11         38339  
12              
13             has registry => ( is => 'ro' );
14              
15             has type => (
16             required => 1,
17             is => 'ro',
18             );
19              
20             has id => (
21             is => 'ro',
22             default => 'id'
23             );
24              
25             has links => ( is => 'ro' );
26             has top_level_links => ( is => 'ro' );
27             has top_level_meta => ( is => 'ro' );
28             has relationships => ( is => 'ro', default => sub { +{} } );
29              
30             has allowed_attributes => ( is => 'ro' );
31             has before_serialize => ( is => 'ro' );
32              
33 23     23 0 189 sub serialize ( $self, $data, $extra_data = {} ) {
  23         45  
  23         48  
  23         60  
  23         41  
34              
35 23         46 my $serial = {};
36              
37 23         89 $serial->{jsonapi} = { version => '1.0' };
38              
39 23         52 my @included;
40              
41 23 100       80 if ( defined $data ) {
42             $serial->{data} =
43 21         121 $self->serialize_data( $data, $extra_data, \@included );
44             }
45              
46 23 100       132 $serial->{links} = gen_links( $self->top_level_links, $data, $extra_data )
47             if $self->top_level_links;
48              
49 23 100 100     399 if ( $self->registry and $self->registry->app ) {
50 1         14 $serial->{links}{self} = $self->registry->app->request->path;
51             }
52              
53 23 100       84 $serial->{meta} = gen_links( $self->top_level_meta, $data, $extra_data )
54             if $self->top_level_meta;
55              
56 23 100       107 $serial->{included} = [ dedupe_included(@included) ] if @included;
57              
58 23         203 return $serial;
59             }
60              
61             sub dedupe_included {
62 3     3 0 8 my %seen;
63 3         9 return grep { not $seen{ $_->{type} }{ $_->{id} }++ } @_;
  9         48  
64             }
65              
66             has attributes => (
67             is => 'ro',
68             default => sub {
69             my $self = shift;
70             return sub {
71             my ( $data, $extra_data ) = @_;
72             return {} if ref $data ne 'HASH';
73             my @keys = grep { not $self->relationships->{$_} }
74             grep { $_ ne $self->id } keys %$data;
75             return { $data->%{@keys} };
76             }
77             }
78             );
79              
80 38     38 1 70 sub serialize_data ( $self, $data, $extra_data = {}, $included = undef ) {
  38         57  
  38         70  
  38         83  
  38         70  
  38         63  
81              
82 38 100       125 return [ map { $self->serialize_data( $_, $extra_data, $included ) }
  17         101  
83             @$data ]
84             if ref $data eq 'ARRAY';
85              
86 30 100       119 if ( $self->before_serialize ) {
87 1         8 $data = $self->before_serialize->( $data, $extra_data );
88             }
89              
90             # it's a scalar? it's the id
91 30 100       145 return { id => $data, type => $self->type } unless ref $data;
92              
93 24         123 my $s = {
94             type => $self->type,
95             id => $self->gen_id( $data, $extra_data )
96             };
97              
98 24 100       104 if ( $self->links ) {
99 4         23 $s->{links} = gen_links( $self->links, $data, $extra_data );
100             }
101              
102 24         784 $s->{attributes} = gen_links( $self->attributes, $data, $extra_data );
103              
104 24         110 my %relationships = $self->relationships->%*;
105              
106 24         75 for my $key ( keys %relationships ) {
107 7         16 my $attr = $data->{$key};
108              
109 7         31 my @inc;
110              
111             my $t = $self->registry->serialize( $relationships{$key}{type},
112 7         80 $attr, \@inc );
113              
114 7 50       27 if ( my $data = obj_ref( $t->{data}, \@inc ) ) {
115 7         26 $s->{relationships}{$key}{data} = $data;
116             }
117              
118 7 100       25 if ( my $links = $relationships{$key}{links} ) {
119             $s->{relationships}{$key}{links} =
120 1         3 gen_links( $links, $data, $extra_data );
121             }
122              
123 7 50       59 push @$included, @inc if $included;
124             }
125              
126 24 100       108 delete $s->{attributes} unless $s->{attributes}->%*;
127              
128 24 100       93 if ( $self->allowed_attributes ) {
129             delete $s->{attributes}{$_}
130 4         23 for ( set( keys $s->{attributes}->%* ) -
131             set( $self->allowed_attributes->@* ) )->@*;
132             }
133              
134 24         937 return $s;
135              
136             }
137              
138 21     21 0 31 sub obj_ref ( $data, $included ) {
  21         37  
  21         28  
  21         77  
139 21 100       58 return [ map { obj_ref( $_, $included ) } @$data ]
  14         64  
140             if ref $data eq 'ARRAY';
141              
142 15 100       57 return $data if keys %$data == 2;
143              
144 9 50       24 return unless keys %$data;
145              
146 9         19 push @$included, $data;
147              
148 9         43 return +{ $data->%{qw/ id type/} };
149             }
150              
151 24     24 0 44 sub gen_id ( $self, $data, $xtra ) {
  24         38  
  24         46  
  24         35  
  24         41  
152 24         86 my $id = $self->id;
153              
154 24 100       142 return ref $id ? $id->( $data, $xtra ) : $data->{$id};
155             }
156              
157 32     32 0 66 sub gen_links ( $links, $data, $extra_data = {} ) {
  32         62  
  32         116  
  32         57  
  32         48  
158              
159 32 100       157 return $links->( $data, $extra_data ) if ref $links eq 'CODE';
160              
161 5     6   73 return { pairmap { $a => gen_item( $b, $data, $extra_data ) } %$links };
  6         29  
162             }
163              
164 6     6 0 12 sub gen_item ( $item, $data, $extra_data ) {
  6         13  
  6         10  
  6         11  
  6         10  
165 6 100       35 return $item unless ref $item;
166              
167 3         17 return $item->( $data, $extra_data );
168             }
169              
170 2     2 0 5 sub deserialize ( $self, $serialized, $included = [] ) {
  2         36  
  2         6  
  2         5  
  2         4  
171              
172 2         8 my $data = $serialized->{data};
173 2   100     21 my @included = ( ( $serialized->{included} // [] )->@*, @$included );
174              
175 2         12 return $self->deserialize_data( $data, \@included );
176             }
177              
178 12     12 0 20 sub expand_object ( $obj, $included ) {
  12         18  
  12         20  
  12         38  
179              
180 12 100       30 if ( ref $obj eq 'ARRAY' ) {
181 3         7 return [ map { expand_object( $_, $included ) } @$obj ];
  8         19  
182             }
183              
184 9         18 for (@$included) {
185 30 100 100     105 return $_ if $_->{type} eq $obj->{type} and $_->{id} eq $obj->{id};
186             }
187              
188 5         21 return $obj;
189             }
190              
191 15     15 0 26 sub deserialize_data ( $self, $data, $included ) {
  15         63  
  15         51  
  15         25  
  15         22  
192              
193 15 100       47 if ( ref $data eq 'ARRAY' ) {
194 4         8 return [ map { $self->deserialize_data( $_, $included ) } @$data ];
  9         29  
195             }
196              
197             my %obj = (
198             ( $data->{attributes} // {} )->%*,
199             pairmap {
200             $a =>
201             $self->registry->type( $self->relationships->{$a}{type} )
202 4     4   31 ->deserialize_data( $b, $included )
203 4     4   12 } pairmap { $a => expand_object( $b, $included ) }
204 11   100 4   182 pairmap { $a => $b->{data} } ( $data->{relationships} // {} )->%*
  4   100     14  
205             );
206              
207 11         82 my $id_key = $self->id;
208 11 50       29 if ( !ref $id_key ) {
209 11         27 $obj{$id_key} = $data->{id};
210             }
211              
212 11 100       32 if ( $data->{type} eq 'photo' ) {
213              
214             # die keys %$data;
215             }
216              
217 11 50 66     55 if ( 1 == keys %obj and exists $obj{id} ) {
218 6         36 return $data->{id};
219             }
220              
221 5         18 return \%obj;
222             }
223              
224             1;
225              
226             __END__