File Coverage

blib/lib/JSON/API/v1.pm
Criterion Covered Total %
statement 36 38 94.7
branch 13 18 72.2
condition n/a
subroutine 9 9 100.0
pod 0 3 0.0
total 58 68 85.2


line stmt bran cond sub pod time code
1 2     2   1777 use utf8;
  2         19  
  2         15  
2              
3             package JSON::API::v1;
4             our $VERSION = '0.001';
5 2     2   667 use Moose;
  2         471847  
  2         14  
6 2     2   15359 use namespace::autoclean;
  2         4  
  2         20  
7 2     2   191 use Carp qw(croak);
  2         4  
  2         166  
8 2     2   14 use List::Util qw(uniq);
  2         4  
  2         2239  
9              
10             our @CARP_NOT = qw(Class::MOP::Method::Wrapped);
11              
12             # ABSTRACT: A JSON API object according to jsonapi.org v1 specification
13              
14             has data => (
15             is => 'ro',
16             isa => 'ArrayRef[JSON::API::v1::Resource]',
17             traits => ['Array'],
18             lazy => 1,
19             default => sub { [] },
20             reader => '_data',
21             handles => { add_data => 'push', has_data => 'count', data => 'uniq' },
22             );
23              
24             has is_set => (
25             is => 'ro',
26             isa => 'Bool',
27             lazy => 1,
28             default => 0,
29             predicate => 'has_is_set',
30             writer => '_is_set',
31             );
32              
33             has errors => (
34             is => 'ro',
35             isa => 'ArrayRef[JSON::API::v1::Error]',
36             traits => ['Array'],
37             lazy => 1,
38             default => sub { [] },
39             handles => { add_error => 'push', has_errors => 'count' },
40             );
41              
42             has jsonapi => (
43             is => 'ro',
44             isa => 'Defined',
45             predicate => 'has_jsonapi',
46             );
47              
48             has included => (
49             is => 'ro',
50             isa => 'ArrayRef[JSON::API::v1::Resource]',
51             traits => ['Array'],
52             lazy => 1,
53             default => sub { [] },
54             reader => '_included',
55             handles => {
56             add_included => 'push',
57             has_included => 'count',
58             included => 'uniq'
59             },
60             );
61              
62             around BUILDARGS => sub {
63             my ($orig, $self, %args) = @_;
64              
65             foreach (qw(data included errors)) {
66             if (exists $args{$_} && defined $args{$_} && blessed($args{$_})) {
67             $args{$_} = [ $args{$_} ];
68             }
69             }
70             if (exists $args{data} && @{ $args{data} } > 1) {
71             if (exists $args{is_set} && !$args{is_set}) {
72             croak(
73             "You are entering a set of data and telling me you are not a"
74             . " set, this is incorrect!");
75             }
76             $args{is_set} = 1 unless exists $args{is_set};
77             }
78             return $self->$orig(%args);
79              
80             };
81              
82             around add_data => sub {
83             my ($orig, $self, @data) = @_;
84              
85             if ($self->has_is_set) {
86             if (!$self->is_set && $self->has_data) {
87             croak("Unable to add data, this isn't a set!");
88             }
89             return $self->$orig(@data);
90             }
91             $self->_is_set(1);
92             return $self->$orig(@data);
93             };
94              
95             around included => sub {
96             my ($orig, $self) = @_;
97             return $self->_assert_uniq('included', $orig);
98             };
99              
100             sub _assert_uniq {
101 7     7   19 my ($self, $type, $orig) = @_;
102              
103 7         238 my @rv = $self->$orig;
104 7         31 my @check = uniq map { { id => $_->id, type => $_->type } } @rv;
  10         276  
105 7 50       22 if (@check == @rv) {
106 7         40 return \@rv;
107             }
108 0         0 croak("Duplicate ID and type are found for $type");
109             }
110              
111             around data => sub {
112             my ($orig, $self) = @_;
113             return $self->_assert_uniq('data', $orig);
114             };
115              
116             sub as_data_object {
117 7     7 0 15 my $self = shift;
118              
119 7 50       232 croak("You called me as a data object, but I'm in an error state!")
120             if $self->has_errors;
121              
122 7         18 my %rv;
123              
124 7 100       221 if ($self->has_data) {
    50          
125 6 100       145 $rv{data} = $self->is_set ? $self->data : $self->data->[0];
126             }
127             elsif ($self->is_set) {
128 0         0 $rv{data} = [],
129             }
130             else {
131 1         4 $rv{data} = undef;
132             }
133              
134 7 100       244 $rv{included} = $self->included if $self->has_included;
135 7 50       216 $rv{jsonapi} = $self->jsonapi if $self->has_jsonapi;
136              
137 7         66 return \%rv;
138             }
139              
140             sub as_error_object {
141 1     1 0 3 my $self = shift;
142              
143 1 50       32 croak("You called me as an error object, but I'm not in an error state!")
144             unless $self->has_errors;
145              
146 1         27 return { errors => $self->errors };
147             }
148              
149             sub TO_JSON {
150 8     8 0 182 my $self = shift;
151              
152 8 100       316 return $self->as_error_object if $self->has_errors;
153 7         25 return $self->as_data_object;
154              
155             }
156              
157             with qw(
158             JSON::API::v1::Roles::TO_JSON
159             JSON::API::v1::Roles::MetaObject
160             JSON::API::v1::Roles::Links
161             );
162              
163             __PACKAGE__->meta->make_immutable;
164              
165             __END__
166              
167             =pod
168              
169             =encoding UTF-8
170              
171             =head1 NAME
172              
173             JSON::API::v1 - A JSON API object according to jsonapi.org v1 specification
174              
175             =head1 VERSION
176              
177             version 0.001
178              
179             =head1 SYNOPSIS
180              
181             use JSON::API::v1;
182              
183             my $object = JSON::API::v1->new(
184             data => JSON::API::v1::Resource->new(
185             ...
186             );
187             );
188              
189             $object->add_error(JSON::API::v1::Error->new(...));
190              
191             $object->add_relationship(JSON::API::v1::Error->new(...));
192              
193             =head1 DESCRIPTION
194              
195             This module attempts to make a Moose object behave like a JSON API object as
196             defined by L<jsonapi.org>. This object adheres to the v1 specification
197              
198             =head1 ATTRIBUTES
199              
200             =head2 data
201              
202             This data object is there a L<JSON::API::v1::Resource> lives.
203              
204             =head2 errors
205              
206             This becomes an array ref of L<JSON::API::v1::Error> once you start
207             adding errors to this object object via C<add_error>.
208              
209             =head2 included
210              
211             This becomes an array ref of L<JSON::API::v1::Resource> once you start
212             adding additional resources to this object object via C<add_included>.
213              
214             =head2 is_set
215              
216             This is to tell the object it is a set and you can add data to it via
217             C<add_data>. It will in turn JSON-y-fi the data to an array of the data you've
218             added. If you don't set this via the constructer, please read the documentation
219             of L<JSON::API::v1/add_data>
220              
221             =head1 METHODS
222              
223             =head2 add_data
224              
225             You can add individual L<JSON::API::v1::Resource> objects to the
226             toplevel object. If you have not set is_set the first call to this function
227             will assume you're adding data and thus want to be a set.
228              
229             =head2 add_error
230              
231             You can add individual L<JSON::API::v1::Error> objects to the
232             toplevel object.
233              
234             =head2 add_included
235              
236             You can add individual L<JSON::API::v1::Resource> objects to the
237             toplevel object.
238              
239             =head1 AUTHOR
240              
241             Wesley Schwengle <waterkip@cpan.org>
242              
243             =head1 COPYRIGHT AND LICENSE
244              
245             This software is Copyright (c) 2020 by Wesley Schwengle.
246              
247             This is free software, licensed under:
248              
249             The (three-clause) BSD License
250              
251             =cut