hal-client 3.1.2 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +46 -10
- data/lib/hal_client/representation.rb +27 -5
- data/lib/hal_client/representation_editor.rb +138 -0
- data/lib/hal_client/representation_set.rb +22 -0
- data/lib/hal_client/version.rb +1 -1
- data/lib/hal_client.rb +32 -0
- data/spec/hal_client/representation_editor_spec.rb +163 -0
- data/spec/hal_client/representation_set_spec.rb +36 -0
- data/spec/hal_client/representation_spec.rb +41 -2
- data/spec/spec_helper.rb +6 -0
- data/spec/support/custom_matchers.rb +19 -0
- metadata +33 -51
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b01e2f92e2ff7241a7b3b9af4e040792ce9254ef
|
4
|
+
data.tar.gz: 70adee990ffa32582f0e40836353746066cf3478
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2ae965fff453c3252769221d653689da092c984fa2e30c47c1696b09ab9504ea13e8cde54a82d265e601a03248f44fe85b5d12245380a4795eff685bf1aa8cf3
|
7
|
+
data.tar.gz: 1466db8f6c04ed59b314b15c996c6c118646989819a98bdac069d3e15bcf0a037052c1655156b8ce1479b317e052670d9e98bcaca652ab3427a406d1321a7b23
|
data/README.md
CHANGED
@@ -31,7 +31,7 @@ In the example above `item` is the link rel. The `#related` method extracts embe
|
|
31
31
|
all_the_authors.first.property("name")
|
32
32
|
# => "Bob Smith"
|
33
33
|
|
34
|
-
#### Request
|
34
|
+
#### Request evaluation order
|
35
35
|
|
36
36
|
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.
|
37
37
|
|
@@ -74,15 +74,6 @@ All `HalClient::Representation`s exposed an `#href` attribute which is its ident
|
|
74
74
|
blog['title'] # => "Some Person's Blog"
|
75
75
|
blog['item'] # => #<RepresentationSet:...>
|
76
76
|
|
77
|
-
### POST requests
|
78
|
-
|
79
|
-
HalClient supports POST requests to remote resources via it's `#post` method.
|
80
|
-
|
81
|
-
blog.post(new_article_as_hal_json_str)
|
82
|
-
#=> #<Representation: http://blog.me>
|
83
|
-
|
84
|
-
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.
|
85
|
-
|
86
77
|
### Paged collections
|
87
78
|
|
88
79
|
HalClient provides a high level abstraction for paged collections encoded using [standard `item`, `next` and `prev` link relations](http://tools.ietf.org/html/rfc6573).
|
@@ -94,6 +85,51 @@ HalClient provides a high level abstraction for paged collections encoded using
|
|
94
85
|
|
95
86
|
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.
|
96
87
|
|
88
|
+
### PUT/POST/PATCH requests
|
89
|
+
|
90
|
+
HalClient supports PUT/POST/PATCH requests to remote resources via it's `#put`, `#post` and `#patch` methods, respectively.
|
91
|
+
|
92
|
+
blog.put(update_article_as_hal_json_str)
|
93
|
+
#=> #<Representation: http://blog.me>
|
94
|
+
|
95
|
+
blog.post(new_article_as_hal_json_str)
|
96
|
+
#=> #<Representation: http://blog.me>
|
97
|
+
|
98
|
+
blog.patch(diffs_of_article_as_hal_json_str)
|
99
|
+
#=> #<Representation: http://blog.me>
|
100
|
+
|
101
|
+
The first argument to `#put`, `#post` and `#patch` may be a `String` or any object that responds to `#to_hal`. Additional options may be passed to change the content type of the post, etc.
|
102
|
+
|
103
|
+
### PUT requests
|
104
|
+
|
105
|
+
HalClient supports PUT requests to remote resources via it's `#put` method.
|
106
|
+
|
107
|
+
blog.put(new_article_as_hal_json_str)
|
108
|
+
#=> #<Representation: http://blog.me>
|
109
|
+
|
110
|
+
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.
|
111
|
+
|
112
|
+
### Editing representation
|
113
|
+
|
114
|
+
HalClient supports editing of representations. This is useful when
|
115
|
+
creating resources from a template or updating resources. For example,
|
116
|
+
consider a resource whose "author" relationship we want to update to
|
117
|
+
point the author's new profile page.
|
118
|
+
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
doc = HalClient.get("http://example.com/somedoc")
|
122
|
+
improved_doc =
|
123
|
+
HalClient::RepresentationEditor.new(doc) # create an editor
|
124
|
+
.reject_related("author") { |it| it.property("name") == "John Doe"} # unlink Johe Doe's old page
|
125
|
+
.add_link("author", "http://example.com/john-doe") # add link to his new page
|
126
|
+
|
127
|
+
doc.put(improved_doc) # save changes to server
|
128
|
+
|
129
|
+
```
|
130
|
+
|
131
|
+
This removes "John Plagerist" from the documents list of authors and then performs an HTTP PUT request with the updated document.
|
132
|
+
|
97
133
|
### Custom media types
|
98
134
|
|
99
135
|
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.
|
@@ -38,6 +38,22 @@ class HalClient
|
|
38
38
|
@hal_client.post(href, data, options)
|
39
39
|
end
|
40
40
|
|
41
|
+
# Puts a `Representation` or `String` to this resource.
|
42
|
+
#
|
43
|
+
# data - a `String` or an object that responds to `#to_hal`
|
44
|
+
# options - set of options to pass to `HalClient#put`
|
45
|
+
def put(data, options={})
|
46
|
+
@hal_client.put(href, data, options)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Patchs a `Representation` or `String` to this resource.
|
50
|
+
#
|
51
|
+
# data - a `String` or an object that responds to `#to_hal`
|
52
|
+
# options - set of options to pass to `HalClient#patch`
|
53
|
+
def patch(data, options={})
|
54
|
+
@hal_client.patch(href, data, options)
|
55
|
+
end
|
56
|
+
|
41
57
|
# Returns true if this representation contains the specified
|
42
58
|
# property.
|
43
59
|
#
|
@@ -191,12 +207,9 @@ class HalClient
|
|
191
207
|
def to_json
|
192
208
|
raw.to_json
|
193
209
|
end
|
210
|
+
alias_method :to_hal, :to_json
|
194
211
|
|
195
|
-
|
196
|
-
attr_reader :hal_client
|
197
|
-
|
198
|
-
MISSING = Object.new
|
199
|
-
|
212
|
+
# Internal: Returns parsed json document
|
200
213
|
def raw
|
201
214
|
if @raw.nil? && @href
|
202
215
|
(fail "unable to make requests due to missing hal client") unless hal_client
|
@@ -206,6 +219,15 @@ class HalClient
|
|
206
219
|
@raw
|
207
220
|
end
|
208
221
|
|
222
|
+
# Internal: Returns the HalClient used to retrieve this
|
223
|
+
# representation
|
224
|
+
attr_reader :hal_client
|
225
|
+
|
226
|
+
protected
|
227
|
+
|
228
|
+
MISSING = Object.new
|
229
|
+
|
230
|
+
|
209
231
|
def links
|
210
232
|
@links ||= LinksSection.new raw.fetch("_links"){{}}
|
211
233
|
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'hal_client'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
class HalClient
|
5
|
+
|
6
|
+
# Provides ability to edit a representation. Editing a
|
7
|
+
# representation is useful in writable APIs as a way to update
|
8
|
+
# resources.
|
9
|
+
#
|
10
|
+
# This class will not actually modify the underlying representation
|
11
|
+
# in any way.
|
12
|
+
#
|
13
|
+
# Example:
|
14
|
+
#
|
15
|
+
# ```ruby
|
16
|
+
# altered_doc = HalClient::RepresentationEditor.new(some_doc)
|
17
|
+
# .reject_relate("author") { |it| it["name"] = "John Plagiarist" }
|
18
|
+
# ```
|
19
|
+
class RepresentationEditor
|
20
|
+
extend Forwardable
|
21
|
+
|
22
|
+
# Initialize a new representation editor.
|
23
|
+
#
|
24
|
+
# a_representation - The representation from which you want to
|
25
|
+
# start. This object will *not* be modified!
|
26
|
+
# raw - Not for public use! Used internally for handling multi-
|
27
|
+
# staged changes.
|
28
|
+
def initialize(a_representation, raw = a_representation.raw)
|
29
|
+
@orig_repr = a_representation
|
30
|
+
@raw = raw
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the raw json representation of this representation
|
34
|
+
def to_json
|
35
|
+
raw.to_json
|
36
|
+
end
|
37
|
+
alias_method :to_hal, :to_json
|
38
|
+
|
39
|
+
# Returns a RepresentationEditor for a representation like the
|
40
|
+
# current one but without the specified links and/or embeddeds.
|
41
|
+
#
|
42
|
+
# rel - The relationship type to remove or filter
|
43
|
+
# blk - When given only linked and embedded resource for whom
|
44
|
+
# the block returns true will be rejected.
|
45
|
+
#
|
46
|
+
# Yields Representation of the target for each link/embedded.
|
47
|
+
def reject_related(rel, &blk)
|
48
|
+
reject_links(rel, &blk).reject_embedded(rel, &blk)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns a RepresentationEditor for a representation like the
|
52
|
+
# current one but without the specified links.
|
53
|
+
#
|
54
|
+
# rel - The relationship type to remove or filter
|
55
|
+
# blk - When given only links to resources for whom
|
56
|
+
# the block returns true will be rejected.
|
57
|
+
#
|
58
|
+
# Yields Representation of the target for each link.
|
59
|
+
def reject_links(rel, &blk)
|
60
|
+
reject_from_section("_links",
|
61
|
+
rel,
|
62
|
+
->(l) {Representation.new(href: l["href"],
|
63
|
+
hal_client: hal_client)},
|
64
|
+
blk)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns a RepresentationEditor for a representation like the
|
68
|
+
# current one but without the specified embedded resources.
|
69
|
+
#
|
70
|
+
# rel - The relationship type to remove or filter
|
71
|
+
# blk - When given only embedded resources for whom
|
72
|
+
# the block returns true will be rejected.
|
73
|
+
#
|
74
|
+
# Yields Representation of the target for each embedded.
|
75
|
+
def reject_embedded(rel, &blk)
|
76
|
+
reject_from_section("_embedded",
|
77
|
+
rel,
|
78
|
+
->(e) {Representation.new(parsed_json: e,
|
79
|
+
hal_client: hal_client)},
|
80
|
+
blk)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns a RepresentationEditor exactly like this one except that
|
84
|
+
# is has an additional link to the specified target with the
|
85
|
+
# specified rel.
|
86
|
+
#
|
87
|
+
# rel - The type of relationship this link represents
|
88
|
+
# target - URL of the target of the link
|
89
|
+
# opts
|
90
|
+
# :templated - is this link templated? Default: false
|
91
|
+
def add_link(rel, target, opts={})
|
92
|
+
templated = opts.fetch(:templated, false)
|
93
|
+
|
94
|
+
link_obj = { "href" => target.to_s }
|
95
|
+
link_obj = link_obj.merge("templated" => true) if templated
|
96
|
+
|
97
|
+
with_new_link = Array(raw.fetch("_links", {}).fetch(rel, [])) + [link_obj]
|
98
|
+
updated_links_section = raw.fetch("_links", {}).merge(rel => with_new_link)
|
99
|
+
|
100
|
+
self.class.new(orig_repr, raw.merge("_links" => updated_links_section))
|
101
|
+
end
|
102
|
+
|
103
|
+
protected
|
104
|
+
|
105
|
+
attr_reader :orig_repr, :raw
|
106
|
+
|
107
|
+
def Array(thing)
|
108
|
+
if Hash === thing
|
109
|
+
[thing]
|
110
|
+
else
|
111
|
+
Kernel.Array(thing)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def hal_client
|
116
|
+
orig_repr.hal_client
|
117
|
+
end
|
118
|
+
|
119
|
+
def reject_from_section(name, rel, coercion, filter=nil)
|
120
|
+
return self unless raw.fetch(name, {}).has_key?(rel)
|
121
|
+
|
122
|
+
filtered_rel = if filter
|
123
|
+
[raw[name].fetch(rel,[])].flatten
|
124
|
+
.reject{|it| filter.call(coercion.call(it)) }
|
125
|
+
else
|
126
|
+
[]
|
127
|
+
end
|
128
|
+
|
129
|
+
new_sec = if filtered_rel.empty?
|
130
|
+
raw[name].reject{|k,_| rel == k}
|
131
|
+
else
|
132
|
+
raw[name].merge(rel => filtered_rel )
|
133
|
+
end
|
134
|
+
|
135
|
+
self.class.new(orig_repr, raw.merge(name => new_sec))
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -49,6 +49,28 @@ class HalClient
|
|
49
49
|
first.post(data, options)
|
50
50
|
end
|
51
51
|
|
52
|
+
# Put a `Representation` or `String` to the resource.
|
53
|
+
#
|
54
|
+
# NOTE: This only works for a single representation.
|
55
|
+
#
|
56
|
+
# data - a `String` or an object that responds to `#to_hal`
|
57
|
+
# options - set of options to pass to `HalClient#put`
|
58
|
+
def put(data, options={})
|
59
|
+
raise NotImplementedError, "We only puts to singular resources." if count > 1
|
60
|
+
first.put(data, options)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Patch a `Representation` or `String` to the resource.
|
64
|
+
#
|
65
|
+
# NOTE: This only works for a single representation.
|
66
|
+
#
|
67
|
+
# data - a `String` or an object that responds to `#to_hal`
|
68
|
+
# options - set of options to pass to `HalClient#patch`
|
69
|
+
def patch(data, options={})
|
70
|
+
raise NotImplementedError, "We only patchs to singular resources." if count > 1
|
71
|
+
first.patch(data, options)
|
72
|
+
end
|
73
|
+
|
52
74
|
protected
|
53
75
|
|
54
76
|
attr_reader :reprs
|
data/lib/hal_client/version.rb
CHANGED
data/lib/hal_client.rb
CHANGED
@@ -15,6 +15,8 @@ class HalClient
|
|
15
15
|
autoload :HttpClientError, 'hal_client/errors'
|
16
16
|
autoload :HttpServerError, 'hal_client/errors'
|
17
17
|
|
18
|
+
autoload :RepresentationEditor, 'hal_client/representation_editor'
|
19
|
+
|
18
20
|
# Initializes a new client instance
|
19
21
|
#
|
20
22
|
# options - hash of configuration options
|
@@ -79,6 +81,36 @@ class HalClient
|
|
79
81
|
interpret_response client_for_post(override_headers: headers).post(url, body: req_body)
|
80
82
|
end
|
81
83
|
|
84
|
+
# Put a `Representation` or `String` to the resource identified at `url`.
|
85
|
+
#
|
86
|
+
# url - The URL of the resource of interest.
|
87
|
+
# data - a `String` or an object that responds to `#to_hal`
|
88
|
+
# headers - custom header fields to use for this request
|
89
|
+
def put(url, data, headers={})
|
90
|
+
req_body = if data.respond_to? :to_hal
|
91
|
+
data.to_hal
|
92
|
+
else
|
93
|
+
data
|
94
|
+
end
|
95
|
+
|
96
|
+
interpret_response client_for_post(override_headers: headers).put(url, body: req_body)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Patch a `Representation` or `String` to the resource identified at `url`.
|
100
|
+
#
|
101
|
+
# url - The URL of the resource of interest.
|
102
|
+
# data - a `String` or an object that responds to `#to_hal`
|
103
|
+
# headers - custom header fields to use for this request
|
104
|
+
def patch(url, data, headers={})
|
105
|
+
req_body = if data.respond_to? :to_hal
|
106
|
+
data.to_hal
|
107
|
+
else
|
108
|
+
data
|
109
|
+
end
|
110
|
+
|
111
|
+
interpret_response client_for_post(override_headers: headers).patch(url, body: req_body)
|
112
|
+
end
|
113
|
+
|
82
114
|
protected
|
83
115
|
|
84
116
|
attr_reader :headers
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require_relative "../spec_helper"
|
2
|
+
require "hal_client/representation_editor"
|
3
|
+
|
4
|
+
RSpec.describe HalClient::RepresentationEditor do
|
5
|
+
describe "creation" do
|
6
|
+
it "take a representation" do
|
7
|
+
expect(described_class.new(a_repr)).to be_kind_of described_class
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
subject { described_class.new(a_repr) }
|
12
|
+
|
13
|
+
specify { expect(subject.to_hal).to be_equivalent_json_to raw_hal }
|
14
|
+
specify { expect(subject.to_json).to be_equivalent_json_to raw_hal }
|
15
|
+
|
16
|
+
describe "#reject_links" do
|
17
|
+
specify { expect(subject.reject_links("up")).not_to have_link "up" }
|
18
|
+
|
19
|
+
it "removes links matching block but not others" do
|
20
|
+
altered = subject.reject_links("up") {|repr| %r|/c1$| === repr.href }
|
21
|
+
|
22
|
+
expect(altered).not_to have_link "up", with_href("http://example.com/c1")
|
23
|
+
expect(altered).to have_link "up", with_href("http://example.com/c2")
|
24
|
+
end
|
25
|
+
|
26
|
+
specify { expect(subject.reject_links("absent-rel")).to be_equivalent_json_to raw_hal }
|
27
|
+
specify { expect(subject.reject_links("about") {|repr| %r|/another$| === repr.href })
|
28
|
+
.not_to have_link "about" }
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#reject_embedded" do
|
32
|
+
specify { expect(subject.reject_embedded("replies"))
|
33
|
+
.not_to have_embedded "replies" }
|
34
|
+
specify { expect(subject.reject_embedded("absent-rel"))
|
35
|
+
.to be_equivalent_json_to raw_hal }
|
36
|
+
|
37
|
+
it "removes links matching block but not others" do
|
38
|
+
altered = subject.reject_embedded("replies") {|repr| "+1" == repr.property("value") }
|
39
|
+
|
40
|
+
expect(altered).not_to have_embedded "replies", hash_including("value" => "+1")
|
41
|
+
expect(altered).to have_embedded "replies", hash_including("value" => "-1")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#reject_related" do
|
46
|
+
it "rejects from links section" do
|
47
|
+
altered = subject.reject_related("up")
|
48
|
+
|
49
|
+
expect(altered).not_to have_link("up")
|
50
|
+
expect(altered).not_to have_embedded("up")
|
51
|
+
end
|
52
|
+
|
53
|
+
it "rejects from links section matching block" do
|
54
|
+
altered = subject.reject_related("up") { |it| %r|/c1$| === it.href }
|
55
|
+
|
56
|
+
expect(altered).not_to have_link "up", with_href("http://example.com/c1")
|
57
|
+
expect(altered).to have_link "up", with_href("http://example.com/c2")
|
58
|
+
expect(altered).not_to have_embedded("up")
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
it "rejects from embedded section" do
|
63
|
+
altered = subject.reject_related("replies")
|
64
|
+
|
65
|
+
expect(altered).not_to have_link("replies")
|
66
|
+
expect(altered).not_to have_embedded("replies")
|
67
|
+
end
|
68
|
+
|
69
|
+
it "rejects from embedded section matching block" do
|
70
|
+
altered = subject.reject_related("replies") {|it| it["value"] == "+1" }
|
71
|
+
|
72
|
+
expect(altered).not_to have_link("replies", hash_including("value" => "+1"))
|
73
|
+
expect(altered).not_to have_embedded("replies", hash_including("value" => "+1"))
|
74
|
+
|
75
|
+
expect(altered).to have_link("replies", hash_including("value" => "-1"))
|
76
|
+
.or(have_embedded("replies", hash_including("value" => "-1")))
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
specify { expect(subject.reject_related("absent-rel"))
|
81
|
+
.to be_equivalent_json_to raw_hal }
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "#add_link" do
|
86
|
+
it "adds brand new link rel" do
|
87
|
+
expect(subject.add_link("related", "http://example.com/third"))
|
88
|
+
.to have_link("related", with_href("http://example.com/third")
|
89
|
+
.and(not_templated))
|
90
|
+
end
|
91
|
+
|
92
|
+
it "adds without replacing existing links" do
|
93
|
+
expect(subject.add_link("about", "http://example.com/third"))
|
94
|
+
.to have_link("about", with_href("http://example.com/third"))
|
95
|
+
.and have_link("about", with_href("http://example.com/another"))
|
96
|
+
end
|
97
|
+
|
98
|
+
it "adds templated links" do
|
99
|
+
expect(subject.add_link("related", "http://example.com/third{?wat}", templated: true))
|
100
|
+
.to have_link "related", with_href("http://example.com/third{?wat}")
|
101
|
+
.and(be_templated)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Background
|
106
|
+
|
107
|
+
let(:a_repr) { HalClient::Representation
|
108
|
+
.new(parsed_json: MultiJson.load(raw_hal)) }
|
109
|
+
|
110
|
+
let(:raw_hal) { <<-HAL }
|
111
|
+
{ "_links": {
|
112
|
+
"self" : { "href": "http://example.com/a_repr" }
|
113
|
+
,"up" : [{ "href": "http://example.com/c1" },
|
114
|
+
{ "href": "http://example.com/c2" }]
|
115
|
+
,"about": { "href": "http://example.com/another" }
|
116
|
+
}
|
117
|
+
,"_embedded": {
|
118
|
+
"replies": [
|
119
|
+
{ "value": "+1" }
|
120
|
+
,{"value": "-1" }
|
121
|
+
]
|
122
|
+
}
|
123
|
+
}
|
124
|
+
HAL
|
125
|
+
|
126
|
+
ANYTHING = ->(_) { true }
|
127
|
+
|
128
|
+
matcher :be_templated do
|
129
|
+
match do |actual|
|
130
|
+
true == actual["templated"]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
matcher :not_templated do
|
135
|
+
match do |actual|
|
136
|
+
true != actual["templated"]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
matcher :with_href do |expected_url|
|
141
|
+
match do |actual|
|
142
|
+
expected_url === actual["href"]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
matcher :have_link do |expected_rel, expected_target=ANYTHING|
|
147
|
+
match do |actual_json|
|
148
|
+
parsed = MultiJson.load(actual_json.to_hal)
|
149
|
+
|
150
|
+
[parsed["_links"].fetch(expected_rel, [])].flatten
|
151
|
+
.any?{|l| expected_target === l }
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
matcher :have_embedded do |expected_rel, expected_target=ANYTHING|
|
156
|
+
match do |actual_json|
|
157
|
+
parsed = MultiJson.load(actual_json.to_hal)
|
158
|
+
|
159
|
+
[parsed["_embedded"].fetch(expected_rel, [])].flatten
|
160
|
+
.any?{|e| expected_target === e }
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -81,6 +81,42 @@ describe HalClient::RepresentationSet do
|
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
84
|
+
describe "#put" do
|
85
|
+
context "with a single representation" do
|
86
|
+
subject(:repr_single_set) { described_class.new([foo_repr]) }
|
87
|
+
let!(:put_request) { stub_request(:put, "example.com/foo") }
|
88
|
+
|
89
|
+
before(:each) do
|
90
|
+
repr_single_set.put("abc")
|
91
|
+
end
|
92
|
+
|
93
|
+
it "makes an HTTP PUT with the data within the representation" do
|
94
|
+
expect(
|
95
|
+
put_request.
|
96
|
+
with(:body => "abc", :headers => {'Content-Type' => 'application/hal+json'})
|
97
|
+
).to have_been_made
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "#patch" do
|
103
|
+
context "with a single representation" do
|
104
|
+
subject(:repr_single_set) { described_class.new([foo_repr]) }
|
105
|
+
let!(:patch_request) { stub_request(:patch, "example.com/foo") }
|
106
|
+
|
107
|
+
before(:each) do
|
108
|
+
repr_single_set.patch("abc")
|
109
|
+
end
|
110
|
+
|
111
|
+
it "makes an HTTP PATCH with the data within the representation" do
|
112
|
+
expect(
|
113
|
+
patch_request.
|
114
|
+
with(:body => "abc", :headers => {'Content-Type' => 'application/hal+json'})
|
115
|
+
).to have_been_made
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
84
120
|
let(:a_client) { HalClient.new }
|
85
121
|
|
86
122
|
let(:foo_repr) { HalClient::Representation.new hal_client: a_client, parsed_json: MultiJson.load(foo_hal)}
|
@@ -25,11 +25,11 @@ HAL
|
|
25
25
|
|
26
26
|
describe "#post" do
|
27
27
|
let!(:post_request) {
|
28
|
-
stub_request(:post,
|
28
|
+
stub_request(:post, repr.href)
|
29
29
|
}
|
30
30
|
|
31
31
|
before(:each) do
|
32
|
-
repr.
|
32
|
+
repr.post("abc")
|
33
33
|
end
|
34
34
|
|
35
35
|
specify("makes request") {
|
@@ -39,6 +39,38 @@ HAL
|
|
39
39
|
}
|
40
40
|
end
|
41
41
|
|
42
|
+
describe "#put" do
|
43
|
+
let!(:put_request) {
|
44
|
+
stub_request(:put, repr.href)
|
45
|
+
}
|
46
|
+
|
47
|
+
before(:each) do
|
48
|
+
repr.put("abc")
|
49
|
+
end
|
50
|
+
|
51
|
+
specify("makes request") {
|
52
|
+
expect(put_request.with(:body => "abc",
|
53
|
+
:headers => {'Content-Type' => 'application/hal+json'}))
|
54
|
+
.to have_been_made
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#patch" do
|
59
|
+
let!(:patch_request) {
|
60
|
+
stub_request(:patch, repr.href)
|
61
|
+
}
|
62
|
+
|
63
|
+
before(:each) do
|
64
|
+
repr.patch("abc")
|
65
|
+
end
|
66
|
+
|
67
|
+
specify("makes request") {
|
68
|
+
expect(patch_request.with(:body => "abc",
|
69
|
+
:headers => {'Content-Type' => 'application/hal+json'}))
|
70
|
+
.to have_been_made
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
42
74
|
describe "#to_s" do
|
43
75
|
subject(:return_val) { repr.to_s }
|
44
76
|
|
@@ -173,6 +205,10 @@ HAL
|
|
173
205
|
specify { expect(subject.has_related? "no-such-link-or-embed").to be false }
|
174
206
|
specify { expect(subject.related? "no-such-link-or-embed").to be false }
|
175
207
|
|
208
|
+
specify { expect(subject.to_json).to be_equivalent_json_to raw_repr }
|
209
|
+
specify { expect(subject.to_hal).to be_equivalent_json_to raw_repr }
|
210
|
+
|
211
|
+
|
176
212
|
context "curie links" do
|
177
213
|
let(:raw_repr) { <<-HAL }
|
178
214
|
{ "_links": {
|
@@ -270,6 +306,7 @@ HAL
|
|
270
306
|
"Expected representation of <#{url}> but found only #{repr_set.map(&:href)}"
|
271
307
|
}
|
272
308
|
end
|
309
|
+
|
273
310
|
end
|
274
311
|
|
275
312
|
describe HalClient::Representation, "w/o hal_client" do
|
@@ -294,4 +331,6 @@ describe HalClient::Representation, "w/o hal_client" do
|
|
294
331
|
}
|
295
332
|
}
|
296
333
|
HAL
|
334
|
+
|
335
|
+
|
297
336
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
module CustomMatchers
|
2
|
+
extend RSpec::Matchers::DSL
|
3
|
+
|
4
|
+
matcher :be_equivalent_json_to do |expected_json|
|
5
|
+
match do |actual_json|
|
6
|
+
MultiJson.load(json(expected_json)) == MultiJson.load(json(actual_json))
|
7
|
+
end
|
8
|
+
|
9
|
+
protected
|
10
|
+
|
11
|
+
def json(jsonish)
|
12
|
+
if String === jsonish
|
13
|
+
jsonish
|
14
|
+
else
|
15
|
+
jsonish.to_json
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
metadata
CHANGED
@@ -1,148 +1,131 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hal-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
5
|
-
prerelease:
|
4
|
+
version: 3.2.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Peter Williams
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2014-
|
11
|
+
date: 2014-11-17 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: http
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- - ~>
|
17
|
+
- - "~>"
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: 0.6.1
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- - ~>
|
24
|
+
- - "~>"
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: 0.6.1
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: addressable
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
|
-
- - ~>
|
31
|
+
- - "~>"
|
36
32
|
- !ruby/object:Gem::Version
|
37
33
|
version: '2.3'
|
38
34
|
type: :runtime
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
|
-
- - ~>
|
38
|
+
- - "~>"
|
44
39
|
- !ruby/object:Gem::Version
|
45
40
|
version: '2.3'
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: multi_json
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
|
-
- - ~>
|
45
|
+
- - "~>"
|
52
46
|
- !ruby/object:Gem::Version
|
53
47
|
version: '1.9'
|
54
48
|
type: :runtime
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
|
-
- - ~>
|
52
|
+
- - "~>"
|
60
53
|
- !ruby/object:Gem::Version
|
61
54
|
version: '1.9'
|
62
55
|
- !ruby/object:Gem::Dependency
|
63
56
|
name: bundler
|
64
57
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
58
|
requirements:
|
67
|
-
- - ~>
|
59
|
+
- - "~>"
|
68
60
|
- !ruby/object:Gem::Version
|
69
61
|
version: '1.5'
|
70
62
|
type: :development
|
71
63
|
prerelease: false
|
72
64
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
65
|
requirements:
|
75
|
-
- - ~>
|
66
|
+
- - "~>"
|
76
67
|
- !ruby/object:Gem::Version
|
77
68
|
version: '1.5'
|
78
69
|
- !ruby/object:Gem::Dependency
|
79
70
|
name: rake
|
80
71
|
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
72
|
requirements:
|
83
|
-
- - ~>
|
73
|
+
- - "~>"
|
84
74
|
- !ruby/object:Gem::Version
|
85
75
|
version: '10.1'
|
86
76
|
type: :development
|
87
77
|
prerelease: false
|
88
78
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
79
|
requirements:
|
91
|
-
- - ~>
|
80
|
+
- - "~>"
|
92
81
|
- !ruby/object:Gem::Version
|
93
82
|
version: '10.1'
|
94
83
|
- !ruby/object:Gem::Dependency
|
95
84
|
name: rspec
|
96
85
|
requirement: !ruby/object:Gem::Requirement
|
97
|
-
none: false
|
98
86
|
requirements:
|
99
|
-
- - ~>
|
87
|
+
- - "~>"
|
100
88
|
- !ruby/object:Gem::Version
|
101
89
|
version: 3.0.0.beta
|
102
90
|
type: :development
|
103
91
|
prerelease: false
|
104
92
|
version_requirements: !ruby/object:Gem::Requirement
|
105
|
-
none: false
|
106
93
|
requirements:
|
107
|
-
- - ~>
|
94
|
+
- - "~>"
|
108
95
|
- !ruby/object:Gem::Version
|
109
96
|
version: 3.0.0.beta
|
110
97
|
- !ruby/object:Gem::Dependency
|
111
98
|
name: webmock
|
112
99
|
requirement: !ruby/object:Gem::Requirement
|
113
|
-
none: false
|
114
100
|
requirements:
|
115
|
-
- - ~>
|
101
|
+
- - "~>"
|
116
102
|
- !ruby/object:Gem::Version
|
117
103
|
version: '1.17'
|
118
|
-
- -
|
104
|
+
- - ">="
|
119
105
|
- !ruby/object:Gem::Version
|
120
106
|
version: 1.17.4
|
121
107
|
type: :development
|
122
108
|
prerelease: false
|
123
109
|
version_requirements: !ruby/object:Gem::Requirement
|
124
|
-
none: false
|
125
110
|
requirements:
|
126
|
-
- - ~>
|
111
|
+
- - "~>"
|
127
112
|
- !ruby/object:Gem::Version
|
128
113
|
version: '1.17'
|
129
|
-
- -
|
114
|
+
- - ">="
|
130
115
|
- !ruby/object:Gem::Version
|
131
116
|
version: 1.17.4
|
132
117
|
- !ruby/object:Gem::Dependency
|
133
118
|
name: rspec-collection_matchers
|
134
119
|
requirement: !ruby/object:Gem::Requirement
|
135
|
-
none: false
|
136
120
|
requirements:
|
137
|
-
- -
|
121
|
+
- - ">="
|
138
122
|
- !ruby/object:Gem::Version
|
139
123
|
version: '0'
|
140
124
|
type: :development
|
141
125
|
prerelease: false
|
142
126
|
version_requirements: !ruby/object:Gem::Requirement
|
143
|
-
none: false
|
144
127
|
requirements:
|
145
|
-
- -
|
128
|
+
- - ">="
|
146
129
|
- !ruby/object:Gem::Version
|
147
130
|
version: '0'
|
148
131
|
description: An easy to use interface for REST APIs that use HAL.
|
@@ -152,8 +135,8 @@ executables: []
|
|
152
135
|
extensions: []
|
153
136
|
extra_rdoc_files: []
|
154
137
|
files:
|
155
|
-
- .gitignore
|
156
|
-
- .travis.yml
|
138
|
+
- ".gitignore"
|
139
|
+
- ".travis.yml"
|
157
140
|
- Gemfile
|
158
141
|
- LICENSE.txt
|
159
142
|
- README.md
|
@@ -166,51 +149,50 @@ files:
|
|
166
149
|
- lib/hal_client/errors.rb
|
167
150
|
- lib/hal_client/links_section.rb
|
168
151
|
- lib/hal_client/representation.rb
|
152
|
+
- lib/hal_client/representation_editor.rb
|
169
153
|
- lib/hal_client/representation_set.rb
|
170
154
|
- lib/hal_client/version.rb
|
171
155
|
- spec/hal_client/collection_spec.rb
|
172
156
|
- spec/hal_client/curie_resolver_spec.rb
|
173
157
|
- spec/hal_client/links_section_spec.rb
|
158
|
+
- spec/hal_client/representation_editor_spec.rb
|
174
159
|
- spec/hal_client/representation_set_spec.rb
|
175
160
|
- spec/hal_client/representation_spec.rb
|
176
161
|
- spec/hal_client_spec.rb
|
177
162
|
- spec/spec_helper.rb
|
163
|
+
- spec/support/custom_matchers.rb
|
178
164
|
homepage: https://github.com/pezra/hal-client
|
179
165
|
licenses:
|
180
166
|
- MIT
|
167
|
+
metadata: {}
|
181
168
|
post_install_message:
|
182
169
|
rdoc_options: []
|
183
170
|
require_paths:
|
184
171
|
- lib
|
185
172
|
required_ruby_version: !ruby/object:Gem::Requirement
|
186
|
-
none: false
|
187
173
|
requirements:
|
188
|
-
- -
|
174
|
+
- - ">="
|
189
175
|
- !ruby/object:Gem::Version
|
190
176
|
version: '0'
|
191
|
-
segments:
|
192
|
-
- 0
|
193
|
-
hash: 916239915633512664
|
194
177
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
195
|
-
none: false
|
196
178
|
requirements:
|
197
|
-
- -
|
179
|
+
- - ">="
|
198
180
|
- !ruby/object:Gem::Version
|
199
181
|
version: '0'
|
200
|
-
segments:
|
201
|
-
- 0
|
202
|
-
hash: 916239915633512664
|
203
182
|
requirements: []
|
204
183
|
rubyforge_project:
|
205
|
-
rubygems_version:
|
184
|
+
rubygems_version: 2.2.2
|
206
185
|
signing_key:
|
207
|
-
specification_version:
|
186
|
+
specification_version: 4
|
208
187
|
summary: Use HAL APIs easily
|
209
188
|
test_files:
|
210
189
|
- spec/hal_client/collection_spec.rb
|
211
190
|
- spec/hal_client/curie_resolver_spec.rb
|
212
191
|
- spec/hal_client/links_section_spec.rb
|
192
|
+
- spec/hal_client/representation_editor_spec.rb
|
213
193
|
- spec/hal_client/representation_set_spec.rb
|
214
194
|
- spec/hal_client/representation_spec.rb
|
215
195
|
- spec/hal_client_spec.rb
|
216
196
|
- spec/spec_helper.rb
|
197
|
+
- spec/support/custom_matchers.rb
|
198
|
+
has_rdoc:
|