NAME
PONAPI::Client - Client to a {JSON:API} service (http://jsonapi.org/) v1.0
VERSION
version 0.002012
SYNOPSIS
use
PONAPI::Client;
my
$client
= PONAPI::Client->new(
host
=>
$host
,
port
=>
$port
,
);
$client
->retrieve_all(
type
=>
$type
);
$client
->retrieve(
type
=>
$type
,
id
=>
$id
,
);
$client
->retrieve_relationships(
type
=>
$type
,
id
=>
$id
,
rel_type
=>
$rel_type
,
);
$client
->retrieve_by_relationship(
type
=>
$type
,
id
=>
$id
,
rel_type
=>
$rel_type
,
);
$client
->create(
type
=>
$type
,
data
=> {
attributes
=> { ... },
relationships
=> { ... },
},
);
$client
->
delete
(
type
=>
$type
,
id
=>
$id
,
);
$client
->update(
type
=>
$type
,
id
=>
$id
,
data
=> {
type
=>
$type
,
id
=>
$id
,
attributes
=> { ... },
relationships
=> { ... },
}
);
$client
->delete_relationships(
type
=>
$type
,
id
=>
$id
,
rel_type
=>
$rel_type
,
data
=> [
{
type
=>
$rel_type
,
id
=>
$rel_id
},
...
],
);
$client
->create_relationships(
type
=>
$type
,
id
=>
$id
,
rel_type
=>
$rel_type
,
data
=> [
{
type
=>
$rel_type
,
id
=>
$rel_id
},
...
],
);
$client
->update_relationships(
type
=>
$type
,
id
=>
$id
,
rel_type
=>
$rel_type
,
# for a one-to-one:
data
=> {
type
=>
$rel_type
,
id
=>
$rel_id
},
# or for a one-to-many:
data
=> [
{
type
=>
$rel_type
,
id
=>
$rel_id
},
...
],
);
# If the endpoint uses an uncommon url format:
$client
->retrieve(
type
=>
'foo'
,
id
=> 43,
# Will generate a request to
# host:port/type_foo_id_43
uri_template
=>
"type_{type}_id_{id}"
,
);
DESCRIPTION
PONAPI::Client
is a {JSON:API} compliant client; it should be able to communicate with any API-compliant service.
The client does a handful of checks required by the spec, then uses Hijk to communicate with the service.
In most cases, all API methods return a response document:
my
$response
=
$client
->retrieve(...);
In list context however, all api methods will return the request status and the document:
my
(
$status
,
$response
) =
$client
->retrieve(...)
Response documents will look something like these:
# Successful retrieve(type => 'articles', id => 2)
{
jsonapi
=> {
version
=>
"1.0"
},
links
=> {
self
=>
"/articles/2"
},
data
=> { ... },
meta
=> { ... },
# May not be there
included
=> [ ... ],
# May not be there, see C<include>
}
# Successful retrieve_all( type => 'articles' )
{
jsonapi
=> {
version
=>
"1.0"
},
links
=> {
self
=>
"/articles"
},
# May include pagination links
data
=> [
{ ... },
{ ... },
...
],
meta
=> { ... },
# May not be there
included
=> [ ... ],
# May not be there, see C<include>
}
# Successful create(type => 'foo', data => { ... })
{
jsonapi
=> {
version
=>
"1.0"
},
links
=> {
self
=>
"/foo/$created_id"
},
data
=> {
type
=>
'foo'
,
id
=>
$created_id
},
}
# Successful update(type => 'foo', id => 2, data => { ... })
{
jsonapi
=> {
version
=>
"1.0"
},
links
=> {
self
=>
"/foo/2"
},
# may not be there
meta
=> { ... },
# may not be there
}
# Error, see http://jsonapi.org/format/#error-objects
{
jsonapi
=> {
version
=>
"1.0"
},
errors
=> [
{ ... },
# error 1
...
# potentially others
],
}
However, there are situations where the server may respond with a 204 No Content
and no response document; depending on the situation, it might be worth checking the status.
METHODS
new
Creates a new PONAPI::Client
object. Takes a couple of attributes:
- host
-
The hostname (or IP address) of the service. Defaults to localhost.
- port
-
Port of the service. Defaults to 5000.
- send_version_header
-
Sends a
X-PONAPI-Client-Version
header set to the {JSON:API} version the client supports. Defaults to true.
retrieve_all
retrieve_all(
type
=>
$type
,
%optional_arguments
)
Retrieves all resources of the given type. In SQL, this is similar to SELECT * FROM $type
.
This handles several arguments:
- fields
-
Spec.
Instead of returning every attribute and relationship from a given resource,
fields
can be used to specify exactly what is returned.This excepts a hashref of arrayrefs, where the keys are types, and the values are either attribute names, or relationship names.
$client
->retrieve_all(
type
=>
'people'
,
fields
=> {
people
=> [
'name'
,
'age'
] }
)
Note that an attribute not being in fields means the opposite to an attribute having empty fields:
# No attributes or relationships for both people and comments
$client
->retrieve_all(
type
=>
'people'
,
fields
=> {
people
=> [],
comments
=> [] },
);
# No attributes or relationships for comments, but
# ALL attributes and relationships for people
$client
->retrieve_all(
type
=>
'people'
,
fields
=> {
comments
=> [] },
);
- include
-
Spec.
include
can be used to fetch related resources. The example below is fetching both all the people, and all comments made by those people:my
$response
=
$client
->retrieve_all(
type
=>
'people'
,
include
=> [
'comments'
]
);
include
expects an arrayref of relationship names. In the response, the resources fetched will be in an arrayref under the top-levelincluded
key:say
$_
->{attributes}{body}
for
@{
$response
->{included} }
- page
-
Spec.
Requests that the server paginate the results. Each endpoint may have different pagination rules.
- sort
-
Spec.
Requests that the server sort the results in a given way:
$client
->retrieve_all(
type
=>
'people'
,
sort
=> [
qw/ age /
],
# sort by age, ascending
);
$client
->retrieve_all(
type
=>
'people'
,
sort
=> [
qw/ -age /
],
# sort by age, descending
);
Although not all endpoints will support this, it may be possible to sort by a relationship's attribute:
$client
->retrieve_all(
type
=>
'people'
,
sort
=> [
qw/ -comments.created_date /
],
);
- filter
-
Spec.
This one is entirely dependent on the endpoint. It's usually employed to act as a
WHERE
clause:$client
->retrieve_all(
type
=>
'people'
,
filter
=> {
id
=> [ 1, 2, 3, 4, 6 ],
# IN ( 1, 2, ... )
age
=> 34,
# age = 34
},
);
Sadly, more complex filters are currently not available.
retrieve
retrieve(
type
=>
$type
,
id
=>
$id
,
%optional_arguments
)
Similar to retrieve_all
, but retrieves a single resource.
retrieve_relationships
retrieve_relationships(
type
=>
$type
,
id
=>
$id
,
rel_type
=>
$rel_type
,
%optional_arguments
)
Retrieves all of $id
's relationships to $rel_type
as resource identifiers; that is, as hashrefs that contain only type
and id
:
# retrieve_relationships(type=>'people', id=>2, rel_type=>'comments')
{
jsonapi
=> {
version
=>
"1.0"
},
data
=> [
{
type
=>
'comments'
,
id
=> 4 },
{
type
=>
'comments'
,
id
=> 9 },
{
type
=>
'comments'
,
id
=> 14 },
]
}
These two do roughly the same thing:
my
$response
=
$client
->retrieve(
type
=>
$type
,
id
=>
$id
);
my
$relationships
=
$response
->{data}{relationships}{
$rel_type
};
say
join
", "
,
map
$_
->{id},
@$relationships
;
my
$response
=
$client
->retrieve_relationships(
type
=>
$type
,
id
=>
$id
,
rel_type
=>
$rel_type
,
);
my
$relationships
=
$response
->{data};
say
join
", "
,
map
$_
->{id},
@$relationships
;
However, retrieve_relationships
also allows you to page those relationships, which may be quite useful.
Keep in mind that retrieve_relationships
will return an arrayref for one-to-many relationships, and a hashref for one-to-ones.
retrieve_by_relationship
retrieve_by_relationship(
type
=>
$type
,
id
=>
$id
,
rel_type
=>
$rel_type
,
%optional_arguments
)
retrieve_relationships
on steroids. It behaves the same way, but will retrieve full resources, not just resource identifiers; because of this, you can also potentially apply more complex filters and sorts.
create
create(
type
=>
$type
,
data
=> { ... },
id
=>
$optional
)
Create a resource of type $type
using $data
to populate it. Data must include the type, and may include two other keys: attributes
and relationships
:
$client
->create(
type
=>
'comments'
,
data
=> {
type
=>
'comments'
,
attributes
=> {
body
=>
'abc'
},
relationships
=> {
author
=> {
type
=>
'people'
,
id
=> 55 },
liked_by
=> [
{
type
=>
'people'
,
id
=> 55 },
{
type
=>
'people'
,
id
=> 577 },
],
}
}
}
An optional id
may be provided, in which case the server may choose to use it when creating the new resource.
update
update(
type
=>
$type
,
id
=>
$id
,
data
=> { ... } )
Can be used to update the resource. Data must have type
and id
keys:
$client
->create(
type
=>
'comments'
,
id
=> 5,
data
=> {
type
=>
'comments'
,
id
=> 5,
attributes
=> {
body
=>
'new body!'
},
relationships
=> {
author
=>
undef
,
# no author
liked_by
=> [
{
type
=>
'people'
,
id
=> 79 },
],
}
}
}
An empty arrayref ([]
) can be used to clear one-to-many relationships, and undef
to clear one-to-one relationships.
A successful update
will always return a response document; see the spec for more details.
Spec.
delete
delete
(
type
=>
$type
,
id
=>
$id
)
Deletes the resource.
update_relationships
update_relationships(
type
=>
$type
,
id
=>
$id
,
rel_type
=>
$rel_type
,
data
=>
$data
)
Update a resource's relationships. Basically a shortcut to using update
.
For one-to-one relationships, data
can be either a single hashref, or undef. For one-to-many relationships, data
can be an arrayref; an empty arrayref means 'clear the relationship'.
create_relationships
create_relationships(
type
=>
$type
,
id
=>
$id
,
rel_type
=>
$rel_type
,
data
=> [{ ... }] )
Adds to the specified one-to-many relationship.
delete_relationships
delete_relationships(
type
=>
$type
,
id
=>
$id
,
rel_type
=>
$rel_type
,
data
=> [{ ... }] )
Deletes from the specified one-to-many relationship.
Endpoint URI format
By default, PONAPI::Client
assumes urls on the endpoint are in this format:
retrieve_all: /
$type
retrieve: /
$type
/
$id
retrieve_by_relationships: /
$type
/
$id
/
$rel_type
retrieve_relationships: /
$type
/
$id
/relationships/
$rel_type
create: /
$type
or /
$type
/
$id
delete
: /
$type
/
$id
update: /
$type
/
$id
update_relationships: /
$type
/
$id
/relationships/
$rel_type
create_relationships: /
$type
/
$id
/relationships/
$rel_type
delete_relationships: /
$type
/
$id
/relationships/
$rel_type
# Will generate a request to /foo/99
$client
->retrieve(
type
=>
'foo'
,
id
=> 99,
);
However, if another format is needed, two approaches are possible:
URI paths have a common prefix
If all the endpoint urls have a common prefix, ala /v1/articles
instead of simply /articles
, then you can just set uri_base
as needed:
$client
->retrieve(
type
=>
'foo'
,
id
=> 99,
uri_base
=>
'/v1'
);
We can also set this when creating the client; if done this way, all requests generated from this client will include the base:
my
$new_client
= PONAPI::Client->new(
uri_base
=>
'/v1'
,
...
);
# This will generate a request to /v1/foo/99
$new_client
->retrieve(
type
=>
'foo'
,
id
=> 99,
);
Completely different uris
If the endpoint's expected formats are wildly different, you can specify uri_template
with your request:
# Will generate a request to id_here_99_and_type_there/foo
$client
->retrieve(
type
=>
'foo'
,
id
=> 99,
uri_template
=>
'id_here_{id}_and_type_there/{type}'
);
These placeholders are recognized:
type
id
rel_type
This can only be done on a per-request basis.
AUTHORS
Mickey Nasriachi <mickey@cpan.org>
Stevan Little <stevan@cpan.org>
Brian Fraser <hugmeir@cpan.org>
COPYRIGHT AND LICENSE
This software is copyright (c) 2019 by Mickey Nasriachi, Stevan Little, Brian Fraser.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.