mirror of https://github.com/sipwise/data-hal.git
This library has been developed by Lars Dieckow for Sipwise GmbH, who is the copyright holder.mr3.2.1
commit
68ae9d4791
@ -0,0 +1,56 @@
|
||||
use Module::Build qw();
|
||||
Module::Build->new(
|
||||
add_to_cleanup => [ 'Data-HAL-*' ],
|
||||
build_requires => {
|
||||
'Data::Visitor::Callback' => 0,
|
||||
'File::Slurp' => 0,
|
||||
'JSON' => 0,
|
||||
'Log::Any::Adapter' => 0,
|
||||
'Log::Any::Adapter::FileHandle' => 0,
|
||||
'strictures' => 0,
|
||||
'Test::Fatal' => 0,
|
||||
'Test::More' => 0,
|
||||
},
|
||||
configure_requires => {
|
||||
'Module::Build' => 0.40,
|
||||
},
|
||||
dist_version_from => 'lib/Data/HAL.pm',
|
||||
dynamic_config => 0,
|
||||
license => 'perl',
|
||||
meta-spec => {
|
||||
url => 'http://module-build.sourceforge.net/META-spec-v1.4.html',
|
||||
version => '1.4',
|
||||
},
|
||||
module_name => 'Data::HAL',
|
||||
no_index => {
|
||||
file => [
|
||||
'lib/Data/HAL/URI/NamespaceMap.pm',
|
||||
],
|
||||
},
|
||||
requires => {
|
||||
'boolean' => 0,
|
||||
'Clone' => 0,
|
||||
'Data::Visitor::Callback' => 0,
|
||||
'failures' => 0,
|
||||
'HTTP::Headers::Util' => 0,
|
||||
'JSON' => 0,
|
||||
'Log::Any' => 0,
|
||||
'MIME::Type' => 0,
|
||||
'Moo' => 0,
|
||||
'perl' => '5.008',
|
||||
'Safe::Isa' => 0,
|
||||
'Scalar::Util' => 0,
|
||||
'strictures' => 0,
|
||||
'Types::Standard' => 0,
|
||||
'URI' => 0,
|
||||
'URI::IRI' => 0,
|
||||
'URI::NamespaceMap' => 0,
|
||||
'URI::Template' => 0,
|
||||
'XML::RegExp' => 0,
|
||||
},
|
||||
resources => {
|
||||
license => 'http://dev.perl.org/licenses/',
|
||||
bugtracker => 'http://rt.cpan.org/NoAuth/Bugs.html?Dist=Data-HAL',
|
||||
repository => 'https://github.com/daxim/Data-HAL',
|
||||
},
|
||||
)->create_build_script;
|
||||
@ -0,0 +1,9 @@
|
||||
=encoding UTF-8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Changes - Revision history for Data-HAL
|
||||
|
||||
=head2 1.000 2013-10-15
|
||||
|
||||
Initial release.
|
||||
@ -0,0 +1,63 @@
|
||||
# Avoid version control files.
|
||||
\bRCS(?!\.pm)\b
|
||||
\bCVS(?!\.pm)\b
|
||||
\bSCCS(?!\.pm)\b
|
||||
,v$
|
||||
\B\.svn\b
|
||||
\B\.git\b
|
||||
\B\.gitignore\b
|
||||
\b_darcs\b
|
||||
\B\.cvsignore$
|
||||
|
||||
# Avoid VMS specific MakeMaker generated files
|
||||
\bDescrip.MMS$
|
||||
\bDESCRIP.MMS$
|
||||
\bdescrip.mms$
|
||||
|
||||
# Avoid Makemaker generated and utility files.
|
||||
\bMANIFEST\.bak
|
||||
\bMakefile$
|
||||
\bblib/
|
||||
\bMakeMaker-\d
|
||||
\bpm_to_blib\.ts$
|
||||
\bpm_to_blib$
|
||||
\bblibdirs\.ts$ # 6.18 through 6.25 generated this
|
||||
|
||||
# Avoid Module::Build generated and utility files.
|
||||
\bBuild$
|
||||
\b_build/
|
||||
\bBuild.bat$
|
||||
\bBuild.COM$
|
||||
\bBUILD.COM$
|
||||
\bbuild.com$
|
||||
^MANIFEST\.SKIP
|
||||
|
||||
# Avoid temp and backup files.
|
||||
~$
|
||||
\.old$
|
||||
\#$
|
||||
\b\.#
|
||||
\.bak$
|
||||
\.tmp$
|
||||
\.#
|
||||
\.rej$
|
||||
\..*\.sw.?$
|
||||
|
||||
# Avoid OS-specific files/dirs
|
||||
# Mac OSX metadata
|
||||
\B\.DS_Store
|
||||
# Mac OSX SMB mount metadata files
|
||||
\B\._
|
||||
|
||||
# Avoid Devel::Cover and Devel::CoverX::Covered files.
|
||||
\bcover_db\b
|
||||
\bcovered\b
|
||||
|
||||
# Avoid prove files
|
||||
\B\.prove$
|
||||
|
||||
# Avoid configuration metadata file
|
||||
^MYMETA\.
|
||||
|
||||
# Avoid archives of this distribution
|
||||
\bData-HAL-[\d\.\_]+
|
||||
@ -0,0 +1,26 @@
|
||||
=encoding UTF-8
|
||||
|
||||
Data-HAL version 1.000 is Copyright © 2013 Sipwise GmbH
|
||||
Author: Lars Dieckow <ldieckow@sipwise.com>
|
||||
|
||||
=head1 NAME
|
||||
|
||||
README - basic information for users prior to downloading
|
||||
|
||||
=head1 INSTALLATION
|
||||
|
||||
See L<How to install CPAN modules|http://www.cpan.org/modules/INSTALL.html>,
|
||||
L<perlfaq8/"How do I install a module from CPAN?">,
|
||||
L<perlfaq8/"How do I keep my own module/library directory?">.
|
||||
|
||||
=head1 LICENCE AND COPYRIGHT
|
||||
|
||||
Copyright © 2013 Sipwise GmbH C<< <daxim@cpan.org> >>
|
||||
|
||||
This library is free software; you can redistribute it and/or modify it
|
||||
under the same terms as Perl 5.18.0.
|
||||
|
||||
=head2 Disclaimer of warranty
|
||||
|
||||
This library is distributed in the hope that it will be useful, but without
|
||||
any warranty; without even the implied warranty of merchantability or fitness
|
||||
@ -0,0 +1,190 @@
|
||||
package Data::HAL::Link;
|
||||
use strictures;
|
||||
use boolean qw(false true);
|
||||
use Data::HAL::URI qw();
|
||||
use HTTP::Headers::Util qw(join_header_words);
|
||||
use JSON qw();
|
||||
use Log::Any qw($log);
|
||||
use MIME::Type qw();
|
||||
use Moo; # has
|
||||
use Safe::Isa qw($_can $_isa);
|
||||
use Types::Standard qw(InstanceOf Str);
|
||||
|
||||
our $VERSION = '1.000';
|
||||
|
||||
my $uri_from_str = sub {
|
||||
my ($val) = @_;
|
||||
return $val->$_isa('Data::HAL::URI') ? $val : Data::HAL::URI->new($val);
|
||||
};
|
||||
|
||||
my $boolean_from_perlbool = sub {
|
||||
my ($val) = @_;
|
||||
return $val ? true : false;
|
||||
};
|
||||
|
||||
has('relation', is => 'rw', isa => InstanceOf['Data::HAL::URI'], coerce => $uri_from_str, required => 1);
|
||||
has('href', is => 'rw', isa => InstanceOf['Data::HAL::URI'], coerce => $uri_from_str, required => 1);
|
||||
has('templated', is => 'rw', isa => InstanceOf['boolean'], coerce => $boolean_from_perlbool);
|
||||
has('type', is => 'rw', isa => InstanceOf['MIME::Type']);
|
||||
has('deprecation', is => 'rw', isa => InstanceOf['Data::HAL::URI'], coerce => $uri_from_str);
|
||||
has('name', is => 'rw', isa => Str);
|
||||
has('profile', is => 'rw', isa => InstanceOf['Data::HAL::URI'], coerce => $uri_from_str);
|
||||
has('title', is => 'rw', isa => Str);
|
||||
has('hreflang', is => 'rw', isa => Str);
|
||||
|
||||
sub BUILD {
|
||||
my ($self) = @_;
|
||||
if ($self->deprecation) {
|
||||
$log->warn(sprintf 'The link (relation: "%s", href: "%s") is deprecated, see <%s>',
|
||||
$self->relation->as_string, $self->href->as_string, $self->deprecation->as_string);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sub _to_nested {
|
||||
my ($self, $root) = @_;
|
||||
my $hal;
|
||||
for my $attr (map { $_->accessor } $self->meta->get_all_attributes) {
|
||||
my $val = $self->$attr;
|
||||
if (defined $val) {
|
||||
$hal->{$attr} = $val->$_can('as_string') ? $val->as_string($root) : $val;
|
||||
}
|
||||
}
|
||||
my $r = delete $hal->{relation};
|
||||
return($hal, $r);
|
||||
}
|
||||
|
||||
sub as_http_link_value {
|
||||
my ($self) = @_;
|
||||
return if 'curies' eq $self->relation->as_string;
|
||||
return join_header_words(
|
||||
'<'.$self->href->as_string.'>' => undef,
|
||||
rel => $self->relation->as_string,
|
||||
$self->hreflang ? (hreflang => $self->hreflang) : (),
|
||||
$self->title ? (title => $self->title) : (),
|
||||
$self->type ? (type => $self->type) : (),
|
||||
$self->name ? (name => $self->name) : (),
|
||||
$self->profile ? (profile => $self->profile->as_string) : (),
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=encoding UTF-8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Data::HAL::Link - Hypertext Application Language link
|
||||
|
||||
=head1 VERSION
|
||||
|
||||
This document describes Data::HAL::Link version 1.000
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
map { +{
|
||||
href => $_->href->uri->as_string,
|
||||
rel => $_->relation->uri->as_string
|
||||
} } @{ $resource->links }
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This section is completely quoted from the specification:
|
||||
|
||||
A Link Object represents a hyperlink from the containing resource to
|
||||
a URI.
|
||||
|
||||
=head1 INTERFACE
|
||||
|
||||
=head2 Composition
|
||||
|
||||
None.
|
||||
|
||||
=head2 Constructors
|
||||
|
||||
=head3 C<new>
|
||||
|
||||
When the L</deprecation> attribute is set, the constructor logs a L<Log::Any> warning:
|
||||
|
||||
C<< The link (relation: "%s", href: "%s") is deprecated, see <%s> >>
|
||||
|
||||
You can consume it with a L<Log::Any::Adapter> of your choice, e.g.
|
||||
|
||||
use Log::Any::Adapter 'Stderr';
|
||||
|
||||
Otherwise the constructor behaves like the default L<Moo> constructor. Returns a C<Data::HAL::Link> object.
|
||||
|
||||
=head2 Attributes
|
||||
|
||||
Perl strings are coerced to the L<Data::HAL::URI> type in the attributes L</relation>, L</href>, L</deprecation>,
|
||||
L</profile>.
|
||||
|
||||
=head3 C<relation>
|
||||
|
||||
Type L<Data::HAL::URI>, B<required>,
|
||||
L<link relation|http://tools.ietf.org/html/draft-kelly-json-hal#section-8.2>
|
||||
|
||||
=head3 C<href>
|
||||
|
||||
Type L<Data::HAL::URI>, B<required>,
|
||||
L<link target URI or URI template|http://tools.ietf.org/html/draft-kelly-json-hal#section-5.1>
|
||||
|
||||
=head3 C<templated>
|
||||
|
||||
Type L<boolean>,
|
||||
L<< whether C<href> is a URI template|http://tools.ietf.org/html/draft-kelly-json-hal#section-5.2 >>
|
||||
|
||||
Perl boolean values are coerced to the L<boolean> type.
|
||||
|
||||
=head3 C<type>
|
||||
|
||||
Type L<MIME::Type>,
|
||||
L<< media type of the C<href> resource|http://tools.ietf.org/html/draft-kelly-json-hal#section-5.3 >>
|
||||
|
||||
=head3 C<deprecation>
|
||||
|
||||
Type L<Data::HAL::URI>, if existing
|
||||
L<< indicates the link is deprecated|http://tools.ietf.org/html/draft-kelly-json-hal#section-5.4 >>
|
||||
|
||||
Setting the attribute triggers a log message in the constructor L</new>.
|
||||
|
||||
=head3 C<name>
|
||||
|
||||
Type C<Str>, L<< secondary key for selecting link objects which share the same relation
|
||||
type|http://tools.ietf.org/html/draft-kelly-json-hal#section-5.5 >>
|
||||
|
||||
=head3 C<profile>
|
||||
|
||||
Type L<Data::HAL::URI>,
|
||||
L<< RFC 6906 profile of the target resource|http://tools.ietf.org/html/draft-kelly-json-hal#section-5.6 >>
|
||||
|
||||
=head3 C<title>
|
||||
|
||||
Type C<Str>, L<< labels the link with a human-readable
|
||||
identifier|http://tools.ietf.org/html/draft-kelly-json-hal#section-5.7 >>
|
||||
|
||||
=head3 C<hreflang>
|
||||
|
||||
Type C<Str>,
|
||||
L<< indicates the language of the target resource|http://tools.ietf.org/html/draft-kelly-json-hal#section-5.8 >>
|
||||
|
||||
=head2 Methods
|
||||
|
||||
=head3 C<as_http_link_value>
|
||||
|
||||
Returns the link as a L<< RFC 5988 C<link-value>|http://tools.ietf.org/html/rfc5988#section-5 >> string, e.g.
|
||||
C<< </orders?page=2>;rel="next" >>.
|
||||
|
||||
=head2 Exports
|
||||
|
||||
None.
|
||||
|
||||
=head1 DIAGNOSTICS
|
||||
|
||||
See L</new> constructor.
|
||||
|
||||
=head1 CONFIGURATION AND ENVIRONMENT
|
||||
|
||||
Requires no configuration files or environment variables.
|
||||
@ -0,0 +1,109 @@
|
||||
package Data::HAL::URI;
|
||||
use strictures;
|
||||
use Moo; # has
|
||||
use Types::Standard qw(InstanceOf Str);
|
||||
use URI qw();
|
||||
|
||||
our $VERSION = '1.000';
|
||||
|
||||
has('_original', is => 'rw', isa => Str);
|
||||
# just records what was passed to the constructor, this is a work-around for
|
||||
# URI->new being a lossy operation
|
||||
has(
|
||||
'uri',
|
||||
is => 'ro',
|
||||
isa => InstanceOf['URI'],
|
||||
default => sub {
|
||||
my ($self) = @_;
|
||||
return URI->new($self->_original);
|
||||
},
|
||||
handles => [qw(abs as_iri canonical clone eq fragment implementor new_abs opaque path rel scheme secure
|
||||
STORABLE_freeze STORABLE_thaw)],
|
||||
lazy => 1,
|
||||
required => 1,
|
||||
);
|
||||
|
||||
sub BUILDARGS {
|
||||
my (undef, @arg) = @_;
|
||||
return 1 == @arg ? {_original => $arg[0]} : {@arg};
|
||||
}
|
||||
|
||||
sub as_string {
|
||||
my ($self, $root) = @_;
|
||||
if (
|
||||
$self->eq($self->_original)
|
||||
||
|
||||
$root && $root->_nsmap && $self->uri->eq($root->_nsmap->uri($self->_original)->as_string)
|
||||
) {
|
||||
return $self->_original;
|
||||
} else {
|
||||
return $self->uri->as_string;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=encoding UTF-8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Data::HAL::URI - URI wrapper
|
||||
|
||||
=head1 VERSION
|
||||
|
||||
This document describes Data::HAL::URI version 1.000
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $relation = $resource->relation->as_string;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This is a wrapper for L<URI> objects.
|
||||
|
||||
=head1 INTERFACE
|
||||
|
||||
=head2 Composition
|
||||
|
||||
None, but L<URI> methods are delegated through the L</uri> attribute.
|
||||
|
||||
=head2 Constructors
|
||||
|
||||
=head3 C<new>
|
||||
|
||||
my $u = Data::HAL::URI->new('http://example.com/something');
|
||||
|
||||
Takes a string argument, returns a C<Data::HAL::URI> object.
|
||||
|
||||
=head2 Attributes
|
||||
|
||||
=head3 C<uri>
|
||||
|
||||
Type C<URI>, B<required>, B<readonly>, can only be set from the L</new> constructor.
|
||||
|
||||
This attribute delegates all methods to L<URI> except L</as_string>.
|
||||
|
||||
=head2 Methods
|
||||
|
||||
=head3 C<as_string>
|
||||
|
||||
Returns the original argument to the constructor if still equal to the L</uri>, where equality also takes CURIE
|
||||
expansion into account, or otherwise the L</uri> as string.
|
||||
|
||||
The unaltered behaviour is still available through the L</uri> accessor, e.g.:
|
||||
|
||||
$resource->relation->uri->as_string
|
||||
|
||||
=head2 Exports
|
||||
|
||||
None.
|
||||
|
||||
=head1 DIAGNOSTICS
|
||||
|
||||
None.
|
||||
|
||||
=head1 CONFIGURATION AND ENVIRONMENT
|
||||
|
||||
Requires no configuration files or environment variables.
|
||||
@ -0,0 +1,27 @@
|
||||
package Data::HAL::URI::NamespaceMap;
|
||||
use strictures;
|
||||
use Data::HAL::URI qw();
|
||||
use Moo; # extends
|
||||
use URI::IRI qw();
|
||||
use URI::Template qw();
|
||||
use XML::RegExp qw();
|
||||
extends 'URI::NamespaceMap';
|
||||
|
||||
our $VERSION = '1.000';
|
||||
|
||||
sub uri {
|
||||
my ($self, $abbr) = @_;
|
||||
if (my ($prefix, $reference) = $abbr =~ m/\A ($XML::RegExp::NCName) : (.*) \z/msx) {
|
||||
if ($self->namespace_uri($prefix)) {
|
||||
return Data::HAL::URI->new(
|
||||
_original => $abbr,
|
||||
uri => URI::Template
|
||||
->new($self->namespace_uri($prefix)->as_string)
|
||||
->process(rel => URI::IRI->new($reference)->canonical->as_string),
|
||||
);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
@ -0,0 +1,13 @@
|
||||
use strictures;
|
||||
use Test::More import => [qw(diag done_testing use_ok)];
|
||||
|
||||
BEGIN {
|
||||
use_ok('Data::HAL');
|
||||
use_ok('Data::HAL::Link');
|
||||
use_ok('Data::HAL::URI');
|
||||
use_ok('Data::HAL::URI::NamespaceMap');
|
||||
}
|
||||
|
||||
diag('Testing Data::HAL ' . Data::HAL->VERSION);
|
||||
|
||||
done_testing;
|
||||
@ -0,0 +1,15 @@
|
||||
use strictures;
|
||||
use Test::More import => [qw(done_testing is ok)];
|
||||
use Data::HAL qw();
|
||||
use File::Slurp qw(read_file);
|
||||
|
||||
my $hal = Data::HAL->from_json(scalar read_file 't/exampleWithMultipleNestedSubresources.json');
|
||||
my @expanded_curies = map { $_->relation->uri->as_string } @{ $hal->links };
|
||||
ok grep { 'https://example.com/apidocs/ns/parent' eq $_ } @expanded_curies;
|
||||
ok grep { 'https://example.com/apidocs/ns/users' eq $_ } @expanded_curies;
|
||||
|
||||
is $hal->embedded->[0]->relation->uri->as_string, 'https://example.com/apidocs/ns/user';
|
||||
is $hal->embedded->[1]->relation->uri->as_string, 'https://example.com/apidocs/ns/user';
|
||||
is $hal->embedded->[0]->embedded->[0]->relation->uri->as_string, 'https://example.com/apidocs/phone/cell';
|
||||
|
||||
done_testing;
|
||||
@ -0,0 +1,14 @@
|
||||
use strictures;
|
||||
use Data::HAL qw();
|
||||
use File::Slurp qw(read_file);
|
||||
use Log::Any::Adapter qw();
|
||||
use Log::Any::Adapter::FileHandle qw();
|
||||
use Test::More import => [qw(done_testing is)];
|
||||
|
||||
my $log;
|
||||
open my $fh, '>', \$log;
|
||||
Log::Any::Adapter->set('FileHandle', fh => $fh);
|
||||
Data::HAL->from_json(scalar read_file 't/example3.json');
|
||||
is $log, qq{[warning] The link (relation: "widgets", href: "/widgets") }
|
||||
. "is deprecated, see <http://example.com/blog/2013/09/no-more-widgets>\n";
|
||||
done_testing;
|
||||
@ -0,0 +1,11 @@
|
||||
use strictures;
|
||||
use Test::More import => [qw(done_testing is)];
|
||||
use Data::HAL qw();
|
||||
use File::Slurp qw(read_file);
|
||||
|
||||
my $hal = Data::HAL->from_json(scalar read_file 't/example.json');
|
||||
my $json1 = $hal->as_json;
|
||||
my $json2 = $hal->as_json;
|
||||
is $json2, $json1;
|
||||
|
||||
done_testing;
|
||||
@ -0,0 +1,30 @@
|
||||
{
|
||||
"_links" : {
|
||||
"curies" : [ {
|
||||
"href" : "https://example.com/apidocs/ns/{rel}",
|
||||
"name" : "ns",
|
||||
"templated" : true
|
||||
}, {
|
||||
"href" : "https://example.com/apidocs/role/{rel}",
|
||||
"name" : "role",
|
||||
"templated" : true
|
||||
} ],
|
||||
"self" : {
|
||||
"href" : "https://example.com/api/customer/123456"
|
||||
},
|
||||
"ns:parent" : {
|
||||
"href" : "https://example.com/api/customer/1234",
|
||||
"name" : "bob",
|
||||
"title" : "The Parent",
|
||||
"hreflang" : "en"
|
||||
},
|
||||
"ns:users" : {
|
||||
"href" : "https://example.com/api/customer/123456?users"
|
||||
}
|
||||
},
|
||||
"age" : 33,
|
||||
"expired" : false,
|
||||
"id" : 123456,
|
||||
"name" : "Example Resource",
|
||||
"optional" : true
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
{
|
||||
"_links": {
|
||||
"self": { "href": "/orders" },
|
||||
"next": { "href": "/orders?page=2" },
|
||||
"find": {
|
||||
"href": "/orders{?id}",
|
||||
"templated": true
|
||||
},
|
||||
"admin": [{
|
||||
"href": "/admins/2",
|
||||
"title": "Fred"
|
||||
}, {
|
||||
"href": "/admins/5",
|
||||
"title": "Kate"
|
||||
}]
|
||||
},
|
||||
"currentlyProcessing": 14,
|
||||
"shippedToday": 20,
|
||||
"_embedded": {
|
||||
"order": [{
|
||||
"_links": {
|
||||
"self": { "href": "/orders/123" },
|
||||
"basket": { "href": "/baskets/98712" },
|
||||
"customer": { "href": "/customers/7809" }
|
||||
},
|
||||
"total": 30.00,
|
||||
"currency": "USD",
|
||||
"status": "shipped"
|
||||
}, {
|
||||
"_links": {
|
||||
"self": { "href": "/orders/124" },
|
||||
"basket": { "href": "/baskets/97213" },
|
||||
"customer": { "href": "/customers/12369" }
|
||||
},
|
||||
"total": 20.00,
|
||||
"currency": "USD",
|
||||
"status": "processing"
|
||||
}]
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
{
|
||||
"_links": {
|
||||
"self": { "href": "/orders" },
|
||||
"curies": [{
|
||||
"name": "acme",
|
||||
"href": "http://docs.acme.com/relations/{rel}",
|
||||
"templated": true
|
||||
}],
|
||||
"acme:widgets": { "href": "/widgets" }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
{
|
||||
"_links": {
|
||||
"self": { "href": "/orders" },
|
||||
"widgets": { "href": "/widgets", "deprecation": "http://example.com/blog/2013/09/no-more-widgets" }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
{
|
||||
"_links": {
|
||||
"self": { "href": "/orders" },
|
||||
"profile": { "href": "http://example.com/shop/documentation" }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
{
|
||||
"array" : [ "one", "two", "three" ],
|
||||
"name" : "Example Resource"
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
{
|
||||
"_links" : {
|
||||
"curies" : [ {
|
||||
"href" : "https://example.com/apidocs/ns/{rel}",
|
||||
"name" : "ns",
|
||||
"templated" : true
|
||||
}, {
|
||||
"href" : "https://example.com/apidocs/role/{rel}",
|
||||
"name" : "role",
|
||||
"templated" : true
|
||||
} ],
|
||||
"self" : {
|
||||
"href" : "https://example.com/api/customer/123456"
|
||||
},
|
||||
"ns:parent" : {
|
||||
"href" : "https://example.com/api/customer/1234",
|
||||
"name" : "bob",
|
||||
"title" : "The Parent",
|
||||
"hreflang" : "en"
|
||||
},
|
||||
"ns:users" : {
|
||||
"href" : "https://example.com/api/customer/123456?users"
|
||||
}
|
||||
},
|
||||
"age" : 33,
|
||||
"expired" : false,
|
||||
"id" : 123456,
|
||||
"name" : "Example Resource",
|
||||
"nullval" : "null",
|
||||
"optional" : true
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
{
|
||||
"_links" : {
|
||||
"curies" : [ {
|
||||
"href" : "https://example.com/apidocs/ns/{rel}",
|
||||
"name" : "ns",
|
||||
"templated" : true
|
||||
}, {
|
||||
"href" : "https://example.com/apidocs/phone/{rel}",
|
||||
"name" : "phone",
|
||||
"templated" : true
|
||||
}, {
|
||||
"href" : "https://example.com/apidocs/role/{rel}",
|
||||
"name" : "role",
|
||||
"templated" : true
|
||||
} ],
|
||||
"self" : {
|
||||
"href" : "https://example.com/api/customer/123456"
|
||||
},
|
||||
"ns:parent" : {
|
||||
"href" : "https://example.com/api/customer/1234",
|
||||
"name" : "bob",
|
||||
"title" : "The Parent",
|
||||
"hreflang" : "en"
|
||||
},
|
||||
"ns:users" : {
|
||||
"href" : "https://example.com/api/customer/123456?users"
|
||||
}
|
||||
},
|
||||
"_embedded" : {
|
||||
"ns:user" : [ {
|
||||
"_links" : {
|
||||
"self" : {
|
||||
"href" : "https://example.com/user/11"
|
||||
}
|
||||
},
|
||||
"age" : 32,
|
||||
"expired" : false,
|
||||
"id" : 11,
|
||||
"name" : "Example User",
|
||||
"optional" : true,
|
||||
"_embedded" : {
|
||||
"phone:cell" : {
|
||||
"_links" : {
|
||||
"self" : {
|
||||
"href" : "https://example.com/phone/1"
|
||||
}
|
||||
},
|
||||
"id" : 1,
|
||||
"number" : "555-666-7890"
|
||||
}
|
||||
}
|
||||
}, {
|
||||
"_links" : {
|
||||
"self" : {
|
||||
"href" : "https://example.com/user/12"
|
||||
}
|
||||
},
|
||||
"age" : 32,
|
||||
"expired" : false,
|
||||
"id" : 12,
|
||||
"name" : "Example User",
|
||||
"optional" : true
|
||||
} ]
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
{
|
||||
"_links" : {
|
||||
"curies" : [ {
|
||||
"href" : "https://example.com/apidocs/ns/{rel}",
|
||||
"name" : "ns",
|
||||
"templated" : true
|
||||
}, {
|
||||
"href" : "https://example.com/apidocs/role/{rel}",
|
||||
"name" : "role",
|
||||
"templated" : true
|
||||
} ],
|
||||
"self" : {
|
||||
"href" : "https://example.com/api/customer/123456"
|
||||
},
|
||||
"ns:parent" : {
|
||||
"href" : "https://example.com/api/customer/1234",
|
||||
"name" : "bob",
|
||||
"title" : "The Parent",
|
||||
"hreflang" : "en"
|
||||
},
|
||||
"ns:users" : {
|
||||
"href" : "https://example.com/api/customer/123456?users"
|
||||
}
|
||||
},
|
||||
"_embedded" : {
|
||||
"ns:user" : [ {
|
||||
"_links" : {
|
||||
"self" : {
|
||||
"href" : "https://example.com/user/11"
|
||||
}
|
||||
},
|
||||
"age" : 32,
|
||||
"expired" : false,
|
||||
"id" : 11,
|
||||
"name" : "Example User",
|
||||
"optional" : true
|
||||
}, {
|
||||
"_links" : {
|
||||
"self" : {
|
||||
"href" : "https://example.com/user/12"
|
||||
}
|
||||
},
|
||||
"age" : 32,
|
||||
"expired" : false,
|
||||
"id" : 12,
|
||||
"name" : "Example User",
|
||||
"optional" : true
|
||||
} ]
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
{
|
||||
"_links" : {
|
||||
"curies" : [ {
|
||||
"href" : "https://example.com/apidocs/ns/{rel}",
|
||||
"name" : "ns",
|
||||
"templated" : true
|
||||
}, {
|
||||
"href" : "https://example.com/apidocs/role/{rel}",
|
||||
"name" : "role",
|
||||
"templated" : true
|
||||
} ],
|
||||
"self" : {
|
||||
"href" : "https://example.com/api/customer/123456"
|
||||
},
|
||||
"ns:parent" : {
|
||||
"href" : "https://example.com/api/customer/1234",
|
||||
"name" : "bob",
|
||||
"title" : "The Parent",
|
||||
"hreflang" : "en"
|
||||
},
|
||||
"ns:users" : {
|
||||
"href" : "https://example.com/api/customer/123456?users"
|
||||
}
|
||||
},
|
||||
"age" : 33,
|
||||
"expired" : false,
|
||||
"id" : 123456,
|
||||
"name" : "Example Resource",
|
||||
"nullprop" : null,
|
||||
"optional" : true
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
{
|
||||
"_links" : {
|
||||
"curies" : [ {
|
||||
"href" : "https://example.com/apidocs/ns/{rel}",
|
||||
"name" : "ns",
|
||||
"templated" : true
|
||||
}, {
|
||||
"href" : "https://example.com/apidocs/role/{rel}",
|
||||
"name" : "role",
|
||||
"templated" : true
|
||||
} ],
|
||||
"self" : {
|
||||
"href" : "https://example.com/api/customer/123456"
|
||||
},
|
||||
"ns:parent" : {
|
||||
"href" : "https://example.com/api/customer/1234",
|
||||
"name" : "bob",
|
||||
"title" : "The Parent",
|
||||
"hreflang" : "en"
|
||||
},
|
||||
"ns:users" : {
|
||||
"href" : "https://example.com/api/customer/123456?users"
|
||||
}
|
||||
},
|
||||
"_embedded" : {
|
||||
"ns:user" : {
|
||||
"_links" : {
|
||||
"self" : {
|
||||
"href" : "https://example.com/user/11"
|
||||
}
|
||||
},
|
||||
"age" : 32,
|
||||
"expired" : false,
|
||||
"id" : 11,
|
||||
"name" : "Example User",
|
||||
"optional" : true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
{
|
||||
"_links" : {
|
||||
"curies" : [ {
|
||||
"href" : "https://example.com/apidocs/ns/{rel}",
|
||||
"name" : "ns",
|
||||
"templated" : true
|
||||
}, {
|
||||
"href" : "https://example.com/apidocs/role/{rel}",
|
||||
"name" : "role",
|
||||
"templated" : true
|
||||
} ],
|
||||
"self" : {
|
||||
"href" : "https://example.com/api/customer/123456"
|
||||
},
|
||||
"ns:parent" : {
|
||||
"href" : "https://example.com/api/customer/1234",
|
||||
"name" : "bob",
|
||||
"title" : "The Parent",
|
||||
"hreflang" : "en"
|
||||
},
|
||||
"ns:users" : {
|
||||
"href" : "https://example.com/api/customer/123456?users"
|
||||
}
|
||||
},
|
||||
"_embedded" : {
|
||||
"ns:user" : {
|
||||
"_links" : {
|
||||
"self" : {
|
||||
"href" : "https://example.com/user/11"
|
||||
},
|
||||
"role:admin" : {
|
||||
"href" : "https://example.com/user/11"
|
||||
}
|
||||
},
|
||||
"age" : 32,
|
||||
"expired" : false,
|
||||
"id" : 11,
|
||||
"name" : "Example User",
|
||||
"optional" : true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
{
|
||||
"_links" : {
|
||||
"curies" : [ {
|
||||
"href" : "https://example.com/apidocs/ns/{rel}",
|
||||
"name" : "ns",
|
||||
"templated" : true
|
||||
}, {
|
||||
"href" : "https://example.com/apidocs/role/{rel}",
|
||||
"name" : "role",
|
||||
"templated" : true
|
||||
} ],
|
||||
"self" : {
|
||||
"href" : "https://example.com/api/customer"
|
||||
},
|
||||
"ns:parent" : {
|
||||
"href" : "https://example.com/api/customer/1234",
|
||||
"name" : "bob",
|
||||
"title" : "The Parent",
|
||||
"hreflang" : "en"
|
||||
},
|
||||
"ns:query" : {
|
||||
"href" : "https://example.com/api/customer/search{?queryParam}",
|
||||
"templated" : true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
{
|
||||
"_links" : {
|
||||
"self" : {
|
||||
"href" : "https://example.com/api/customer/123456"
|
||||
}
|
||||
},
|
||||
"_name" : "Example Resource"
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
{
|
||||
"name" : "Example Resource"
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
use strictures;
|
||||
use Test::More import => [qw(done_testing is_deeply)];
|
||||
use Data::HAL qw();
|
||||
|
||||
my $hal = Data::HAL->new(
|
||||
links => [
|
||||
Data::HAL::Link->new(relation => 'self', href => 'http://example.com/a-resource'),
|
||||
Data::HAL::Link->new(relation => 'profile', href => 'http://example.com/docs'),
|
||||
Data::HAL::Link->new(relation => 'curies', name => 'example', href => 'http://example.com/rel#{rel}', templated => 1),
|
||||
Data::HAL::Link->new(relation => 'example:foo', href => '/a-foo-thing'),
|
||||
Data::HAL::Link->new(relation => 'example:bar', href => '/some-bar'),
|
||||
],
|
||||
);
|
||||
|
||||
is_deeply [$hal->http_headers], [
|
||||
'Content-Type' => 'application/hal+json; profile="http://example.com/docs"',
|
||||
'Link' => '<http://example.com/a-resource>; rel=self',
|
||||
'Link' => '<http://example.com/docs>; rel=profile',
|
||||
'Link' => '</a-foo-thing>; rel="http://example.com/rel#foo"',
|
||||
'Link' => '</some-bar>; rel="http://example.com/rel#bar"',
|
||||
];
|
||||
|
||||
done_testing;
|
||||
@ -0,0 +1,7 @@
|
||||
use strictures;
|
||||
use Data::HAL qw();
|
||||
use Test::Fatal qw(exception);
|
||||
use Test::More import => [qw(done_testing isa_ok)];
|
||||
|
||||
isa_ok exception { Data::HAL->from_json('[]') }, 'failure::Data::HAL::InvalidJSON';
|
||||
done_testing;
|
||||
@ -0,0 +1,8 @@
|
||||
use strictures;
|
||||
use Data::HAL qw();
|
||||
use File::Slurp qw(read_file);
|
||||
use Test::More import => [qw(done_testing is)];
|
||||
|
||||
is {Data::HAL->from_json(scalar read_file 't/example4.json')->http_headers}->{'Content-Type'},
|
||||
'application/hal+json; profile="http://example.com/shop/documentation"';
|
||||
done_testing;
|
||||
@ -0,0 +1,27 @@
|
||||
use strictures;
|
||||
use Test::More import => [qw(done_testing is_deeply)];
|
||||
use Data::HAL qw();
|
||||
use Data::Visitor::Callback qw();
|
||||
use File::Slurp qw(read_file);
|
||||
use JSON qw();
|
||||
|
||||
sub round_trip {
|
||||
my ($file_name, $json_in) = @_;
|
||||
my $json_out = Data::HAL->from_json($json_in)->as_json;
|
||||
my $in_unwrapped = Data::Visitor::Callback->new(
|
||||
array => sub {
|
||||
if (1 == @{ $_ }) {
|
||||
my ($inner) = @{ $_ };
|
||||
return $inner;
|
||||
}
|
||||
return $_;
|
||||
}
|
||||
)->visit(JSON::from_json($json_in));
|
||||
is_deeply JSON::from_json($json_out), $in_unwrapped, $file_name;
|
||||
}
|
||||
|
||||
for my $file_name (sort glob 't/*.json') {
|
||||
round_trip($file_name, scalar read_file $file_name);
|
||||
}
|
||||
|
||||
done_testing;
|
||||
Loading…
Reference in new issue