---
name: "PONAPI::Client"
section: "3pm"
platform: "linux"
package: "libponapi-client-perl"
version: "0.002012-2"
release: "Debian 13 (Trixie)"
platforms: ["linux"]
url: "https://new.carta.tech/PONAPI::Client"
---

# PONAPI::Client(3pm)

Client to a {JSON:API} service (http://jsonapi.org/) v1.0

## 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} \<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 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 \<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 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 \<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:               /$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.
