Skip to content

Commit c9ae7ce

Browse files
committed
Added support for _curries.
1 parent af7de24 commit c9ae7ce

File tree

9 files changed

+154
-14
lines changed

9 files changed

+154
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* [#58](https://github.com/codegram/hyperclient/issues/58): Automatically follow redirects (by [@dblock](https://github.com/dblock)).
88
* [#63](https://github.com/codegram/hyperclient/pull/63): You can omit the navigational elements, `api.links.products` is now equivalent to `api.products` (by @dblock).
99
* Implemented Rubocop, Ruby-style linter (by @dblock).
10+
* [#64](https://github.com/codegram/hyperclient/issues/64): Added support for curies (by [@dblock](https://github.com/dblock)).
1011

1112
* bug fixes
1213
* Nothing.

lib/hyperclient/curie.rb

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
require 'hyperclient/resource'
2+
3+
module Hyperclient
4+
# Internal: Curies are named tokens that you can define in the document and use
5+
# to express curie relation URIs in a friendlier, more compact fashion.
6+
#
7+
class Curie
8+
# Public: Initializes a new Curie.
9+
#
10+
# curie - The String with the URI of the curie.
11+
# entry_point - The EntryPoint object to inject the cofnigutation.
12+
def initialize(curie_hash, entry_point)
13+
@curie_hash = curie_hash
14+
@entry_point = entry_point
15+
end
16+
17+
# Public: Indicates if the curie is an URITemplate or a regular URI.
18+
#
19+
# Returns true if it is templated.
20+
# Returns false if it not templated.
21+
def templated?
22+
!!@curie_hash['templated']
23+
end
24+
25+
# Public: Returns the name property of the Curie
26+
def name
27+
@curie_hash['name']
28+
end
29+
30+
# Public: Returns the href property of the Curie
31+
def href
32+
@curie_hash['href']
33+
end
34+
35+
def inspect
36+
"#<#{self.class.name} #{@curie_hash}>"
37+
end
38+
39+
# Public: Expands the Curie when is templated with the given variables.
40+
#
41+
# rel - The rel to expand.
42+
#
43+
# Returns a new expanded url.
44+
def expand(rel)
45+
return rel unless rel && templated?
46+
href.gsub('{rel}', rel) if href
47+
end
48+
end
49+
end

lib/hyperclient/link_collection.rb

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'hyperclient/collection'
22
require 'hyperclient/link'
3+
require 'hyperclient/curie'
34

45
module Hyperclient
56
# Public: A wrapper class to easily acces the links in a Resource.
@@ -12,13 +13,19 @@ module Hyperclient
1213
class LinkCollection < Collection
1314
# Public: Initializes a LinkCollection.
1415
#
15-
# collection - The Hash with the links.
16+
# collection - The Hash with the links.
17+
# curies - Link curies.
1618
# entry_point - The EntryPoint object to inject the configuration.
17-
def initialize(collection, entry_point)
19+
def initialize(collection, curies, entry_point)
1820
fail "Invalid response for LinkCollection. The response was: #{collection.inspect}" if collection && !collection.respond_to?(:collect)
1921

22+
@curies = (curies || {}).reduce({}) do |hash, curie_hash|
23+
curie = build_curie(curie_hash, entry_point)
24+
hash.update(curie.name => curie)
25+
end
26+
2027
@collection = (collection || {}).reduce({}) do |hash, (name, link)|
21-
hash.update(name => build_link(name, link, entry_point))
28+
hash.update(name => build_link(name, link, @curies, entry_point))
2229
end
2330
end
2431

@@ -27,16 +34,33 @@ def initialize(collection, entry_point)
2734
# Internal: Creates links from the response hash.
2835
#
2936
# link_or_links - A Hash or an Array of hashes with the links to build.
30-
# entry_point - The EntryPoint object to inject the configuration.
37+
# entry_point - The EntryPoint object to inject the configuration.
38+
# curies - Optional curies for templated links.
3139
#
3240
# Returns a Link or an array of Links when given an Array.
33-
def build_link(name, link_or_links, entry_point)
41+
def build_link(name, link_or_links, curies, entry_point)
3442
return unless link_or_links
35-
return Link.new(name, link_or_links, entry_point) unless link_or_links.respond_to?(:to_ary)
36-
37-
link_or_links.map do |link|
38-
build_link(name, link, entry_point)
43+
if link_or_links.respond_to?(:to_ary)
44+
link_or_links.map do |link|
45+
build_link(name, link, curies, entry_point)
46+
end
47+
elsif (curie_parts = /(?<ns>[^:]+):(?<short_name>.+)/.match(name))
48+
curie = curies[curie_parts[:ns]]
49+
link_or_links['href'] = curie.expand(link_or_links['href']) if curie
50+
Link.new(name, link_or_links, entry_point)
51+
else
52+
Link.new(name, link_or_links, entry_point)
3953
end
4054
end
55+
56+
# Internal: Creates a curie from the response hash.
57+
#
58+
# curie_hash - A Hash with the curie.
59+
# entry_point - The EntryPoint object to inject the configuration.
60+
#
61+
# Returns a Link or an array of Links when given an Array.
62+
def build_curie(curie_hash, entry_point)
63+
Curie.new(curie_hash, entry_point)
64+
end
4165
end
4266
end

lib/hyperclient/resource.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class Resource
3333
# entry_point - The EntryPoint object to inject the configutation.
3434
def initialize(representation, entry_point, response = nil)
3535
representation = representation ? representation.dup : {}
36-
@_links = LinkCollection.new(representation['_links'], entry_point)
36+
@_links = LinkCollection.new(representation['_links'], representation['_curies'], entry_point)
3737
@_embedded = ResourceCollection.new(representation['_embedded'], entry_point)
3838
@_attributes = Attributes.new(representation)
3939
@_entry_point = entry_point

test/fixtures/element.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
{
2+
"_curies" : [
3+
{
4+
"name": "image",
5+
"href": "/images/{rel}",
6+
"templated": true
7+
}
8+
],
29
"_links": {
310
"self": {
411
"href": "/productions/1"
@@ -15,7 +22,11 @@
1522
"href": "/gizmos/2"
1623
}
1724
],
18-
"null_link": null
25+
"null_link": null,
26+
"image:thumbnail": {
27+
"href": "thumbnails/{version}.jpg",
28+
"templated": true
29+
}
1930
},
2031
"title": "Real World ASP.NET MVC3",
2132
"description": "In this advanced, somewhat-opinionated production you'll get your very own startup off the ground using ASP.NET MVC 3...",

test/hyperclient/collection_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ module Hyperclient
3232
name
3333
end
3434

35-
names.must_equal %w(_links title description permitted _hidden_attribute _embedded)
35+
names.must_equal %w(_curies _links title description permitted _hidden_attribute _embedded)
3636
end
3737

3838
describe '#to_hash' do

test/hyperclient/curie_test.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
require_relative '../test_helper'
2+
require 'hyperclient/curie'
3+
require 'hyperclient/entry_point'
4+
5+
module Hyperclient
6+
describe Curie do
7+
let(:entry_point) do
8+
EntryPoint.new('http://api.example.org/')
9+
end
10+
11+
describe 'templated?' do
12+
it 'returns true if the curie is templated' do
13+
curie = Curie.new({ 'name' => 'image', 'templated' => true }, entry_point)
14+
15+
curie.templated?.must_equal true
16+
end
17+
18+
it 'returns false if the curie is not templated' do
19+
curie = Curie.new({ 'name' => 'image' }, entry_point)
20+
21+
curie.templated?.must_equal false
22+
end
23+
end
24+
25+
let(:curie) do
26+
Curie.new({ 'name' => 'image', 'href' => '/images/{?rel}', 'templated' => true }, entry_point)
27+
end
28+
describe '_name' do
29+
it 'returns curie name' do
30+
curie.name.must_equal 'image'
31+
end
32+
end
33+
end
34+
end

test/hyperclient/link_collection_test.rb

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ module Hyperclient
1010
end
1111

1212
let(:links) do
13-
LinkCollection.new(representation['_links'], entry_point)
13+
LinkCollection.new(representation['_links'], representation['_curies'], entry_point)
1414
end
1515

1616
it 'is a collection' do
@@ -30,6 +30,27 @@ module Hyperclient
3030
links['gizmos'].must_be_kind_of Array
3131
end
3232

33+
describe 'plain link' do
34+
let(:plain_link) { links.self }
35+
it 'must be correct' do
36+
plain_link._url.must_equal '/productions/1'
37+
end
38+
end
39+
40+
describe 'templated link' do
41+
let(:templated_link) { links.filter }
42+
it 'must expand' do
43+
templated_link._expand(filter: 'gizmos')._url.must_equal '/productions/1?categories=gizmos'
44+
end
45+
end
46+
47+
describe 'curied link' do
48+
let(:curied_link) { links['image:thumbnail'] }
49+
it 'must expand' do
50+
curied_link._expand(version: 'small')._url.must_equal '/images/thumbnails/small.jpg'
51+
end
52+
end
53+
3354
describe 'array of links' do
3455
let(:gizmos) { links.gizmos }
3556

test/hyperclient/resource_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module Hyperclient
77

88
describe 'initialize' do
99
it 'initializes its links' do
10-
LinkCollection.expects(:new).with({ 'self' => { 'href' => '/orders/523' } }, entry_point)
10+
LinkCollection.expects(:new).with({ 'self' => { 'href' => '/orders/523' } }, nil, entry_point)
1111

1212
Resource.new({ '_links' => { 'self' => { 'href' => '/orders/523' } } }, entry_point)
1313
end

0 commit comments

Comments
 (0)