line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package WebAPI::DBIC; |
2
|
|
|
|
|
|
|
$WebAPI::DBIC::VERSION = '0.004001'; |
3
|
2
|
|
|
2
|
|
3040426
|
use strict; # keep our kwalitee up! |
|
2
|
|
|
|
|
48
|
|
|
2
|
|
|
|
|
144
|
|
4
|
2
|
|
|
2
|
|
17
|
use warnings; |
|
2
|
|
|
|
|
1
|
|
|
2
|
|
|
|
|
155
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
1; |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
__END__ |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
=pod |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
=encoding UTF-8 |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head1 NAME |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
WebAPI::DBIC |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 VERSION |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
version 0.004001 |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=head1 DESCRIPTION |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
WebAPI::DBIC (or "WAPID" for short) provides the parts you need to build a |
26
|
|
|
|
|
|
|
feature-rich RESTful JSON web service API backed by DBIx::Class schemas. |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
WebAPI::DBIC features include: |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
* Built as fine-grained roles for maximum reusability and extensibility. |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
* Integrates with other L<Plack> based applications. |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
* The resource roles can be added to your existing application. |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
* Built on the strong foundations of L<Plack> and L<Web::Machine>, plus |
37
|
|
|
|
|
|
|
L<Path::Router> as the router. (Other routers could be supported.) |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
* Rich support for multiple hypermedia types, including ActiveModel / Ember-Data |
40
|
|
|
|
|
|
|
(C<application/json>), JSON API (C<application/vnd.api+json>) |
41
|
|
|
|
|
|
|
and HAL (C<application/hal+json>). |
42
|
|
|
|
|
|
|
The Collection+JSON and JSON-LD hypermedia types could be added in future. |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
* Automatic detection and exposure of result set relationships. |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
* Supports safe robust multi-related-record CRUD transactions. |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
* An example .psgi file that gives you an instant web service for any |
49
|
|
|
|
|
|
|
DBIx::Class schema. |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
* A generic pure-javascript HAL API browser application is integrated with |
52
|
|
|
|
|
|
|
WebAPI::DBIC so you can be browsing your new API in seconds. |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
=head2 Media Types Supported |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
The HTTP C<Content-Type> and C<Accept> headers are used to specify |
57
|
|
|
|
|
|
|
the 'media type' of a request, and the desired response. In the case of JSON |
58
|
|
|
|
|
|
|
types, the media type defines not only that the content is a JSON data structure, |
59
|
|
|
|
|
|
|
but the semantics (meaning) of the the scructure. |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
A single application can support requests and responses in multiple media |
62
|
|
|
|
|
|
|
types, using the headers to negotiate the right behaviour for ech request. |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=head3 ActiveModel |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
Designed to match the behaviour of the active_model_serializers Ruby gem |
67
|
|
|
|
|
|
|
and thus be directly usable as a backend for frameworks compatible with it, |
68
|
|
|
|
|
|
|
including Ember. This uses the C<application/json> media type. (This is a very |
69
|
|
|
|
|
|
|
common 'default' media type for web data services.) |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
See L<WebAPI::DBIC::Resource::ActiveModel> for more information. |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=head3 HAL |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
The Hypertext Application Language hypermedia type (or HAL for short) |
76
|
|
|
|
|
|
|
is a simple JSON format that gives a consistent and easy way to hyperlink |
77
|
|
|
|
|
|
|
between resources in your API. It uses the C<application/hal+json> media type. |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
A pure-JavaScript "HAL Browser" application is integrated with the WebAPI::DBIC |
80
|
|
|
|
|
|
|
distribution via the L<Alien::Web::HalBrowser> module. It's a great way to |
81
|
|
|
|
|
|
|
explore your API. |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
See L<http://stateless.co/hal_specification.html> for more details of the specification. |
84
|
|
|
|
|
|
|
See L<WebAPI::DBIC::Resource::HAL> for more details of WebAPI::DBIC support. |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=head3 JSON API |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
The JSON API media type is designed to minimize both the number of requests and |
89
|
|
|
|
|
|
|
the amount of data transmitted between clients and servers. This efficiency is |
90
|
|
|
|
|
|
|
achieved without compromising readability, flexibility, and discoverability. |
91
|
|
|
|
|
|
|
It's an (as yet immature) evolution of the ActiveModel media type. |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
See L<WebAPI::DBIC::Resource::JSONAPI> for more details. |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
=head3 WAPID |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
The WebAPI::DBIC core and tests use the C<application/vnd.wapid+json> media |
98
|
|
|
|
|
|
|
type. It's subject to change without notice. |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
=head2 Web::Machine |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
The L<Web::Machine> module provides a RESTful web framework modeled as a formal |
103
|
|
|
|
|
|
|
state machine. This is a rigorous and powerful approach, originally developed |
104
|
|
|
|
|
|
|
in Erlang and since ported to many other languages. |
105
|
|
|
|
|
|
|
See L<https://raw.githubusercontent.com/basho/webmachine/develop/docs/http-headers-status-v3.png> |
106
|
|
|
|
|
|
|
for an image of the state machine. |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
By building on Web::Machine, WebAPI::DBIC removes the need to implement all the |
109
|
|
|
|
|
|
|
logic needed for accurate and full-featured HTTP protocol behaviour. |
110
|
|
|
|
|
|
|
You just provide small pieces of logic at the decision points you care about |
111
|
|
|
|
|
|
|
and Web::Machine looks after the rest. |
112
|
|
|
|
|
|
|
See L<https://github.com/basho/webmachine/wiki> for more information. |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
Web::Machine provides the logic to handle a HTTP request for a I<single resource>. |
115
|
|
|
|
|
|
|
With WebAPI::DBIC those resources typically represent a DBIx::Class result set, |
116
|
|
|
|
|
|
|
a row, or a method invocation on a row or result set. They are implemented as a subclass of |
117
|
|
|
|
|
|
|
L<Web::Machine::Resource> that consumes a some set of WebAPI::DBIC roles which add |
118
|
|
|
|
|
|
|
the desired functionality to the resource. |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
=head2 Path::Router |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
The L<Path::Router> module is used to organize multiple resources into a URL |
123
|
|
|
|
|
|
|
namespace. It's used to route incoming requests to the appropriate Web::Machine |
124
|
|
|
|
|
|
|
instance. It's also used in reverse to construct links to other resources that |
125
|
|
|
|
|
|
|
are included in the outgoing responses. |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
Path::Router supports full reversability: the value produced by a path match |
128
|
|
|
|
|
|
|
can be passed back in and you will get the same path you originally put in. |
129
|
|
|
|
|
|
|
This removes ambiguity and reduces mis-routings. This is important for |
130
|
|
|
|
|
|
|
WebAPI::DBIC because, for each resource returned, it automatically add HAL |
131
|
|
|
|
|
|
|
C<_links> containing the URLs of the related resources, as defined by the |
132
|
|
|
|
|
|
|
DBIx::Class schema. This is what makes the API discoverable and browseable. |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=head1 NAME |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
WebAPI::DBIC - A composable RESTful JSON API to DBIx::Class schemas using roles and Web::Machine |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
=head1 STATUS |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
The WebAPI::DBIC code has been in production use since early 2013, however it's |
141
|
|
|
|
|
|
|
only recently been open sourced (July 2014) so it's still lacking in documentation. |
142
|
|
|
|
|
|
|
It's also undergoing a period of refactoring, enhancement and evolution now |
143
|
|
|
|
|
|
|
there are more developers contributing and the code is being applied to more domains. |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
Interested? Please get involved! See L</HOW TO GET HELP> below. |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
=head1 QUICK START |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
To demonstrate the rich functionality that the combination of DBIx::Class and |
150
|
|
|
|
|
|
|
HAL provides, the WebAPI::DBIC framework includes a ready-to-use L<Plack> .psgi |
151
|
|
|
|
|
|
|
file that provides an instant web data service for any DBIx::Class schema. |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
$ git clone https://github.com/timbunce/WebAPI-DBIC.git |
154
|
|
|
|
|
|
|
$ cd WebAPI-DBIC |
155
|
|
|
|
|
|
|
$ cpanm Module::CPANfile |
156
|
|
|
|
|
|
|
$ cpanm --installdeps . #Â this may take a while |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
$ export WEBAPI_DBIC_SCHEMA=DummyLoadedSchema |
159
|
|
|
|
|
|
|
$ plackup -Ilib -It/lib webapi-dbic-any.psgi |
160
|
|
|
|
|
|
|
... open a web browser on port 5000 to browse the API |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
Then try it out with your own schema: |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
$ export WEBAPI_DBIC_SCHEMA=Foo::Bar # your own schema |
165
|
|
|
|
|
|
|
$ export WEBAPI_DBIC_HTTP_AUTH_TYPE=none # recommended |
166
|
|
|
|
|
|
|
$ export DBI_DSN=dbi:Driver:... # your own database |
167
|
|
|
|
|
|
|
$ export DBI_USER=... # for initial connection, if needed |
168
|
|
|
|
|
|
|
$ export DBI_PASS=... # for initial connection, if needed |
169
|
|
|
|
|
|
|
$ plackup -Ilib webapi-dbic-any.psgi |
170
|
|
|
|
|
|
|
... open a web browser on port 5000 to browse your new API |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
The API is read-only by default. To enable PUT, POST, DELETE etc, set the |
173
|
|
|
|
|
|
|
C<WEBAPI_DBIC_WRITABLE> environment variable. |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
=head1 MODULES |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
=head2 Core Roles |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
L<WebAPI::DBIC::Resource::Role::DBIC> is responsible for interfacing with |
180
|
|
|
|
|
|
|
L<DBIx::Class>, 'rendering' individual records as resource data structures. |
181
|
|
|
|
|
|
|
It also interfaces with Path::Router to handle relationship linking. |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
L<WebAPI::DBIC::Resource::Role::Set> is responsible for accepting GET and HEAD |
184
|
|
|
|
|
|
|
requests for set resources (collections) and returning the results as JSON. |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
L<WebAPI::DBIC::Resource::Role::SetWritable> is responsible for accepting POST |
187
|
|
|
|
|
|
|
request for set resources. It handles the recursive creation of related records. |
188
|
|
|
|
|
|
|
Related records can be nested to any depth and are created from the bottom up |
189
|
|
|
|
|
|
|
within a transaction. |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
L<WebAPI::DBIC::Resource::Role::Item> is responsible for GET and HEAD requests |
192
|
|
|
|
|
|
|
for single item resources and returning the results as JSON. |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
L<WebAPI::DBIC::Resource::Role::ItemWritable> is responsible for accepting PUT |
195
|
|
|
|
|
|
|
and DELETE requests for single item resources. It handles the recursive update of |
196
|
|
|
|
|
|
|
related records. Related records can be nested to any depth and are updated |
197
|
|
|
|
|
|
|
from the bottom up within a transaction. Handles both 'PUT is replace' and |
198
|
|
|
|
|
|
|
'PUT is update' logic. |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
L<WebAPI::DBIC::Resource::Role::ItemInvoke> is responsible for accepting POST |
201
|
|
|
|
|
|
|
requests for single item resources representing the invocation of a specific |
202
|
|
|
|
|
|
|
method on an item (e.g. POST /widget/42/invoke/my_method_name?args=...). |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
L<WebAPI::DBIC::Resource::Role::SetInvoke> is responsible for accepting POST |
205
|
|
|
|
|
|
|
requests for set resources representing the invocation of a specific |
206
|
|
|
|
|
|
|
method on a result set (e.g. POST /widget/invoke/my_method_name?args=...). |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
L<WebAPI::DBIC::Resource::Role::DBICAuth> is responsible for checking |
209
|
|
|
|
|
|
|
authorization to access a resource. It currently supports Basic Authentication, |
210
|
|
|
|
|
|
|
using the DBI DSN as the realm name and the return username and password as the |
211
|
|
|
|
|
|
|
username and password for the database connection. |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
L<WebAPI::DBIC::Resource::Role::DBICParams> is responsible for handling request |
214
|
|
|
|
|
|
|
parameters related to DBIx::Class such as C<page>, C<rows>, C<sort>, C<me>, |
215
|
|
|
|
|
|
|
C<prefetch>, C<fields> etc. |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
=head2 ActiveModel Roles |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
For support of the C<application/json> media type, see L<WebAPI::DBIC::Resource::ActiveModel>. |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
=head2 JSON+HAL Roles |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
See L<WebAPI::DBIC::Resource::HAL> |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
=head2 JSON API Roles |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
See L<WebAPI::DBIC::Resource::JSONAPI> |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
=head2 Utility Roles |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
L<WebAPI::DBIC::Role::JsonEncoder> provides encode_json() and decode_json() methods. |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
L<WebAPI::DBIC::Role::JsonParams> provides a param() method that returns query |
234
|
|
|
|
|
|
|
parameters, except that any parameters with names that have a C<~json> suffix |
235
|
|
|
|
|
|
|
have their values JSON decoded, so they can be arbitrary data structures. |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
=head2 Resource Classes |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
To make building typical applications easier, WebAPI::DBIC provides several |
240
|
|
|
|
|
|
|
pre-defined resource classes: |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
L<WebAPI::DBIC::Resource::GenericCore> is a base class that consumes all the |
243
|
|
|
|
|
|
|
general-purpose resource roles. |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
L<WebAPI::DBIC::Resource::GenericSet> subclasses GenericCore and consumes extra |
246
|
|
|
|
|
|
|
roles for resources represented by a DBIx::Class result set. |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
L<WebAPI::DBIC::Resource::GenericItem> subclasses GenericCore and consumes |
249
|
|
|
|
|
|
|
extra roles for resources represented by an individual DBIx::Class row. |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
L<WebAPI::DBIC::Resource::GenericSetInvoke> subclasses GenericCore and |
252
|
|
|
|
|
|
|
consumes extra roles for resources that represent a specific method call on a |
253
|
|
|
|
|
|
|
set resource. |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
L<WebAPI::DBIC::Resource::GenericItemInvoke> subclasses GenericCore and |
256
|
|
|
|
|
|
|
consumes extra roles for resources that represent a specific method call on an |
257
|
|
|
|
|
|
|
item resource. |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
These classes are I<very> simple because all the work is done by the various |
260
|
|
|
|
|
|
|
roles they consume. For example, here's the entire code for |
261
|
|
|
|
|
|
|
L<WebAPI::DBIC::Resource::GenericCore>: |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
package WebAPI::DBIC::Resource::GenericCore; |
264
|
|
|
|
|
|
|
use Moo; |
265
|
|
|
|
|
|
|
extends 'WebAPI::DBIC::Resource::Base'; |
266
|
|
|
|
|
|
|
with 'WebAPI::DBIC::Role::JsonEncoder', |
267
|
|
|
|
|
|
|
'WebAPI::DBIC::Role::JsonParams', |
268
|
|
|
|
|
|
|
'WebAPI::DBIC::Resource::Role::Router', |
269
|
|
|
|
|
|
|
'WebAPI::DBIC::Resource::Role::Identity', |
270
|
|
|
|
|
|
|
'WebAPI::DBIC::Resource::Role::Relationship', |
271
|
|
|
|
|
|
|
'WebAPI::DBIC::Resource::Role::DBIC', |
272
|
|
|
|
|
|
|
'WebAPI::DBIC::Resource::Role::DBICException', |
273
|
|
|
|
|
|
|
'WebAPI::DBIC::Resource::Role::DBICAuth', |
274
|
|
|
|
|
|
|
'WebAPI::DBIC::Resource::Role::DBICParams', |
275
|
|
|
|
|
|
|
; |
276
|
|
|
|
|
|
|
1; |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
and L<WebAPI::DBIC::Resource::GenericItem>: |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
package WebAPI::DBIC::Resource::GenericSet; |
281
|
|
|
|
|
|
|
use Moo; |
282
|
|
|
|
|
|
|
extends 'WebAPI::DBIC::Resource::GenericCore'; |
283
|
|
|
|
|
|
|
with 'WebAPI::DBIC::Resource::Role::Set', |
284
|
|
|
|
|
|
|
'WebAPI::DBIC::Resource::Role::SetWritable', |
285
|
|
|
|
|
|
|
; |
286
|
|
|
|
|
|
|
1; |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
=head2 Other Classes |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
A few other classes are provided: |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
L<WebAPI::DBIC::Util.pm> provides a few general utilities. |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
L<WebAPI::DBIC::WebApp> - this is the main app class and is most likely to |
295
|
|
|
|
|
|
|
change in the near future so isn't documented much yet. |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
=head1 TRANSPARENCY |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
WebAPI::DBIC aims to be a fairly 'transparent' layer between your |
300
|
|
|
|
|
|
|
L<DBIx::Class> schema and the JSON that's generated and received. |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
So it's the responibility of your schema to return data in the format you want |
303
|
|
|
|
|
|
|
in your generated URLs and JSON, and to accept data in the format that arrives |
304
|
|
|
|
|
|
|
in requests from clients. |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
For an example of how to handle dates using L<DateTime> nicely, see: |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
https://blog.afoolishmanifesto.com/posts/solution-on-how-to-serialize-dates-nicely/ |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
=head1 COMPARISONS |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
This section provides links to similar modules with a few notes about how they |
313
|
|
|
|
|
|
|
differ from WebAPI::DBIC. |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
=head2 ... others? ... |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
=head2 App::AutoCRUD |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
L<App::AutoCRUD> provides an automatically generated I<HTML> interface to a |
320
|
|
|
|
|
|
|
database, including search forms. It can export data in various formats |
321
|
|
|
|
|
|
|
including JSON but isn't designed as a JSON API, so it's not directly |
322
|
|
|
|
|
|
|
comparable to WebAPI::DBIC. See also L<RapidApp>. |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
App::AutoCRUD doesn't use DBIx::Class, it uses DBIx::DataModel (a UML-based ORM |
325
|
|
|
|
|
|
|
framework), but creates the model on the fly. That doesn't let you build |
326
|
|
|
|
|
|
|
business logic into the schema model the way you can with DBIx::Class. |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
=head2 RapidApp |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
To quote the documentation: L<RapidApp> is an extension to L<Catalyst> - the |
331
|
|
|
|
|
|
|
Perl MVC framework. It provides a feature-rich extended development stack, as |
332
|
|
|
|
|
|
|
well as easy access to common out-of-the-box application paradigms, such as |
333
|
|
|
|
|
|
|
powerful CRUD-based front-ends for DBIx::Class models, user access and |
334
|
|
|
|
|
|
|
authorization, RESTful URL navigation schemes, pure Ajax interfaces with no |
335
|
|
|
|
|
|
|
browser page loads, templating engine with front-side CMS features, declarative |
336
|
|
|
|
|
|
|
configuration layers, and more. |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
It's not designed as a JSON API and doesn't use HAL, so it's not directly |
339
|
|
|
|
|
|
|
comparable to WebAPI::DBIC. |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
=head1 INTEGRATIONS |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
This section provides information on how to integrate WebAPI::DBIC with |
344
|
|
|
|
|
|
|
existing applications. |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
=head2 Catalyst |
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
As with any PSGI application, WebAPI::DBIC can integrate into Catalyst fairly |
349
|
|
|
|
|
|
|
simply with L<Catalyst::Action::FromPSGI>. Here's an example integration: |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
package MyApp::Controller::HelloName; |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
use base 'Catalyst::Controller'; |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
sub api : Path('/api') ActionClass('FromPSGI') { |
356
|
|
|
|
|
|
|
my ($self, $c) = @_; |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
WebAPI::DBIC::WebApp->new({ |
359
|
|
|
|
|
|
|
schema => $c->model('DB')->schema, |
360
|
|
|
|
|
|
|
... |
361
|
|
|
|
|
|
|
})->to_psgi_app |
362
|
|
|
|
|
|
|
} |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
=head2 Dancer |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
I<I'd welcome any information you could contribute here.> |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
=head2 Mojolicious |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
I<I'd welcome any information you could contribute here.> |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
=head2 ... |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
=head1 HOW TO GET HELP |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
=over |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
=item * IRC: irc.perl.org#webapi |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
=for html <a href="https://chat.mibbit.com/#webapi@irc.perl.org">(click for instant chatroom login)</a> |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
=for comment =item * RT Bug Tracker: L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=WebAPI-DBIC> |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
=item * Source: L<https://github.com/timbunce/WebAPI-DBIC> |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
=back |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
See also https://metacpan.org/pod/distribution/WebAPI-DBIC/NOTES.pod |
389
|
|
|
|
|
|
|
and https://github.com/timbunce/WebAPI-DBIC/issues |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
If there's anything you specifically need, just ask! |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
=head1 CREDITS |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
Stevan Little gets top billing for creating L<Web::Machine> and L<Path::Router> |
396
|
|
|
|
|
|
|
(not to mention L<Moose> and much else besides). |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
Matt Trout and Peter Rabbitson and the rest of the L<DBIx::Class> team for |
399
|
|
|
|
|
|
|
creating and maintaining such an excellent object <-> relational mapper. |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
Arthur Axel "fREW" Schmidt, both for his original "drinkup" prototype using |
402
|
|
|
|
|
|
|
Web::Machine that WebAPI::DBIC is based on, and for offering to help with the |
403
|
|
|
|
|
|
|
work required to open source and release WebAPI::DBIC to CPAN. Without that, |
404
|
|
|
|
|
|
|
and further help from Fitz Elliott, WebAPI::DBIC might still be a closed source |
405
|
|
|
|
|
|
|
internal project. |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
=head1 OVERVIEW OF REPRESENTIONS AND ACTIONS |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
The docs below are from old internal documentation. They're a bit rought and |
410
|
|
|
|
|
|
|
will be reworked and found a better home. They're here for now because they are |
411
|
|
|
|
|
|
|
useful to give a sense of how the API works and what it supports. |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
=head2 GENERIC ENTITY REPRESENTIONS |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
Here we define the default behavior for GET, PUT, DELETE and POST methods on |
416
|
|
|
|
|
|
|
item and set resources. |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
In these examples the ~ symbol is used to represent a common prefix. The |
419
|
|
|
|
|
|
|
prefix is intended to contain at least a single path name element plus a |
420
|
|
|
|
|
|
|
version number element, for example, in: |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
GET ~/ecosystems/ |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
the ~ represents a prefix such as "/clients/v1", so the above is a shorthand |
425
|
|
|
|
|
|
|
way of representing: |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
GET /clients/v1/ecosystems/ |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
=head3 Conventions |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
Resource names are typically plural nouns, and lower case, with underscores if required. |
432
|
|
|
|
|
|
|
Verbs could be used for for non-resource requests and might be capitalized (e.g. /Convert?from=Y&to=Y). |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
A parameter that's part of the url is represented in these examples with the |
435
|
|
|
|
|
|
|
:name convention, e.g. :id. |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
XXX That might change to the 'URL Template' RFC6570 style |
438
|
|
|
|
|
|
|
http://tools.ietf.org/html/rfc6570 |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
=head2 GET Item |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
GET ~/resources/:id |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
returns |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
{ |
447
|
|
|
|
|
|
|
_links: { ... } # optional |
448
|
|
|
|
|
|
|
_embedded: { ... } # optional |
449
|
|
|
|
|
|
|
_meta: { ... } # optional |
450
|
|
|
|
|
|
|
... # data attributes, optional |
451
|
|
|
|
|
|
|
} |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
The optional _links object holds relevant links in the HAL format |
454
|
|
|
|
|
|
|
(see below). This enables interactive browsing of the API. |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
The optional _embedded object holds embedded resources in the HAL format. |
457
|
|
|
|
|
|
|
(see L</prefetch>). |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
The optional _meta attribute might include things like the name of the |
460
|
|
|
|
|
|
|
attribute to treat as the label, or a count of items matching a search. |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
GET ~/ecosystems/1 |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
would include |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
{ |
467
|
|
|
|
|
|
|
id: 1, |
468
|
|
|
|
|
|
|
... |
469
|
|
|
|
|
|
|
person_id: 2, # foreign key |
470
|
|
|
|
|
|
|
... |
471
|
|
|
|
|
|
|
_links { |
472
|
|
|
|
|
|
|
self: { |
473
|
|
|
|
|
|
|
href: "/ecosystems/1" |
474
|
|
|
|
|
|
|
}, |
475
|
|
|
|
|
|
|
"relation:person": { |
476
|
|
|
|
|
|
|
href: /person/19 |
477
|
|
|
|
|
|
|
}, |
478
|
|
|
|
|
|
|
"relation:email_domain": { |
479
|
|
|
|
|
|
|
href: "/email_domain/8" |
480
|
|
|
|
|
|
|
} |
481
|
|
|
|
|
|
|
}, |
482
|
|
|
|
|
|
|
} |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
The "relation" links describe the relationships this resource has with other resources. |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
Also see L</prefetch>. |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
=head2 GET Item - Optional Parameters |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
=head3 prefetch |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
Prefetch is a mechanism in DBIx::Class by which related resultsets can be returned |
493
|
|
|
|
|
|
|
along with the primary resultset. This prefetching is performed by a single query and |
494
|
|
|
|
|
|
|
so improves efficiency by reducing the number of database requests. |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
In WebAPI::DBIC the C<prefetch> parameter enables use of DBIx::Class prefetch and |
497
|
|
|
|
|
|
|
so allows any data in related resultsets to be returned as part of the same |
498
|
|
|
|
|
|
|
response. This allows the user to make one GET to return most, and possibly all, |
499
|
|
|
|
|
|
|
of the data needed by the requesting application. This reduces the number of HTTP requests. |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
Note that prefetch is only effective for response types that support embedded |
502
|
|
|
|
|
|
|
data, e.g, C<application/hal+json>. |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
Prefetching in WebAPI::DBIC and DBIx::Class uses the accessor names defined in the |
505
|
|
|
|
|
|
|
Result class for the given Resultset. These should be used in the prefetch parameter. |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
The following examples assume a Schema setup similar to the following: |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
package MyApp::Schema::Result::Artist; |
510
|
|
|
|
|
|
|
__PACKAGE__->has_many('albums' => 'MyApp::Schema::Result::CD', 'album_artist'); |
511
|
|
|
|
|
|
|
__PACKAGE__->has_many('cd_artists' => 'MyApp::Schama::Result::CDArtist', 'artistid'); |
512
|
|
|
|
|
|
|
__PACKAGE__->belongs_to('producer' => 'MyApp::Schema::Result::Producer', 'producerid'); |
513
|
|
|
|
|
|
|
__PACKAGE__->many_to_many('cds' => 'cd_artists', 'cd'); |
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
package MyApp::Schema::Result::CD; |
516
|
|
|
|
|
|
|
__PACKAGE__->has_many('cd_artists' => 'MyApp::Schema::Result::CDArtist', 'cdid'); |
517
|
|
|
|
|
|
|
__PACKAGE__->belongs_to('album_artist' => 'MyApp::Schema::Result::Artist', 'album_artist'); |
518
|
|
|
|
|
|
|
__PACKAGE__->many_to_many('artists' => 'cd_artists', 'artist'); |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
package MyApp::Schema::Result::CDArtists; |
521
|
|
|
|
|
|
|
__PACKAGE__->belongs_to('cd' => 'MyApp::Schema::Result::CD', 'cdid'); |
522
|
|
|
|
|
|
|
__PACKAGE__->belongs_to('artist' => 'MyApp::Schema::Result::Artist', 'artistid'); |
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
=head4 comma seperated lists |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
Where all related data for individual directly related resultsets are desired |
527
|
|
|
|
|
|
|
then a comma seperated list can be provided to the the prefetch parameter |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
artist/1?prefetch=producer,albums |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
(Note that you can't provide the prefetch parameter multiple times to achieve |
532
|
|
|
|
|
|
|
the same result.) |
533
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
This would return the following JSON+HAL: |
535
|
|
|
|
|
|
|
|
536
|
|
|
|
|
|
|
{ |
537
|
|
|
|
|
|
|
artistid: 1, |
538
|
|
|
|
|
|
|
producerid: 1, |
539
|
|
|
|
|
|
|
_embedded: { |
540
|
|
|
|
|
|
|
producer: { |
541
|
|
|
|
|
|
|
producer: id, |
542
|
|
|
|
|
|
|
}, |
543
|
|
|
|
|
|
|
albums: [{ |
544
|
|
|
|
|
|
|
cdid: 1, |
545
|
|
|
|
|
|
|
album_artist: 1, |
546
|
|
|
|
|
|
|
},{ |
547
|
|
|
|
|
|
|
cdid: 2, |
548
|
|
|
|
|
|
|
album_artist: 1, |
549
|
|
|
|
|
|
|
}], |
550
|
|
|
|
|
|
|
}, |
551
|
|
|
|
|
|
|
_links: { |
552
|
|
|
|
|
|
|
producer: /producer/1, |
553
|
|
|
|
|
|
|
albums: /artists/1?albums~json={-or: [{cdid: 1}, {cdid: 2}]} # XXX not correct |
554
|
|
|
|
|
|
|
} |
555
|
|
|
|
|
|
|
} |
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
=head4 json |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
The C<prefetch> parameter can be specified as a more complex JSON-encoded |
560
|
|
|
|
|
|
|
parameter value. This allows for the full use of prefetch chains. |
561
|
|
|
|
|
|
|
Using key value pairs and lists, prefetches can be nested from one resultset to |
562
|
|
|
|
|
|
|
another: |
563
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
artist/1?prefetch~json={["producer","albums"]} |
565
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
This would produce the same results as above: |
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
artist/1?prefetch~json={["producer","cd_artists",{"cds":"album_artist"}]} |
569
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
would producer the following JSON+HAL: |
571
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
{ |
573
|
|
|
|
|
|
|
artistid: 1, |
574
|
|
|
|
|
|
|
producerid: 1, |
575
|
|
|
|
|
|
|
_embedded: { |
576
|
|
|
|
|
|
|
producer: { |
577
|
|
|
|
|
|
|
producer: id, |
578
|
|
|
|
|
|
|
}, |
579
|
|
|
|
|
|
|
cd_artists: [{ |
580
|
|
|
|
|
|
|
artistsid: 1, |
581
|
|
|
|
|
|
|
cdid: 1, |
582
|
|
|
|
|
|
|
_embedded: { |
583
|
|
|
|
|
|
|
cd: { |
584
|
|
|
|
|
|
|
cdid: 1, |
585
|
|
|
|
|
|
|
album_artist: 1, |
586
|
|
|
|
|
|
|
_embedded: { |
587
|
|
|
|
|
|
|
album_artist: { |
588
|
|
|
|
|
|
|
artistid: 1, |
589
|
|
|
|
|
|
|
producerid: 1, |
590
|
|
|
|
|
|
|
}, |
591
|
|
|
|
|
|
|
}, |
592
|
|
|
|
|
|
|
_links: { |
593
|
|
|
|
|
|
|
/album_artist: /artist/1 |
594
|
|
|
|
|
|
|
}, |
595
|
|
|
|
|
|
|
}, |
596
|
|
|
|
|
|
|
}, |
597
|
|
|
|
|
|
|
_links: { |
598
|
|
|
|
|
|
|
cd: /cd/1 |
599
|
|
|
|
|
|
|
}, |
600
|
|
|
|
|
|
|
}], |
601
|
|
|
|
|
|
|
}, |
602
|
|
|
|
|
|
|
_links: { |
603
|
|
|
|
|
|
|
producer: /producer/1, |
604
|
|
|
|
|
|
|
cd_artists: /cdartists?artistid=1&cdid=1, |
605
|
|
|
|
|
|
|
} |
606
|
|
|
|
|
|
|
} |
607
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
NOTE: many_to_many relationships can't be supported as they are not true relationships |
609
|
|
|
|
|
|
|
the related data should be prefetched using the has_many relationship and their join |
610
|
|
|
|
|
|
|
table as in the above example. |
611
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
=head4 where on prefetch |
613
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
The WHERE clause generated can filter results based on related data, as you |
615
|
|
|
|
|
|
|
would expect in a SQL style JOIN. To refer to fields in related resultsets, |
616
|
|
|
|
|
|
|
prefix the name of the field with the name of the relationship: |
617
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
/artist?prefetch=albumns&albums.title='My CD Title' |
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
This would return all artists which have an album with the title 'My CD Title'. |
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
=head3 fields |
623
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
The fields parameter can be used to limit the fields returned in the response. |
625
|
|
|
|
|
|
|
For example: |
626
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
fields=field1,field2 |
628
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
This also works |
630
|
|
|
|
|
|
|
in combination with the prefetch parameter. As with querying on prefetched relations, the |
631
|
|
|
|
|
|
|
relation accessor should be appended before the field name in question. |
632
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
/artist/1?prefetch=albums&fields=artistid,albumns.title |
634
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
For more information on PREFETCHING and JOINS see L<DBIx::Class::Resultset#PREFETCHING> |
636
|
|
|
|
|
|
|
|
637
|
|
|
|
|
|
|
NOTE: DBIx::Class does not support the returning of related data if the relationship |
638
|
|
|
|
|
|
|
accessor for that data matches a column on the requested Set or Item but the fields |
639
|
|
|
|
|
|
|
parameter does not include that column. You must explicitly request fields if prefetching |
640
|
|
|
|
|
|
|
a relation with the same name |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
Using the above example. If the artist has a producer column/field then the following is |
643
|
|
|
|
|
|
|
invalid: |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
/artist/1?prefetch=producer&fields=artistid,producer.producer.producerid |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
but the following is valid: |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
/artist/1?prefetch=producer&fields=artistid,producer,producer.producer.producerid |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
=head3 with |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
The C<with> parameter is used to control optional items within responses. It's |
654
|
|
|
|
|
|
|
a comma separated list of words. This parameter is only passed-through in paging links. |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
* B<count> |
657
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
Adds a C<count> attribute to the C<_meta> hash in the results containing the |
659
|
|
|
|
|
|
|
count of items in the set matched by the request, i.e., the number of items |
660
|
|
|
|
|
|
|
that would be returned if paging was disabled. Also adds a C<last> link to the |
661
|
|
|
|
|
|
|
C<_links> section of the results. |
662
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
* B<nolinks> |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
TBD - possibly used to disable links in the results, especially for large sets |
666
|
|
|
|
|
|
|
of small items where the links section would take significant time and space to |
667
|
|
|
|
|
|
|
construct and return. Might be better as a linkdepth=N where N is decremented |
668
|
|
|
|
|
|
|
at each level of embedding so linkdepth=0 disables all links, but linkdepth=1 |
669
|
|
|
|
|
|
|
allows paging of the set but doesn't include links in the embedded resources. |
670
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
=head2 GET on Set |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
GET ~/ecosystems |
674
|
|
|
|
|
|
|
|
675
|
|
|
|
|
|
|
returns |
676
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
{ |
678
|
|
|
|
|
|
|
_links: { ... }, # optional |
679
|
|
|
|
|
|
|
_meta: { ... }, # optional |
680
|
|
|
|
|
|
|
_embedded: { |
681
|
|
|
|
|
|
|
ecosystems => [ |
682
|
|
|
|
|
|
|
{ ... }, ... |
683
|
|
|
|
|
|
|
] |
684
|
|
|
|
|
|
|
} |
685
|
|
|
|
|
|
|
} |
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
The _embedded object contains a key matching the resource name whoose |
688
|
|
|
|
|
|
|
value is an array of those resources, in HAL format. It may seem unusual that |
689
|
|
|
|
|
|
|
the response isn't simply an array of the resources, but you can think of the |
690
|
|
|
|
|
|
|
'set' as a 'virtual' entity that I<contains nothing itself> but just acts as a |
691
|
|
|
|
|
|
|
container, or view, for a set of I<embedded resources>. |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
The _links objects would include links in HAL format for first/prev/next/last. |
694
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
The _meta could include attributes like limit, offset. |
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
=head2 GET on Set - Optional Parameters |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
=head3 Paging |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
Set results are returned in pages to prevent accidentally trying to |
702
|
|
|
|
|
|
|
fetch very large numbers of rows. The default is a small number. |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
rows=N - default 30 (at the time of writing) |
705
|
|
|
|
|
|
|
page=N - default 1 |
706
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
=head3 fields |
708
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
Partial results, as for GET Item above. |
710
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
=head3 Sorting and Ordering |
712
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
sort=field1 |
714
|
|
|
|
|
|
|
sort=field1,-field2 |
715
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
A comma-separated list of one or more ordering clauses. Each clause consists of a |
717
|
|
|
|
|
|
|
field designator with an optional C<-> prefix to indicate descending order |
718
|
|
|
|
|
|
|
instead of ascending. |
719
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
Field names can refer to fields of L</prefetch> relations. For example: |
721
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
~/ecosystems_people?prefetch=person,client_auth&sort=client_auth.username |
723
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
The parameter name C<order> can be used as a deprecated alias for C<sort>. |
725
|
|
|
|
|
|
|
The direction can also be specified by appending either "C< asc>" or "C< desc>" |
726
|
|
|
|
|
|
|
to the field designator. This syntax is deprecated. |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
=head3 Filtering |
729
|
|
|
|
|
|
|
|
730
|
|
|
|
|
|
|
?me.fieldname=value |
731
|
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
Filtering with query params |
733
|
|
|
|
|
|
|
|
734
|
|
|
|
|
|
|
?me.color=red&me.state=running |
735
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
The me.*= values can be JSON data structures if the field name is sufixed with |
737
|
|
|
|
|
|
|
~json, for example: |
738
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
?me.color~json=["red","blue"] # would actually be URL encoded |
740
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
which would be evaluated as an SQL 'IN' expression: |
742
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
color IN ('red', 'blue') |
744
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
More complex expressions can be expressed using hashes, for example: |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
?me.color~json={"like":"%red%"} # would actually be URL encoded |
748
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
would be evaluated as |
750
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
color LIKE '%red%' |
752
|
|
|
|
|
|
|
|
753
|
|
|
|
|
|
|
and |
754
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
?me.foo~json=[ "-and", {"!=":2}, {"!=":1} ] # shown unencoded |
756
|
|
|
|
|
|
|
|
757
|
|
|
|
|
|
|
would be evaluated as |
758
|
|
|
|
|
|
|
|
759
|
|
|
|
|
|
|
foo != 2 and foo != 1 |
760
|
|
|
|
|
|
|
|
761
|
|
|
|
|
|
|
See https://metacpan.org/module/SQL::Abstract#WHERE-CLAUSES for more examples. |
762
|
|
|
|
|
|
|
|
763
|
|
|
|
|
|
|
The me.* parameters are only passed-through in paging links. |
764
|
|
|
|
|
|
|
|
765
|
|
|
|
|
|
|
=head3 Prefetching Related Objects |
766
|
|
|
|
|
|
|
|
767
|
|
|
|
|
|
|
?prefetch=person,client_auth |
768
|
|
|
|
|
|
|
|
769
|
|
|
|
|
|
|
The resource may have 1-1 and 1-N relationships with other resources. |
770
|
|
|
|
|
|
|
(E.g., "belongs_to" and "has_many" relationships in DBIx::Class terminology.) |
771
|
|
|
|
|
|
|
|
772
|
|
|
|
|
|
|
The relevant instances of related resources can be fetched and returned along |
773
|
|
|
|
|
|
|
with the requested resource by listing the relationships in a prefetch parameter. |
774
|
|
|
|
|
|
|
|
775
|
|
|
|
|
|
|
For example: GET /ecosystems_people?prefetch=person,client_auth |
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
{ |
778
|
|
|
|
|
|
|
"_links": { ... }, |
779
|
|
|
|
|
|
|
"_embedded": { |
780
|
|
|
|
|
|
|
"ecosystems_people": [ |
781
|
|
|
|
|
|
|
{ |
782
|
|
|
|
|
|
|
"client_auth_id": "29", |
783
|
|
|
|
|
|
|
"person_id": "8", |
784
|
|
|
|
|
|
|
... |
785
|
|
|
|
|
|
|
"_links": { ... }, |
786
|
|
|
|
|
|
|
"_embedded": { |
787
|
|
|
|
|
|
|
"client_auth": { # embedded client_auth resourse |
788
|
|
|
|
|
|
|
"id": 29 |
789
|
|
|
|
|
|
|
... |
790
|
|
|
|
|
|
|
}, |
791
|
|
|
|
|
|
|
"person": { # embedded person resourse |
792
|
|
|
|
|
|
|
"id": 8, |
793
|
|
|
|
|
|
|
... |
794
|
|
|
|
|
|
|
} |
795
|
|
|
|
|
|
|
}, |
796
|
|
|
|
|
|
|
}, |
797
|
|
|
|
|
|
|
... # next ecosystems_people resource |
798
|
|
|
|
|
|
|
] |
799
|
|
|
|
|
|
|
} |
800
|
|
|
|
|
|
|
} |
801
|
|
|
|
|
|
|
|
802
|
|
|
|
|
|
|
=head3 distinct |
803
|
|
|
|
|
|
|
|
804
|
|
|
|
|
|
|
distinct=1 |
805
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
Only return distinct results. |
807
|
|
|
|
|
|
|
|
808
|
|
|
|
|
|
|
Currently this parameter requires that both the fields and sort parameters are |
809
|
|
|
|
|
|
|
provided, and have identical values. |
810
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
The results are returned in HAL format, i.e., as an array of objects in an |
812
|
|
|
|
|
|
|
_embedded field, but the objects themselves are not in HAL format, i.e. they |
813
|
|
|
|
|
|
|
don't have _links or _embedded elements. |
814
|
|
|
|
|
|
|
|
815
|
|
|
|
|
|
|
=head2 PUT on Item |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
Update resource attributes using the JSON attribute values in the request body. |
818
|
|
|
|
|
|
|
|
819
|
|
|
|
|
|
|
Embedded related resources can be supplied (if the Content-Type is C<application/hal+json>). |
820
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
Changes will be made in a single transaction. |
822
|
|
|
|
|
|
|
|
823
|
|
|
|
|
|
|
Prefetch of related resources is supported. |
824
|
|
|
|
|
|
|
|
825
|
|
|
|
|
|
|
TODO Enable use of the ETag header for optimistic locking? |
826
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
=head2 PUT on Set |
828
|
|
|
|
|
|
|
|
829
|
|
|
|
|
|
|
Not supported. |
830
|
|
|
|
|
|
|
|
831
|
|
|
|
|
|
|
=head2 DELETE on Item |
832
|
|
|
|
|
|
|
|
833
|
|
|
|
|
|
|
Delete the record. |
834
|
|
|
|
|
|
|
|
835
|
|
|
|
|
|
|
=head2 DELETE on Set |
836
|
|
|
|
|
|
|
|
837
|
|
|
|
|
|
|
Not supported. |
838
|
|
|
|
|
|
|
|
839
|
|
|
|
|
|
|
=head2 POST on Item |
840
|
|
|
|
|
|
|
|
841
|
|
|
|
|
|
|
Not supported. |
842
|
|
|
|
|
|
|
|
843
|
|
|
|
|
|
|
=head2 POST on Set |
844
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
Create a new resource in the set. Returns a 302 redirect with a Location |
846
|
|
|
|
|
|
|
header giving the URL of the newly created resource. |
847
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
Any attributes that aren't specified in the POST data will be given the default |
849
|
|
|
|
|
|
|
values specified by the database schema. |
850
|
|
|
|
|
|
|
|
851
|
|
|
|
|
|
|
The C<prefetch> parameter can be used to request that the created resource |
852
|
|
|
|
|
|
|
(C<prefetch=self>) and any related resources, be returned in the body of the |
853
|
|
|
|
|
|
|
response. |
854
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
The rollback=1 parameter let's you rollback a POST to a set, e.g., for testing. |
856
|
|
|
|
|
|
|
|
857
|
|
|
|
|
|
|
TBD check that only fields valid for GET have been supplied |
858
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
=head2 Creating Related Resources |
860
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
If the Content-Type is C<application/hal+json> then related resources can be |
862
|
|
|
|
|
|
|
provided via the C<_embedded> attribute. They will be created first and the |
863
|
|
|
|
|
|
|
corresponding key fields of the main resource will be set to the appropriate |
864
|
|
|
|
|
|
|
values before it's inserted. All database changes will happen in a single transaction. |
865
|
|
|
|
|
|
|
|
866
|
|
|
|
|
|
|
For example, given a POST to /albums containing: |
867
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
{ |
869
|
|
|
|
|
|
|
name: "album name", |
870
|
|
|
|
|
|
|
artist_id: null, # optional |
871
|
|
|
|
|
|
|
_embedded => { |
872
|
|
|
|
|
|
|
artist => { |
873
|
|
|
|
|
|
|
name: "artist name", |
874
|
|
|
|
|
|
|
} |
875
|
|
|
|
|
|
|
} |
876
|
|
|
|
|
|
|
} |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
The artist resource would be created first and its primary key would be |
879
|
|
|
|
|
|
|
used to set the artist_id field before that was created. |
880
|
|
|
|
|
|
|
|
881
|
|
|
|
|
|
|
This process works recursively for any number of level and any number of |
882
|
|
|
|
|
|
|
relations at each level. |
883
|
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
=head2 Errors |
885
|
|
|
|
|
|
|
|
886
|
|
|
|
|
|
|
Error status responses should include a JSON object with at least these fields: |
887
|
|
|
|
|
|
|
|
888
|
|
|
|
|
|
|
{ |
889
|
|
|
|
|
|
|
status: NNN, |
890
|
|
|
|
|
|
|
message: "...", |
891
|
|
|
|
|
|
|
} |
892
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
XXX Needs to be extended to be able to express errors related to specific |
894
|
|
|
|
|
|
|
attributes in the request. |
895
|
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
The above is out of date. XXX review work on JSON media types for error |
897
|
|
|
|
|
|
|
reporting (I recall there's one that has adopted HAL). |
898
|
|
|
|
|
|
|
|
899
|
|
|
|
|
|
|
=head2 Invoking Methods |
900
|
|
|
|
|
|
|
|
901
|
|
|
|
|
|
|
To enable the execution of functionality not covered by the general HTTP |
902
|
|
|
|
|
|
|
mechanisms described above, it's possible to define resources that represent |
903
|
|
|
|
|
|
|
arbitary methods. These methods are executed by a POST request to the |
904
|
|
|
|
|
|
|
correponding resource. The body of the request contains the parameters to the |
905
|
|
|
|
|
|
|
method. |
906
|
|
|
|
|
|
|
|
907
|
|
|
|
|
|
|
Currently a method can only be invoked on an item resource. The resource for |
908
|
|
|
|
|
|
|
the method call is simply the url of the item resource with '/invoke/:method' |
909
|
|
|
|
|
|
|
appended: |
910
|
|
|
|
|
|
|
|
911
|
|
|
|
|
|
|
POST ~/ecosystems/:id/invoke/:method |
912
|
|
|
|
|
|
|
|
913
|
|
|
|
|
|
|
The request supports the same query parameters as the corresponding item |
914
|
|
|
|
|
|
|
resource. |
915
|
|
|
|
|
|
|
|
916
|
|
|
|
|
|
|
=head3 Default Argument and Response Handling |
917
|
|
|
|
|
|
|
|
918
|
|
|
|
|
|
|
Custom method resources can be defined which can perform any desired action, |
919
|
|
|
|
|
|
|
argument and response handling. |
920
|
|
|
|
|
|
|
|
921
|
|
|
|
|
|
|
A default behaviour is provided to handle simple cases, and that is what is |
922
|
|
|
|
|
|
|
described here. |
923
|
|
|
|
|
|
|
|
924
|
|
|
|
|
|
|
The named method is invoked on the item object specified by the item resource. |
925
|
|
|
|
|
|
|
In other words, the method is a method in the schema's Result class. |
926
|
|
|
|
|
|
|
|
927
|
|
|
|
|
|
|
The POST request must use content-type of application/json and, if arguments |
928
|
|
|
|
|
|
|
are required, are specified via an 'args' element in the body JSON: |
929
|
|
|
|
|
|
|
|
930
|
|
|
|
|
|
|
{ args => [ ... ] } |
931
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
The method is called in a scalar context. |
933
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
If the method returns a DBIx::Class::Row object it is returned as a JSON hash. |
935
|
|
|
|
|
|
|
|
936
|
|
|
|
|
|
|
If the method returns a DBIx::Class ResultSet object it is returned as a JSON |
937
|
|
|
|
|
|
|
array containing a hash for every row in the result set. There is no paging. |
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
If the method returns any other kind of value it it returned as a JSON hash |
940
|
|
|
|
|
|
|
containing a single element 'result': |
941
|
|
|
|
|
|
|
|
942
|
|
|
|
|
|
|
{ result: ... } |
943
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
(To avoid attempting to serialize objects, if the result is blessed then it's |
945
|
|
|
|
|
|
|
stringified.) |
946
|
|
|
|
|
|
|
|
947
|
|
|
|
|
|
|
Note that this default behaviour is liable to change. If you want to make |
948
|
|
|
|
|
|
|
method calls like this you should define your own resource based on the one provided. |
949
|
|
|
|
|
|
|
|
950
|
|
|
|
|
|
|
=head1 AUTHOR |
951
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
Tim Bunce <Tim.Bunce@pobox.com> |
953
|
|
|
|
|
|
|
|
954
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
955
|
|
|
|
|
|
|
|
956
|
|
|
|
|
|
|
This software is copyright (c) 2015 by Tim Bunce. |
957
|
|
|
|
|
|
|
|
958
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
959
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
960
|
|
|
|
|
|
|
|
961
|
|
|
|
|
|
|
=cut |