hyperclient 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rvmrc +1 -1
- data/.travis.yml +2 -0
- data/.yardopts +8 -0
- data/Gemfile +9 -1
- data/Guardfile +6 -0
- data/LICENSE +1 -1
- data/MIT-LICENSE +20 -0
- data/Rakefile +27 -1
- data/Readme.md +145 -0
- data/examples/hal_shop.rb +59 -0
- data/hyperclient.gemspec +11 -6
- data/lib/hyperclient.rb +72 -3
- data/lib/hyperclient/discoverer.rb +63 -0
- data/lib/hyperclient/http.rb +90 -0
- data/lib/hyperclient/resource.rb +82 -0
- data/lib/hyperclient/response.rb +42 -0
- data/lib/hyperclient/version.rb +1 -1
- data/test/fixtures/collection.json +34 -0
- data/test/fixtures/element.json +65 -0
- data/test/fixtures/root.json +6 -0
- data/test/hyperclient/discoverer_test.rb +76 -0
- data/test/hyperclient/http_test.rb +96 -0
- data/test/hyperclient/resource_test.rb +80 -0
- data/test/hyperclient/response_test.rb +50 -0
- data/test/hyperclient_test.rb +63 -0
- data/test/test_helper.rb +7 -0
- metadata +110 -12
- data/README.md +0 -71
@@ -0,0 +1,80 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
require 'hyperclient/resource'
|
3
|
+
|
4
|
+
module Hyperclient
|
5
|
+
describe Resource do
|
6
|
+
let(:response) do
|
7
|
+
File.read('test/fixtures/element.json')
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:parsed_response) do
|
11
|
+
JSON.parse(response)
|
12
|
+
end
|
13
|
+
|
14
|
+
before do
|
15
|
+
Resource.entry_point = 'http://api.example.org'
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'url' do
|
19
|
+
it 'merges the resource url with the entry point' do
|
20
|
+
resource = Resource.new('/path/to/resource')
|
21
|
+
resource.url.to_s.must_equal 'http://api.example.org/path/to/resource'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns the given url if it cannot merge it' do
|
25
|
+
resource = Resource.new('/search={terms}')
|
26
|
+
resource.url.to_s.must_equal '/search={terms}'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'initialize' do
|
31
|
+
before do
|
32
|
+
stub_request(:get, 'http://api.example.org')
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'initializes the response when one is given' do
|
36
|
+
resource = Resource.new('/', {response: JSON.parse(response)})
|
37
|
+
|
38
|
+
assert_not_requested(:get, 'http://api.example.org/')
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'updates the resource URL if the response has one' do
|
42
|
+
resource = Resource.new('/', {response: JSON.parse(response)})
|
43
|
+
|
44
|
+
resource.url.must_include '/productions/1'
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'does no update the resource URL if the response does not have one' do
|
48
|
+
resource = Resource.new('/', {})
|
49
|
+
|
50
|
+
resource.url.wont_include '/productions/1'
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'sets the resource name' do
|
54
|
+
resource = Resource.new('/', {name: 'posts'})
|
55
|
+
|
56
|
+
resource.name.must_equal 'posts'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'reload' do
|
61
|
+
before do
|
62
|
+
stub_request(:get, "http://api.example.org/productions/1").
|
63
|
+
to_return(:status => 200, :body => response, headers: {content_type: 'application/json'})
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'retrives itself from the API' do
|
67
|
+
resource = Resource.new('/productions/1')
|
68
|
+
resource.reload
|
69
|
+
|
70
|
+
assert_requested(:get, 'http://api.example.org/productions/1', times: 1)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'returns itself' do
|
74
|
+
resource = Resource.new('/productions/1')
|
75
|
+
|
76
|
+
resource.reload.must_equal resource
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
require 'hyperclient/response'
|
3
|
+
|
4
|
+
module Hyperclient
|
5
|
+
describe Response do
|
6
|
+
let (:response) do
|
7
|
+
Response.new JSON.parse(File.read('test/fixtures/element.json'))
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'attributes' do
|
11
|
+
it 'returns the resource attributes' do
|
12
|
+
response.attributes['title'].must_equal 'Real World ASP.NET MVC3'
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'does not include _links as attributes' do
|
16
|
+
response.attributes.wont_include '_links'
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'does not include _embedded as attributes' do
|
20
|
+
response.attributes.wont_include '_embedded'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'links' do
|
25
|
+
it 'returns resources included in the _links section' do
|
26
|
+
response.links.filter.must_be_kind_of Resource
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'resources' do
|
31
|
+
it 'returns resources included in the _embedded section' do
|
32
|
+
response.resources.author.must_be_kind_of Resource
|
33
|
+
response.resources.episodes.first.must_be_kind_of Resource
|
34
|
+
response.resources.episodes.last.must_be_kind_of Resource
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'url' do
|
39
|
+
it 'returns the url of the resource grabbed from the response' do
|
40
|
+
response.url.must_equal '/productions/1'
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'returns nil when the response does not include the resource url' do
|
44
|
+
response = Response.new({_links: {media: {href: '/media/1'}}})
|
45
|
+
|
46
|
+
response.url.must_equal nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
require 'hyperclient'
|
3
|
+
|
4
|
+
describe Hyperclient do
|
5
|
+
let(:api) do
|
6
|
+
Class.new do
|
7
|
+
include Hyperclient
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'entry point' do
|
12
|
+
it 'sets the entry point for Hyperclient::Resource' do
|
13
|
+
api.entry_point 'http://my.api.org'
|
14
|
+
|
15
|
+
Hyperclient::Resource.new('/').url.must_include 'http://my.api.org'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'entry' do
|
20
|
+
before do
|
21
|
+
api.entry_point 'http://my.api.org'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'initializes a Resource at the entry point' do
|
25
|
+
api.new.entry.url.must_equal 'http://my.api.org/'
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'sets the Resource name' do
|
29
|
+
api.new.name.must_equal 'Entry point'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'auth' do
|
34
|
+
it 'sets authentication type' do
|
35
|
+
api.auth(:digest, nil, nil)
|
36
|
+
|
37
|
+
api.http_options[:http][:auth][:type].must_equal :digest
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'sets the authentication credentials' do
|
41
|
+
api.auth(:digest, 'user', 'secret')
|
42
|
+
|
43
|
+
api.http_options[:http][:auth][:credentials].must_include 'user'
|
44
|
+
api.http_options[:http][:auth][:credentials].must_include 'secret'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'method missing' do
|
49
|
+
class Hyperclient::Resource
|
50
|
+
def foo
|
51
|
+
'foo'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'delegates undefined methods to the API when they exist' do
|
56
|
+
api.new.foo.must_equal 'foo'
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'raises an error when the method does not exist in the API' do
|
60
|
+
lambda { api.new.this_method_does_not_exist }.must_raise(NoMethodError)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
CHANGED
@@ -1,32 +1,115 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hyperclient
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
|
-
-
|
8
|
+
- Oriol Gual
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
13
|
-
dependencies:
|
14
|
-
|
12
|
+
date: 2012-05-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: httparty
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: minitest
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: turn
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: webmock
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: HyperClient is a Ruby Hypermedia API client.
|
15
79
|
email:
|
16
|
-
-
|
80
|
+
- oriol.gual@gmail.com
|
17
81
|
executables: []
|
18
82
|
extensions: []
|
19
83
|
extra_rdoc_files: []
|
20
84
|
files:
|
85
|
+
- .gitignore
|
21
86
|
- .rvmrc
|
87
|
+
- .travis.yml
|
88
|
+
- .yardopts
|
22
89
|
- Gemfile
|
90
|
+
- Guardfile
|
23
91
|
- LICENSE
|
24
|
-
-
|
92
|
+
- MIT-LICENSE
|
25
93
|
- Rakefile
|
94
|
+
- Readme.md
|
95
|
+
- examples/hal_shop.rb
|
26
96
|
- hyperclient.gemspec
|
27
97
|
- lib/hyperclient.rb
|
98
|
+
- lib/hyperclient/discoverer.rb
|
99
|
+
- lib/hyperclient/http.rb
|
100
|
+
- lib/hyperclient/resource.rb
|
101
|
+
- lib/hyperclient/response.rb
|
28
102
|
- lib/hyperclient/version.rb
|
29
|
-
|
103
|
+
- test/fixtures/collection.json
|
104
|
+
- test/fixtures/element.json
|
105
|
+
- test/fixtures/root.json
|
106
|
+
- test/hyperclient/discoverer_test.rb
|
107
|
+
- test/hyperclient/http_test.rb
|
108
|
+
- test/hyperclient/resource_test.rb
|
109
|
+
- test/hyperclient/response_test.rb
|
110
|
+
- test/hyperclient_test.rb
|
111
|
+
- test/test_helper.rb
|
112
|
+
homepage: http://codegram.github.com/hyperclient/
|
30
113
|
licenses: []
|
31
114
|
post_install_message:
|
32
115
|
rdoc_options: []
|
@@ -38,17 +121,32 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
38
121
|
- - ! '>='
|
39
122
|
- !ruby/object:Gem::Version
|
40
123
|
version: '0'
|
124
|
+
segments:
|
125
|
+
- 0
|
126
|
+
hash: -2437981689318086472
|
41
127
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
42
128
|
none: false
|
43
129
|
requirements:
|
44
130
|
- - ! '>='
|
45
131
|
- !ruby/object:Gem::Version
|
46
132
|
version: '0'
|
133
|
+
segments:
|
134
|
+
- 0
|
135
|
+
hash: -2437981689318086472
|
47
136
|
requirements: []
|
48
137
|
rubyforge_project:
|
49
|
-
rubygems_version: 1.8.
|
138
|
+
rubygems_version: 1.8.23
|
50
139
|
signing_key:
|
51
140
|
specification_version: 3
|
52
|
-
summary:
|
53
|
-
|
54
|
-
|
141
|
+
summary: ''
|
142
|
+
test_files:
|
143
|
+
- test/fixtures/collection.json
|
144
|
+
- test/fixtures/element.json
|
145
|
+
- test/fixtures/root.json
|
146
|
+
- test/hyperclient/discoverer_test.rb
|
147
|
+
- test/hyperclient/http_test.rb
|
148
|
+
- test/hyperclient/resource_test.rb
|
149
|
+
- test/hyperclient/response_test.rb
|
150
|
+
- test/hyperclient_test.rb
|
151
|
+
- test/test_helper.rb
|
152
|
+
has_rdoc:
|
data/README.md
DELETED
@@ -1,71 +0,0 @@
|
|
1
|
-
# Hyperclient
|
2
|
-
|
3
|
-
This gem aims to explore and demonstrate how to write a client for [hypermedia APIs](http://blog.steveklabnik.com/posts/2012-02-23-rest-is-over), formerly
|
4
|
-
known as REST interfaces that respect the [HATEOAS constraint](http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven).
|
5
|
-
|
6
|
-
Many people have demonstrated how the server should respond. I'm investigating how the client should behave when interacting with a true hypermedia server.
|
7
|
-
|
8
|
-
The gem is being developed against the [Hypertext Application Language](http://stateless.co/hal_specification.html) and [Atompub](http://bitworking.org/projects/atom/rfc5023.html) specifications.
|
9
|
-
|
10
|
-
Contributors welcome!
|
11
|
-
|
12
|
-
## Installation
|
13
|
-
|
14
|
-
Add this line to your application's Gemfile:
|
15
|
-
|
16
|
-
gem 'hyperclient'
|
17
|
-
|
18
|
-
And then execute:
|
19
|
-
|
20
|
-
$ bundle
|
21
|
-
|
22
|
-
Or install it yourself as:
|
23
|
-
|
24
|
-
$ gem install hyperclient
|
25
|
-
|
26
|
-
## Usage
|
27
|
-
|
28
|
-
```ruby
|
29
|
-
require 'hyperclient'
|
30
|
-
|
31
|
-
# NOTE this is the only URI you ever need to specify yourself. The server will provide all future links.
|
32
|
-
resource = Hyperclient::Resource.new("http://api.example.com")
|
33
|
-
|
34
|
-
resource.links.each { |l| puts(l.relation => l.uri) }
|
35
|
-
# {:self => "https://api.example.com"}
|
36
|
-
# {:orders => "https://api.example.com/orders"}
|
37
|
-
# {:customers => "https://api.example.com/customers"}
|
38
|
-
|
39
|
-
# since we're at the root level of this API we haven't drilled into any objects yet
|
40
|
-
pp resource.objects
|
41
|
-
# []
|
42
|
-
|
43
|
-
orders_resource = resource[:orders].get
|
44
|
-
pp orders_resource.links
|
45
|
-
# {:self => "https://api.example.com/orders",
|
46
|
-
# :next => "https://api.example.com/orders/page/2"}
|
47
|
-
|
48
|
-
# some servers may return embedded objects
|
49
|
-
orders_resource.objects.each { |l| puts l.class => l.attributes }
|
50
|
-
# [Hyperclient::Resource => {:id => 50, :item_name => "R2 Motivator", :created_at => "2012-02-03 12:15:02 -0400"},
|
51
|
-
# Hyperclient::Resource => {:id => 51, :item_name => "Hydrospanner", :created_at => "2012-02-04 13:18:12 -0500"}]
|
52
|
-
|
53
|
-
order_resource = orders_resource.post({ item_name: "Droid Coolant" })
|
54
|
-
pp order_resource.location
|
55
|
-
# "https://api.example.com/orders/52"
|
56
|
-
|
57
|
-
pp order_resource.attributes
|
58
|
-
# {:id => 52, :item_name => "Droid Coolant", :created_at => "2012-03-05 21:31:04 -0500"}
|
59
|
-
```
|
60
|
-
|
61
|
-
## Contributing
|
62
|
-
|
63
|
-
1. Fork it
|
64
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
65
|
-
3. Commit your changes (`git commit -am 'Added some feature'`)
|
66
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
67
|
-
5. Create new Pull Request
|
68
|
-
|
69
|
-
## Questions?
|
70
|
-
|
71
|
-
Contact me on [Twitter](https://twitter/subelsky) or [email](mike@subelsky.com).
|