Skip to content

Added _curies support. #64

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 30, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* [#58](https://github.com/codegram/hyperclient/issues/58): Automatically follow redirects (by [@dblock](https://github.com/dblock)).
* [#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).
* Implemented Rubocop, Ruby-style linter (by @dblock).
* [#64](https://github.com/codegram/hyperclient/issues/64): Added support for curies (by [@dblock](https://github.com/dblock)).

* bug fixes
* Nothing.
Expand Down
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,22 @@ end

Actually, you can call any [Enumerable][enumerable] method :D

If a Resource doesn't have friendly name you can always access it as a Hash:
If a resource doesn't have friendly name you can always access it as a hash:

```ruby
api._links['http://myapi.org/rels/post_categories']
```

### Curies

Curies are named tokens that you can define in the document and use to express curie relation URIs in a friendlier, more compact fashion. Hyperclient handles curies automatically and resolves them into full links.

Access and expand curied links like any other link:

```ruby
api._links['image:thumbnail'].expand(version: 'small')
```

### Embedded resources

Accessing embedded resources is similar to accessing links:
Expand Down Expand Up @@ -178,17 +188,9 @@ api.connection.use :http_cache

There's also a PHP library named [HyperClient](https://github.com/FoxyCart/HyperClient), if that's what you were looking for :)

## TODO

* Resource permissions: Using the `Allow` header Hyperclient should be able to
restrict the allowed method on a given `Resource`.
* Curie syntax support for links (see http://tools.ietf.org/html/draft-kelly-json-hal-03#section-8.2)
* Profile support for links


## Contributing

* [List of hyperclient contributors][contributors]
* [List of hyperclient contributors][contributors].

* Fork the project.
* Make your feature addition or bug fix.
Expand Down
49 changes: 49 additions & 0 deletions lib/hyperclient/curie.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
require 'hyperclient/resource'

module Hyperclient
# Internal: Curies are named tokens that you can define in the document and use
# to express curie relation URIs in a friendlier, more compact fashion.
#
class Curie
# Public: Initializes a new Curie.
#
# curie - The String with the URI of the curie.
# entry_point - The EntryPoint object to inject the cofnigutation.
def initialize(curie_hash, entry_point)
@curie_hash = curie_hash
@entry_point = entry_point
end

# Public: Indicates if the curie is an URITemplate or a regular URI.
#
# Returns true if it is templated.
# Returns false if it not templated.
def templated?
!!@curie_hash['templated']
end

# Public: Returns the name property of the Curie
def name
@curie_hash['name']
end

# Public: Returns the href property of the Curie
def href
@curie_hash['href']
end

def inspect
"#<#{self.class.name} #{@curie_hash}>"
end

# Public: Expands the Curie when is templated with the given variables.
#
# rel - The rel to expand.
#
# Returns a new expanded url.
def expand(rel)
return rel unless rel && templated?
href.gsub('{rel}', rel) if href
end
end
end
42 changes: 33 additions & 9 deletions lib/hyperclient/link_collection.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'hyperclient/collection'
require 'hyperclient/link'
require 'hyperclient/curie'

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

@curies = (curies || {}).reduce({}) do |hash, curie_hash|
curie = build_curie(curie_hash, entry_point)
hash.update(curie.name => curie)
end

@collection = (collection || {}).reduce({}) do |hash, (name, link)|
hash.update(name => build_link(name, link, entry_point))
hash.update(name => build_link(name, link, @curies, entry_point))
end
end

Expand All @@ -27,16 +34,33 @@ def initialize(collection, entry_point)
# Internal: Creates links from the response hash.
#
# link_or_links - A Hash or an Array of hashes with the links to build.
# entry_point - The EntryPoint object to inject the configuration.
# entry_point - The EntryPoint object to inject the configuration.
# curies - Optional curies for templated links.
#
# Returns a Link or an array of Links when given an Array.
def build_link(name, link_or_links, entry_point)
def build_link(name, link_or_links, curies, entry_point)
return unless link_or_links
return Link.new(name, link_or_links, entry_point) unless link_or_links.respond_to?(:to_ary)

link_or_links.map do |link|
build_link(name, link, entry_point)
if link_or_links.respond_to?(:to_ary)
link_or_links.map do |link|
build_link(name, link, curies, entry_point)
end
elsif (curie_parts = /(?<ns>[^:]+):(?<short_name>.+)/.match(name))
curie = curies[curie_parts[:ns]]
link_or_links['href'] = curie.expand(link_or_links['href']) if curie
Link.new(name, link_or_links, entry_point)
else
Link.new(name, link_or_links, entry_point)
end
end

# Internal: Creates a curie from the response hash.
#
# curie_hash - A Hash with the curie.
# entry_point - The EntryPoint object to inject the configuration.
#
# Returns a Link or an array of Links when given an Array.
def build_curie(curie_hash, entry_point)
Curie.new(curie_hash, entry_point)
end
end
end
2 changes: 1 addition & 1 deletion lib/hyperclient/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class Resource
# entry_point - The EntryPoint object to inject the configutation.
def initialize(representation, entry_point, response = nil)
representation = representation ? representation.dup : {}
@_links = LinkCollection.new(representation['_links'], entry_point)
@_links = LinkCollection.new(representation['_links'], representation['_curies'], entry_point)
@_embedded = ResourceCollection.new(representation['_embedded'], entry_point)
@_attributes = Attributes.new(representation)
@_entry_point = entry_point
Expand Down
13 changes: 12 additions & 1 deletion test/fixtures/element.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
{
"_curies" : [
{
"name": "image",
"href": "/images/{rel}",
"templated": true
}
],
"_links": {
"self": {
"href": "/productions/1"
Expand All @@ -15,7 +22,11 @@
"href": "/gizmos/2"
}
],
"null_link": null
"null_link": null,
"image:thumbnail": {
"href": "thumbnails/{version}.jpg",
"templated": true
}
},
"title": "Real World ASP.NET MVC3",
"description": "In this advanced, somewhat-opinionated production you'll get your very own startup off the ground using ASP.NET MVC 3...",
Expand Down
2 changes: 1 addition & 1 deletion test/hyperclient/collection_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ module Hyperclient
name
end

names.must_equal %w(_links title description permitted _hidden_attribute _embedded)
names.must_equal %w(_curies _links title description permitted _hidden_attribute _embedded)
end

describe '#to_hash' do
Expand Down
34 changes: 34 additions & 0 deletions test/hyperclient/curie_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
require_relative '../test_helper'
require 'hyperclient/curie'
require 'hyperclient/entry_point'

module Hyperclient
describe Curie do
let(:entry_point) do
EntryPoint.new('http://api.example.org/')
end

describe 'templated?' do
it 'returns true if the curie is templated' do
curie = Curie.new({ 'name' => 'image', 'templated' => true }, entry_point)

curie.templated?.must_equal true
end

it 'returns false if the curie is not templated' do
curie = Curie.new({ 'name' => 'image' }, entry_point)

curie.templated?.must_equal false
end
end

let(:curie) do
Curie.new({ 'name' => 'image', 'href' => '/images/{?rel}', 'templated' => true }, entry_point)
end
describe '_name' do
it 'returns curie name' do
curie.name.must_equal 'image'
end
end
end
end
23 changes: 22 additions & 1 deletion test/hyperclient/link_collection_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module Hyperclient
end

let(:links) do
LinkCollection.new(representation['_links'], entry_point)
LinkCollection.new(representation['_links'], representation['_curies'], entry_point)
end

it 'is a collection' do
Expand All @@ -30,6 +30,27 @@ module Hyperclient
links['gizmos'].must_be_kind_of Array
end

describe 'plain link' do
let(:plain_link) { links.self }
it 'must be correct' do
plain_link._url.must_equal '/productions/1'
end
end

describe 'templated link' do
let(:templated_link) { links.filter }
it 'must expand' do
templated_link._expand(filter: 'gizmos')._url.must_equal '/productions/1?categories=gizmos'
end
end

describe 'curied link' do
let(:curied_link) { links['image:thumbnail'] }
it 'must expand' do
curied_link._expand(version: 'small')._url.must_equal '/images/thumbnails/small.jpg'
end
end

describe 'array of links' do
let(:gizmos) { links.gizmos }

Expand Down
2 changes: 1 addition & 1 deletion test/hyperclient/resource_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module Hyperclient

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

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