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_43uri_template => "type_{type}_id_{id}",);
DESCRIPTION
"PONAPI::Client" is a {JSON:API} <http://jsonapi.org/> 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 thereincluded => [ ... ], # May not be there, see C<include>}# Successful retrieve_all( type => 'articles' ){jsonapi => { version => "1.0" },links => { self => "/articles" }, # May include pagination linksdata => [{ ... },{ ... },...],meta => { ... }, # May not be thereincluded => [ ... ], # 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 theremeta => { ... }, # 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 <http://jsonapi.org/format/#fetching-sparse-fieldsets>.
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 <http://jsonapi.org/format/#fetching-includes>.
"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-level"included"key:say $_->{attributes}{body} for @{ $response->{included} }
- page
-
Spec <http://jsonapi.org/format/#fetching-pagination>.
Requests that the server paginate the results. Each endpoint may have different pagination rules.
- sort
-
Spec <http://jsonapi.org/format/#fetching-sorting>.
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 <http://jsonapi.org/format/#fetching-filtering>.
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 authorliked_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 <http://jsonapi.org/format/#crud-updating>.
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: /$typeretrieve: /$type/$idretrieve_by_relationships: /$type/$id/$rel_typeretrieve_relationships: /$type/$id/relationships/$rel_typecreate: /$type or /$type/$iddelete: /$type/$idupdate: /$type/$idupdate_relationships: /$type/$id/relationships/$rel_typecreate_relationships: /$type/$id/relationships/$rel_typedelete_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 <[email protected]>
- •
-
Stevan Little <[email protected]>
- •
-
Brian Fraser <[email protected]>
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.