hal-client 2.0.2 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +28 -3
- data/hal-client.gemspec +1 -1
- data/lib/hal_client/collection.rb +73 -0
- data/lib/hal_client/errors.rb +1 -0
- data/lib/hal_client/representation.rb +16 -2
- data/lib/hal_client/representation_set.rb +23 -0
- data/lib/hal_client/version.rb +1 -1
- data/lib/hal_client.rb +41 -5
- data/spec/hal_client/collection_spec.rb +87 -0
- data/spec/hal_client/representation_set_spec.rb +21 -1
- data/spec/hal_client/representation_spec.rb +33 -0
- data/spec/hal_client_spec.rb +65 -1
- metadata +11 -8
data/README.md
CHANGED
@@ -47,7 +47,7 @@ In the example above `item` is the link rel. The `#related` method extracts embe
|
|
47
47
|
|
48
48
|
#### Request timing
|
49
49
|
|
50
|
-
If the `author` relationship was a link in the above example the HTTP GET to retrieve Bob's representation from the server does not happen until the `#property` method is called. This lazy dereferencing allows for working with efficiently with larger relationship sets.
|
50
|
+
If the `author` relationship was a regular link (that is, not embedded) in the above example the HTTP GET to retrieve Bob's representation from the server does not happen until the `#property` method is called. This lazy dereferencing allows for working with efficiently with larger relationship sets.
|
51
51
|
|
52
52
|
#### CURIEs
|
53
53
|
|
@@ -66,7 +66,7 @@ Bob's home location can be retrieved with
|
|
66
66
|
|
67
67
|
Links are always accessed using the full link relation, rather than the CURIE, because the document producer can use any arbitrary string as the prefix. This means that clients must not make any assumptions regarding what prefix will be used because it might change over time or even between documents.
|
68
68
|
|
69
|
-
|
69
|
+
#### Templated links
|
70
70
|
|
71
71
|
The `#related` methods takes a `Hash` as its second argument which is used to expand any templated links that are involved in the navigation.
|
72
72
|
|
@@ -88,6 +88,26 @@ All `HalClient::Representation`s exposed an `#href` attribute which is its ident
|
|
88
88
|
blog['title'] # => "Some Person's Blog"
|
89
89
|
blog['item'] # => #<RepresentationSet:...>
|
90
90
|
|
91
|
+
### POST requests
|
92
|
+
|
93
|
+
HalClient supports POST requests to remote resources via it's `#post` method.
|
94
|
+
|
95
|
+
blog.post(new_article_as_hal_json_str)
|
96
|
+
#=> #<Representation: http://blog.me>
|
97
|
+
|
98
|
+
The argument to post may be `String` or any object that responds to `#to_hal`. Additional options may be passed to change the content type of the post, etc.
|
99
|
+
|
100
|
+
### Paged collections
|
101
|
+
|
102
|
+
HalClient provides a high level abstraction for paged collections encoded using [standard `item`, `next` and `prev` link relations](http://tools.ietf.org/html/rfc6573).
|
103
|
+
|
104
|
+
articles = HalClient::Collection.new(blog)
|
105
|
+
articles.each do |an_article|
|
106
|
+
# do something with each article representation
|
107
|
+
end
|
108
|
+
|
109
|
+
If the collection is paged this will navigate to the next page after yielding all the items on the current page. `HalClient::Collection` is `Enumerable` so all your favorite collection methods are available.
|
110
|
+
|
91
111
|
### Custom media types
|
92
112
|
|
93
113
|
If the API uses one or more a custom mime types we can specify that they be included in the `Accept` header field of each request.
|
@@ -96,7 +116,12 @@ If the API uses one or more a custom mime types we can specify that they be incl
|
|
96
116
|
my_client.get("http://blog.me/")
|
97
117
|
# => #<Representation: http://blog.me/>
|
98
118
|
|
99
|
-
|
119
|
+
Similarly we can set the default `Content-Type` for post requests.
|
120
|
+
|
121
|
+
my_client = HalClient.new(accept: "application/vnd.myapp+hal+json",
|
122
|
+
content_type: "application/vnd.myapp+hal+json")
|
123
|
+
|
124
|
+
### Parsing representations on the server side
|
100
125
|
|
101
126
|
HalClient can be used by servers of HAL APIs to interpret the bodies of requests. For example,
|
102
127
|
|
data/hal-client.gemspec
CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_dependency "rest-client", "~> 1.6", '>= 1.6.0'
|
22
22
|
spec.add_dependency "addressable", "~> 2.3", '>= 2.3.0'
|
23
|
-
spec.add_dependency "multi_json", "~> 1.
|
23
|
+
spec.add_dependency "multi_json", "~> 1.9", '>= 1.9.0'
|
24
24
|
|
25
25
|
spec.add_development_dependency "bundler", "~> 1.5"
|
26
26
|
spec.add_development_dependency "rake", "~> 10.1", '>= 10.1.0'
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require_relative "../hal_client"
|
2
|
+
|
3
|
+
class HalClient
|
4
|
+
|
5
|
+
# Enumerable for items in a paged collection of HAL representations
|
6
|
+
# that are encoded using the IANA standard `item`, `next` and `prev`
|
7
|
+
# link rels.
|
8
|
+
#
|
9
|
+
# This will fetch subsequent pages on iteration
|
10
|
+
class Collection
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
# Initializes a collection starting at `first_page`.
|
14
|
+
#
|
15
|
+
# first_page - The HalClient::Representation of the first page of
|
16
|
+
# the collection to be iterated over.
|
17
|
+
#
|
18
|
+
# Raises HalClient::NotACollectionError if `first_page` is not a
|
19
|
+
# page of a collection.
|
20
|
+
# Raises ArgumentError if `first_page` is some page other than
|
21
|
+
# the first of the collection.
|
22
|
+
def initialize(first_page)
|
23
|
+
(fail NotACollectionError) unless first_page.has_related? "item"
|
24
|
+
(fail ArgumentError, "Not the first page of the collection") if first_page.has_related? "prev"
|
25
|
+
|
26
|
+
@first_page = first_page
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the number of items in the collection if it is fast to
|
30
|
+
# calculate.
|
31
|
+
#
|
32
|
+
# Raises NotImplementedError if any of the pages of the collection
|
33
|
+
# have not already been cached.
|
34
|
+
def count(&blk)
|
35
|
+
(fail NotImplementedError, "Cowardly refusing to make an arbitrary number of HTTP requests") unless all_pages_cached?
|
36
|
+
|
37
|
+
total = 0
|
38
|
+
each_page do |p|
|
39
|
+
total += p.related("item").count
|
40
|
+
end
|
41
|
+
|
42
|
+
total
|
43
|
+
end
|
44
|
+
|
45
|
+
# Iterates over the members of the collection fetching the next
|
46
|
+
# page as necessary.
|
47
|
+
#
|
48
|
+
# Yields the next item of the iteration.
|
49
|
+
def each(&blk)
|
50
|
+
each_page do |a_page|
|
51
|
+
a_page.related("item").each(&blk)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
attr_reader :first_page
|
58
|
+
|
59
|
+
def all_pages_cached?
|
60
|
+
! first_page.has_related?("next")
|
61
|
+
end
|
62
|
+
|
63
|
+
def each_page(&blk)
|
64
|
+
yield first_page
|
65
|
+
|
66
|
+
cur_page = first_page
|
67
|
+
while cur_page.has_related? "next"
|
68
|
+
cur_page = cur_page.related("next").first
|
69
|
+
yield cur_page
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/hal_client/errors.rb
CHANGED
@@ -27,7 +27,15 @@ class HalClient
|
|
27
27
|
@raw.nil? && @href.nil?
|
28
28
|
|
29
29
|
(fail InvalidRepresentationError, "Invalid HAL representation: #{raw.inspect}") if
|
30
|
-
raw && ! hashish?(raw)
|
30
|
+
@raw && ! hashish?(@raw)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Posts a `Representation` or `String` to this resource.
|
34
|
+
#
|
35
|
+
# data - a `String` or an object that responds to `#to_hal`
|
36
|
+
# options - set of options to pass to `HalClient#post`
|
37
|
+
def post(data, options={})
|
38
|
+
@hal_client.post(href, data, options)
|
31
39
|
end
|
32
40
|
|
33
41
|
# Returns The value of the specified property or the specified
|
@@ -146,13 +154,19 @@ class HalClient
|
|
146
154
|
"#<" + self.class.name + ": " + href + ">"
|
147
155
|
end
|
148
156
|
|
157
|
+
# Returns the raw json representation of this representation
|
158
|
+
def to_json
|
159
|
+
raw.to_json
|
160
|
+
end
|
161
|
+
|
149
162
|
protected
|
150
163
|
attr_reader :hal_client
|
151
164
|
|
152
165
|
MISSING = Object.new
|
153
166
|
|
154
167
|
def raw
|
155
|
-
if @raw.nil? && @href
|
168
|
+
if @raw.nil? && @href
|
169
|
+
(fail "unable to make requests due to missing hal client") unless hal_client
|
156
170
|
@raw ||= hal_client.get(@href).raw
|
157
171
|
end
|
158
172
|
|
@@ -27,6 +27,29 @@ class HalClient
|
|
27
27
|
RepresentationSet.new flat_map{|it| it.related(link_rel, options){[]}.to_a }
|
28
28
|
end
|
29
29
|
|
30
|
+
# Returns true if any member representation contains a link
|
31
|
+
# (including embedded links) whose rel is `link_rel`.
|
32
|
+
#
|
33
|
+
# link_rel - The link rel of interest
|
34
|
+
def has_related?(link_rel)
|
35
|
+
_ = related link_rel
|
36
|
+
true
|
37
|
+
|
38
|
+
rescue KeyError
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
# Post a `Representation` or `String` to the resource.
|
43
|
+
#
|
44
|
+
# NOTE: This only works for a single representation.
|
45
|
+
#
|
46
|
+
# data - a `String` or an object that responds to `#to_hal`
|
47
|
+
# options - set of options to pass to `HalClient#post`
|
48
|
+
def post(data, options={})
|
49
|
+
raise NotImplementedError, "We only posts to singular resources." if count > 1
|
50
|
+
first.post(data, options)
|
51
|
+
end
|
52
|
+
|
30
53
|
protected
|
31
54
|
|
32
55
|
attr_reader :reprs
|
data/lib/hal_client/version.rb
CHANGED
data/lib/hal_client.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "hal_client/version"
|
2
2
|
require 'rest-client'
|
3
|
+
require 'multi_json'
|
3
4
|
|
4
5
|
# Adapter used to access resources.
|
5
6
|
class HalClient
|
@@ -7,14 +8,18 @@ class HalClient
|
|
7
8
|
autoload :RepresentationSet, 'hal_client/representation_set'
|
8
9
|
autoload :CurieResolver, 'hal_client/curie_resolver'
|
9
10
|
autoload :InvalidRepresentationError, 'hal_client/errors'
|
11
|
+
autoload :NotACollectionError, 'hal_client/errors'
|
10
12
|
|
11
13
|
# Initializes a new client instance
|
12
14
|
#
|
13
15
|
# options - hash of configuration options
|
14
|
-
# :accept - one or more content types that should be
|
16
|
+
# :accept - one or more content types that should be
|
15
17
|
# prepended to the `Accept` header field of each request.
|
18
|
+
# :content_type - a single content type that should be
|
19
|
+
# prepended to the `Content-Type` header field of each request.
|
16
20
|
def initialize(options={})
|
17
21
|
@default_accept = options.fetch(:accept, 'application/hal+json')
|
22
|
+
@default_content_type = options.fetch(:content_type, 'application/hal+json')
|
18
23
|
end
|
19
24
|
|
20
25
|
# Returns a `Representation` of the resource identified by `url`.
|
@@ -22,16 +27,38 @@ class HalClient
|
|
22
27
|
# url - The URL of the resource of interest.
|
23
28
|
# options - set of options to pass to `RestClient#get`
|
24
29
|
def get(url, options={})
|
25
|
-
resp = RestClient.get url,
|
30
|
+
resp = RestClient.get url, get_options(options)
|
26
31
|
Representation.new hal_client: self, parsed_json: MultiJson.load(resp)
|
27
32
|
end
|
28
33
|
|
34
|
+
# Post a `Representation` or `String` to the resource identified at `url`.
|
35
|
+
#
|
36
|
+
# url - The URL of the resource of interest.
|
37
|
+
# data - a `String` or an object that responds to `#to_hal`
|
38
|
+
# options - set of options to pass to `RestClient#post`
|
39
|
+
def post(url, data, options={})
|
40
|
+
resp = RestClient.post url, data, post_options(options)
|
41
|
+
|
42
|
+
begin
|
43
|
+
Representation.new hal_client: self, parsed_json: MultiJson.load(resp)
|
44
|
+
rescue MultiJson::ParseError, InvalidRepresentationError => e
|
45
|
+
resp
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
29
49
|
protected
|
30
50
|
|
31
|
-
attr_reader :default_accept
|
51
|
+
attr_reader :default_accept, :default_content_type
|
32
52
|
|
33
|
-
def
|
34
|
-
{accept: default_accept}.merge overrides
|
53
|
+
def get_options(overrides)
|
54
|
+
{ accept: default_accept }.merge overrides
|
55
|
+
end
|
56
|
+
|
57
|
+
def post_options(overrides)
|
58
|
+
{
|
59
|
+
accept: default_accept,
|
60
|
+
content_type: default_content_type
|
61
|
+
}.merge overrides
|
35
62
|
end
|
36
63
|
|
37
64
|
module EntryPointCovenienceMethods
|
@@ -43,6 +70,15 @@ class HalClient
|
|
43
70
|
default_client.get(url, options)
|
44
71
|
end
|
45
72
|
|
73
|
+
# Post a `Representation` or `String` to the resource identified at `url`.
|
74
|
+
#
|
75
|
+
# url - The URL of the resource of interest.
|
76
|
+
# data - a `String` or an object that responds to `#to_hal`
|
77
|
+
# options - set of options to pass to `RestClient#get`
|
78
|
+
def post(url, data, options={})
|
79
|
+
default_client.post(url, data, options)
|
80
|
+
end
|
81
|
+
|
46
82
|
protected
|
47
83
|
|
48
84
|
def default_client
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require_relative "../spec_helper"
|
2
|
+
|
3
|
+
require 'hal_client/collection'
|
4
|
+
|
5
|
+
describe HalClient::Collection do
|
6
|
+
describe "creation" do
|
7
|
+
subject { described_class }
|
8
|
+
|
9
|
+
specify do
|
10
|
+
expect { described_class.new(collection_page) }
|
11
|
+
.not_to raise_error
|
12
|
+
end
|
13
|
+
|
14
|
+
specify do
|
15
|
+
expect { described_class.new(non_collection_repr) }
|
16
|
+
.to raise_error HalClient::NotACollectionError
|
17
|
+
end
|
18
|
+
|
19
|
+
specify do
|
20
|
+
expect { described_class.new(non_first_page) }
|
21
|
+
.to raise_error ArgumentError, /first page/
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:non_collection_repr) { repr({}) }
|
25
|
+
let(:non_first_page) { collection_page(prev_href: "http://example.com/p1") }
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "multi-item, multi-page" do
|
29
|
+
subject(:collection) { described_class.new(first_page) }
|
30
|
+
let!(:second_page_req) { stub_request(:get, second_page.href)
|
31
|
+
.to_return body: second_page.to_json }
|
32
|
+
|
33
|
+
it "fetches all the pages when iterating" do
|
34
|
+
collection.each do |it| end
|
35
|
+
|
36
|
+
expect(a_request(:get, second_page.href)).to have_been_made
|
37
|
+
end
|
38
|
+
|
39
|
+
it "iteration yields all the items" do
|
40
|
+
yielded = collection.map { |it| it.href }
|
41
|
+
expect(yielded).to eq ["foo", "bar", "baz"]
|
42
|
+
end
|
43
|
+
|
44
|
+
specify { expect { collection.count }.to raise_exception }
|
45
|
+
|
46
|
+
|
47
|
+
let(:first_page_href) { "http://example.com/p1" }
|
48
|
+
let(:first_page) { collection_page(next_href: second_page_href,
|
49
|
+
self_href: first_page_href,
|
50
|
+
items: ["foo", "bar"]) }
|
51
|
+
|
52
|
+
let(:second_page_href) { "http://example.com/p2" }
|
53
|
+
let(:second_page) { collection_page(items: ["baz"],
|
54
|
+
self_href: second_page_href,
|
55
|
+
prev_href: first_page_href) }
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "multi-item, single page" do
|
59
|
+
subject(:collection) { described_class.new(only_page) }
|
60
|
+
|
61
|
+
specify { expect(collection.count).to eq 2 }
|
62
|
+
|
63
|
+
let(:only_page) { collection_page(self_href: "http://example.com/p1",
|
64
|
+
items: ["foo", "bar"]) }
|
65
|
+
end
|
66
|
+
|
67
|
+
let(:hal_client) { HalClient.new }
|
68
|
+
|
69
|
+
def collection_page(opts={})
|
70
|
+
next_href = opts[:next_href]
|
71
|
+
prev_href = opts[:prev_href]
|
72
|
+
self_href = opts.fetch(:self_href, "a_page")
|
73
|
+
items = opts.fetch(:items, [])
|
74
|
+
.map{|it| {"_links"=>{"self"=>{"href"=>it}}} }
|
75
|
+
|
76
|
+
full = {"_embedded"=>{"item"=>items},
|
77
|
+
"_links"=>{"self"=>{"href"=>self_href}}}
|
78
|
+
full["_links"]["next"] = {"href" => next_href} if next_href
|
79
|
+
full["_links"]["prev"] = {"href" => prev_href} if prev_href
|
80
|
+
|
81
|
+
repr full
|
82
|
+
end
|
83
|
+
|
84
|
+
def repr(a_hash)
|
85
|
+
HalClient::Representation.new parsed_json: a_hash, hal_client: hal_client
|
86
|
+
end
|
87
|
+
end
|
@@ -28,6 +28,7 @@ describe HalClient::RepresentationSet do
|
|
28
28
|
it "returns true if there are any" do
|
29
29
|
expect(subject.any?{|it| it == foo_repr }).to be_true
|
30
30
|
end
|
31
|
+
|
31
32
|
it "returns false if there aren't any" do
|
32
33
|
expect(subject.any?{|it| false }).to be_false
|
33
34
|
end
|
@@ -40,6 +41,7 @@ describe HalClient::RepresentationSet do
|
|
40
41
|
it { should include_representation_of "http://example.com/bar-spouse" }
|
41
42
|
it { should have(2).items }
|
42
43
|
end
|
44
|
+
|
43
45
|
context "multiple targets" do
|
44
46
|
subject(:returned_val) { repr_set.related("sibling") }
|
45
47
|
it { should include_representation_of "http://example.com/foo-brother" }
|
@@ -47,6 +49,7 @@ describe HalClient::RepresentationSet do
|
|
47
49
|
it { should include_representation_of "http://example.com/bar-brother" }
|
48
50
|
it { should have(3).items }
|
49
51
|
end
|
52
|
+
|
50
53
|
context "templated" do
|
51
54
|
subject(:returned_val) { repr_set.related("cousin", distance: "first") }
|
52
55
|
it { should include_representation_of "http://example.com/foo-first-cousin" }
|
@@ -56,6 +59,24 @@ describe HalClient::RepresentationSet do
|
|
56
59
|
end
|
57
60
|
end
|
58
61
|
|
62
|
+
describe "#post" do
|
63
|
+
context "with a single representation" do
|
64
|
+
subject(:repr_single_set) { described_class.new([foo_repr]) }
|
65
|
+
let!(:post_request) { stub_request(:post, "example.com/foo") }
|
66
|
+
|
67
|
+
before(:each) do
|
68
|
+
repr_single_set.post("abc")
|
69
|
+
end
|
70
|
+
|
71
|
+
it "makes an HTTP POST with the data within the representation" do
|
72
|
+
expect(
|
73
|
+
post_request.
|
74
|
+
with(:body => "abc", :headers => {'Content-Type' => 'application/hal+json'})
|
75
|
+
).to have_been_made
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
59
80
|
let(:a_client) { HalClient.new }
|
60
81
|
|
61
82
|
let(:foo_repr) { HalClient::Representation.new hal_client: a_client, parsed_json: MultiJson.load(foo_hal)}
|
@@ -101,7 +122,6 @@ describe HalClient::RepresentationSet do
|
|
101
122
|
to_return body: %Q|{"_links":{"self":{"href":#{url.to_json}}}}|
|
102
123
|
end
|
103
124
|
|
104
|
-
|
105
125
|
RSpec::Matchers.define(:include_representation_of) do |url|
|
106
126
|
match { |repr_set|
|
107
127
|
repr_set.any?{|it| it.href == url}
|
@@ -23,6 +23,23 @@ HAL
|
|
23
23
|
subject(:repr) { described_class.new(hal_client: a_client,
|
24
24
|
parsed_json: MultiJson.load(raw_repr)) }
|
25
25
|
|
26
|
+
describe "#post" do
|
27
|
+
let!(:post_request) {
|
28
|
+
stub_request(:post, "example.com/bar")
|
29
|
+
}
|
30
|
+
|
31
|
+
before(:each) do
|
32
|
+
repr.related("link1").post("abc")
|
33
|
+
end
|
34
|
+
|
35
|
+
specify {
|
36
|
+
expect(
|
37
|
+
post_request.
|
38
|
+
with(:body => "abc", :headers => {'Content-Type' => 'application/hal+json'})
|
39
|
+
).to have_been_made
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
26
43
|
describe "#to_s" do
|
27
44
|
subject(:return_val) { repr.to_s }
|
28
45
|
|
@@ -34,6 +51,7 @@ HAL
|
|
34
51
|
subject { repr.property "prop1" }
|
35
52
|
it { should eq 1 }
|
36
53
|
end
|
54
|
+
|
37
55
|
context "non-existent" do
|
38
56
|
it "raises exception" do
|
39
57
|
expect{repr.property 'wat'}.to raise_exception KeyError
|
@@ -48,6 +66,7 @@ HAL
|
|
48
66
|
subject { repr.fetch "prop1" }
|
49
67
|
it { should eq 1 }
|
50
68
|
end
|
69
|
+
|
51
70
|
context "for existent link" do
|
52
71
|
subject { repr.fetch "link1" }
|
53
72
|
it { should have(1).item }
|
@@ -55,6 +74,7 @@ HAL
|
|
55
74
|
expect(subject.first.href).to eq "http://example.com/bar"
|
56
75
|
end
|
57
76
|
end
|
77
|
+
|
58
78
|
context "for existent embedded" do
|
59
79
|
subject { repr.fetch "embed1" }
|
60
80
|
it { should have(1).item }
|
@@ -62,15 +82,18 @@ HAL
|
|
62
82
|
expect(subject.first.href).to eq "http://example.com/baz"
|
63
83
|
end
|
64
84
|
end
|
85
|
+
|
65
86
|
context "non-existent item w/o default" do
|
66
87
|
it "raises exception" do
|
67
88
|
expect{repr.fetch 'wat'}.to raise_exception KeyError
|
68
89
|
end
|
69
90
|
end
|
91
|
+
|
70
92
|
context "non-existent item w/ default value" do
|
71
93
|
subject { repr.fetch "wat", "whatevs" }
|
72
94
|
it { should eq "whatevs" }
|
73
95
|
end
|
96
|
+
|
74
97
|
context "non-existent item w/ default value generator" do
|
75
98
|
subject { repr.fetch("wat"){|key| key+"gen" } }
|
76
99
|
it { should eq "watgen" }
|
@@ -82,16 +105,19 @@ HAL
|
|
82
105
|
subject { repr["prop1"] }
|
83
106
|
it { should eq 1 }
|
84
107
|
end
|
108
|
+
|
85
109
|
context "for existent link" do
|
86
110
|
subject { repr["link1"] }
|
87
111
|
it { should have(1).item }
|
88
112
|
it { should include_representation_of "http://example.com/bar" }
|
89
113
|
end
|
114
|
+
|
90
115
|
context "for existent embedded" do
|
91
116
|
subject { repr["embed1"] }
|
92
117
|
it { should have(1).item }
|
93
118
|
it { should include_representation_of "http://example.com/baz" }
|
94
119
|
end
|
120
|
+
|
95
121
|
context "non-existent item w/o default" do
|
96
122
|
subject { repr["wat"] }
|
97
123
|
it { should be_nil }
|
@@ -104,22 +130,26 @@ HAL
|
|
104
130
|
it { should have(1).item }
|
105
131
|
it { should include_representation_of "http://example.com/bar" }
|
106
132
|
end
|
133
|
+
|
107
134
|
context "for existent compound link" do
|
108
135
|
subject { repr.related "link3" }
|
109
136
|
it { should have(2).item }
|
110
137
|
it { should include_representation_of "http://example.com/link3-a" }
|
111
138
|
it { should include_representation_of "http://example.com/link3-b" }
|
112
139
|
end
|
140
|
+
|
113
141
|
context "for existent templated link" do
|
114
142
|
subject { repr.related "link2", name: "bob" }
|
115
143
|
it { should have(1).item }
|
116
144
|
it { should include_representation_of "http://example.com/people?name=bob" }
|
117
145
|
end
|
146
|
+
|
118
147
|
context "for existent embedded" do
|
119
148
|
subject { repr.related "embed1" }
|
120
149
|
it { should have(1).item }
|
121
150
|
it { should include_representation_of "http://example.com/baz" }
|
122
151
|
end
|
152
|
+
|
123
153
|
context "non-existent item w/o default" do
|
124
154
|
it "raises exception" do
|
125
155
|
expect{repr.related 'wat'}.to raise_exception KeyError
|
@@ -133,11 +163,13 @@ HAL
|
|
133
163
|
it { should have(1).item }
|
134
164
|
it { should include "http://example.com/bar" }
|
135
165
|
end
|
166
|
+
|
136
167
|
context "for existent embedded" do
|
137
168
|
subject { repr.related_hrefs "embed1" }
|
138
169
|
it { should have(1).item }
|
139
170
|
it { should include "http://example.com/baz" }
|
140
171
|
end
|
172
|
+
|
141
173
|
context "non-existent item w/o default" do
|
142
174
|
it "raises exception" do
|
143
175
|
expect{repr.related_hrefs 'wat'}.to raise_exception KeyError
|
@@ -146,6 +178,7 @@ HAL
|
|
146
178
|
end
|
147
179
|
|
148
180
|
specify { expect(subject.has_related? "link1").to be true }
|
181
|
+
specify { expect(subject.has_related? "link3").to be true }
|
149
182
|
specify { expect(subject.has_related? "embed1").to be true }
|
150
183
|
|
151
184
|
specify { expect(subject.has_related? "no-such-link").to be false }
|
data/spec/hal_client_spec.rb
CHANGED
@@ -39,7 +39,7 @@ describe HalClient do
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
-
describe ".get(<url>)" do
|
42
|
+
describe ".get(<url>)" do
|
43
43
|
let!(:return_val) { HalClient.get "http://example.com/foo" }
|
44
44
|
|
45
45
|
it "returns a HalClient::Representation" do
|
@@ -57,6 +57,70 @@ describe HalClient do
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
+
describe "#post(<url>)" do
|
61
|
+
subject(:client) { HalClient.new }
|
62
|
+
let!(:return_val) { client.post "http://example.com/foo", post_data }
|
63
|
+
|
64
|
+
it "returns a HalClient::Representation" do
|
65
|
+
expect(return_val).to be_kind_of HalClient::Representation
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "request" do
|
69
|
+
subject { post_request }
|
70
|
+
it("should have been made") { should have_been_made }
|
71
|
+
|
72
|
+
it "sends content type header" do
|
73
|
+
expect(post_request.with(headers: {'Content-Type' => 'application/hal+json'})).
|
74
|
+
to have_been_made
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "explicit content type" do
|
79
|
+
subject(:client) { HalClient.new content_type: 'app/test' }
|
80
|
+
it "sends specified content-type header" do
|
81
|
+
expect(post_request.with(headers: {'Content-Type' => 'app/test'})).
|
82
|
+
to have_been_made
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "with no response body" do
|
87
|
+
subject { empty_post_request }
|
88
|
+
let!(:return_val) { client.post "http://example.com/foo", nil }
|
89
|
+
|
90
|
+
it "returns a 2xx status code in the response" do
|
91
|
+
expect(return_val.code.to_s).to match(/^2../)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe ".post(<url>)" do
|
97
|
+
let!(:return_val) { HalClient.post "http://example.com/foo", post_data }
|
98
|
+
|
99
|
+
it "returns a HalClient::Representation" do
|
100
|
+
expect(return_val).to be_kind_of HalClient::Representation
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "request" do
|
104
|
+
subject { post_request }
|
105
|
+
it("should have been made") { should have_been_made }
|
106
|
+
|
107
|
+
it "sends accept header" do
|
108
|
+
expect(post_request.with(headers: {'Content-Type' => 'application/hal+json'})).
|
109
|
+
to have_been_made
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
let(:post_data) { "ABC" }
|
115
|
+
|
116
|
+
let!(:empty_post_request) { stub_request(:post, "http://example.com/foo").
|
117
|
+
with(:body => nil).
|
118
|
+
to_return body: nil }
|
119
|
+
|
120
|
+
let!(:post_request) { stub_request(:post, "http://example.com/foo").
|
121
|
+
with(:body => post_data).
|
122
|
+
to_return body: "{}" }
|
123
|
+
|
60
124
|
let!(:request) { stub_request(:get, "http://example.com/foo").
|
61
125
|
to_return body: "{}" }
|
62
126
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hal-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-03-
|
12
|
+
date: 2014-03-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rest-client
|
@@ -62,10 +62,10 @@ dependencies:
|
|
62
62
|
requirements:
|
63
63
|
- - ~>
|
64
64
|
- !ruby/object:Gem::Version
|
65
|
-
version: '1.
|
65
|
+
version: '1.9'
|
66
66
|
- - ! '>='
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 1.
|
68
|
+
version: 1.9.0
|
69
69
|
type: :runtime
|
70
70
|
prerelease: false
|
71
71
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -73,10 +73,10 @@ dependencies:
|
|
73
73
|
requirements:
|
74
74
|
- - ~>
|
75
75
|
- !ruby/object:Gem::Version
|
76
|
-
version: '1.
|
76
|
+
version: '1.9'
|
77
77
|
- - ! '>='
|
78
78
|
- !ruby/object:Gem::Version
|
79
|
-
version: 1.
|
79
|
+
version: 1.9.0
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: bundler
|
82
82
|
requirement: !ruby/object:Gem::Requirement
|
@@ -175,11 +175,13 @@ files:
|
|
175
175
|
- hal-client.gemspec
|
176
176
|
- lib/hal-client.rb
|
177
177
|
- lib/hal_client.rb
|
178
|
+
- lib/hal_client/collection.rb
|
178
179
|
- lib/hal_client/curie_resolver.rb
|
179
180
|
- lib/hal_client/errors.rb
|
180
181
|
- lib/hal_client/representation.rb
|
181
182
|
- lib/hal_client/representation_set.rb
|
182
183
|
- lib/hal_client/version.rb
|
184
|
+
- spec/hal_client/collection_spec.rb
|
183
185
|
- spec/hal_client/curie_resolver_spec.rb
|
184
186
|
- spec/hal_client/representation_set_spec.rb
|
185
187
|
- spec/hal_client/representation_spec.rb
|
@@ -200,7 +202,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
200
202
|
version: '0'
|
201
203
|
segments:
|
202
204
|
- 0
|
203
|
-
hash:
|
205
|
+
hash: 2964904468148201420
|
204
206
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
205
207
|
none: false
|
206
208
|
requirements:
|
@@ -209,7 +211,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
209
211
|
version: '0'
|
210
212
|
segments:
|
211
213
|
- 0
|
212
|
-
hash:
|
214
|
+
hash: 2964904468148201420
|
213
215
|
requirements: []
|
214
216
|
rubyforge_project:
|
215
217
|
rubygems_version: 1.8.23
|
@@ -217,6 +219,7 @@ signing_key:
|
|
217
219
|
specification_version: 3
|
218
220
|
summary: Use HAL APIs easily
|
219
221
|
test_files:
|
222
|
+
- spec/hal_client/collection_spec.rb
|
220
223
|
- spec/hal_client/curie_resolver_spec.rb
|
221
224
|
- spec/hal_client/representation_set_spec.rb
|
222
225
|
- spec/hal_client/representation_spec.rb
|