hal-client 4.2.0 → 4.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +15 -0
- data/hal-client.gemspec +1 -0
- data/lib/hal_client/form/field.rb +67 -0
- data/lib/hal_client/form.rb +108 -0
- data/lib/hal_client/representation.rb +13 -0
- data/lib/hal_client/representation_set.rb +21 -0
- data/lib/hal_client/version.rb +1 -1
- data/spec/hal_client/form/field_spec.rb +151 -0
- data/spec/hal_client/form_spec.rb +113 -0
- data/spec/hal_client/representation_set_spec.rb +118 -0
- data/spec/hal_client/representation_spec.rb +92 -0
- data/spec/support/custom_matchers.rb +16 -0
- metadata +22 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 77b2bd2a2ee75f106ff5dfc1ca7af3bb49c53ac7
|
4
|
+
data.tar.gz: 5e2abf209a2e898da64afb88f0118a58a61a4c47
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8bbbb2faa0fcc3cc2f072b9fff4e0acf582509cd554cd0884aaeaa95ddcba4a81fc800970139a37a66550189f7b07eee9d5c4a80a95a301d9fa26b358fa02725
|
7
|
+
data.tar.gz: 25982fec801a367a71d163ce0830e7cbaae826531a1d4d56505d2464a43c3264fb7865768adf740e0aeab7ad05363c5774c838fa1b9ca5d039614d99fdbbe8f1
|
data/README.md
CHANGED
@@ -136,6 +136,21 @@ doc.put(improved_doc) # save c
|
|
136
136
|
|
137
137
|
This removes the obsolete link to "John Doe" from the documents list of authors and replaces it with the correct link then performs an HTTP PUT request with the updated document.
|
138
138
|
|
139
|
+
### Forms
|
140
|
+
|
141
|
+
HalClient supports [Dwolla HAL forms](https://github.com/Dwolla/hal-forms). For example, given a collection with a [`create-form`](https://tools.ietf.org/html/rfc6861#section-3.1) link to a resource with a [`default`](https://github.com/Dwolla/hal-forms#properties) form that creates new members of the collection, the following code would create a new member of `http://example.com/somecollection`.
|
142
|
+
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
collection = HalClient.get("http://example.com/somecollection")
|
146
|
+
create_form = collection.related("create-form").form
|
147
|
+
create_form.submit(
|
148
|
+
name: "my item",
|
149
|
+
author_url: URI("http://example.com/john-doe")
|
150
|
+
description: "super duper!"
|
151
|
+
)
|
152
|
+
|
153
|
+
```
|
139
154
|
### Custom media types
|
140
155
|
|
141
156
|
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.
|
data/hal-client.gemspec
CHANGED
@@ -21,6 +21,7 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.add_dependency "http", ">= 0.8.4", "< 3.0"
|
22
22
|
spec.add_dependency "addressable", "~> 2.3"
|
23
23
|
spec.add_dependency "multi_json", "~> 1.9"
|
24
|
+
spec.add_dependency "hana", "~> 1.3"
|
24
25
|
|
25
26
|
spec.add_development_dependency "bundler", "~> 1.5"
|
26
27
|
spec.add_development_dependency "rake", "~> 10.1"
|
@@ -0,0 +1,67 @@
|
|
1
|
+
class HalClient
|
2
|
+
class Form
|
3
|
+
# A single field in a form.
|
4
|
+
#
|
5
|
+
# Current implementation is very basic. It only understands
|
6
|
+
# `hidden` and `string` field types. All other field types are
|
7
|
+
# treated as `string` per the spec.
|
8
|
+
class Field
|
9
|
+
|
10
|
+
# Initializes a new field.
|
11
|
+
#
|
12
|
+
# parsed_json - the parsed JSON of the field
|
13
|
+
def initialize(parsed_json)
|
14
|
+
@aliases = extract_aliases(parsed_json)
|
15
|
+
@value = extract_value(parsed_json)
|
16
|
+
@type = extract_type(parsed_json)
|
17
|
+
@path = extract_path(parsed_json)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the path to which this field should be encoded in JSON documents, if any.
|
21
|
+
attr_reader :path
|
22
|
+
|
23
|
+
def extract_answer(answers)
|
24
|
+
return value if :hidden == type
|
25
|
+
|
26
|
+
key = aliases.find{|maybe_key| answers.has_key?(maybe_key) }
|
27
|
+
|
28
|
+
coerce_value(answers.fetch(key, value))
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
attr_reader :aliases, :value, :type
|
34
|
+
|
35
|
+
def coerce_value(val)
|
36
|
+
return nil if val.nil?
|
37
|
+
|
38
|
+
val.to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
def extract_aliases(parsed_json)
|
42
|
+
name = parsed_json.fetch("name") {
|
43
|
+
raise ArgumentError, "field doesn't have a name"
|
44
|
+
}
|
45
|
+
|
46
|
+
[name, name.to_sym]
|
47
|
+
end
|
48
|
+
|
49
|
+
def extract_value(parsed_json)
|
50
|
+
parsed_json.fetch("value", nil)
|
51
|
+
end
|
52
|
+
|
53
|
+
def extract_type(parsed_json)
|
54
|
+
case parsed_json["type"]
|
55
|
+
when /hidden/i
|
56
|
+
:hidden
|
57
|
+
else
|
58
|
+
:string
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def extract_path(parsed_json)
|
63
|
+
parsed_json["path"]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'hal_client/form/field'
|
2
|
+
require 'hana'
|
3
|
+
|
4
|
+
class HalClient
|
5
|
+
|
6
|
+
# A single [Dwolla HAL
|
7
|
+
# form](https://github.com/Dwolla/hal-forms). Instances of this
|
8
|
+
# class allow clients to complete and submit individual forms.
|
9
|
+
#
|
10
|
+
# This is an incomplete implementation of the spec. Thus far it
|
11
|
+
# supports string and hidden fields with JSON encoding. Enhancements
|
12
|
+
# are requested.
|
13
|
+
class Form
|
14
|
+
|
15
|
+
# Initializes a newly created form
|
16
|
+
#
|
17
|
+
# parsed_json - a Hash create by parsing the JSON.
|
18
|
+
# hal_client - the `HalClient` with which to submit the new form.
|
19
|
+
def initialize(parsed_json, hal_client)
|
20
|
+
@hal_client = hal_client
|
21
|
+
@target_tmpl = extract_target_tmpl(parsed_json)
|
22
|
+
@method = extract_method(parsed_json)
|
23
|
+
@content_type = extract_content_type(parsed_json)
|
24
|
+
@fields = extract_fields(parsed_json)
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
# Returns the `Addressable::URI` to which this form is targeted
|
29
|
+
def target_url(answers={})
|
30
|
+
target_tmpl.expand(answers)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the `HalClient::Representation` returned from submitting
|
34
|
+
# the form.
|
35
|
+
#
|
36
|
+
# answers - `Hash` containing the answer key to submit. Keys are
|
37
|
+
# the field names; values are the values to submit.
|
38
|
+
def submit(answers={})
|
39
|
+
if :get == method
|
40
|
+
hal_client.get(target_url(answers))
|
41
|
+
else
|
42
|
+
hal_client.public_send(method, target_url(answers), body(answers), "Content-Type" => content_type)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
attr_reader :target_tmpl, :method, :hal_client, :content_type, :fields
|
49
|
+
|
50
|
+
def extract_target_tmpl(parsed_json)
|
51
|
+
tmpl_str = parsed_json
|
52
|
+
.fetch("_links")
|
53
|
+
.fetch("target")
|
54
|
+
.fetch("href")
|
55
|
+
|
56
|
+
Addressable::Template.new(tmpl_str)
|
57
|
+
|
58
|
+
rescue KeyError
|
59
|
+
raise ArgumentError, "form has no target href"
|
60
|
+
end
|
61
|
+
|
62
|
+
def extract_method(parsed_json)
|
63
|
+
parsed_json
|
64
|
+
.fetch("method")
|
65
|
+
.downcase
|
66
|
+
.to_sym
|
67
|
+
|
68
|
+
rescue KeyError
|
69
|
+
raise ArgumentError, "form doesn't specify a method"
|
70
|
+
end
|
71
|
+
|
72
|
+
def extract_content_type(parsed_json)
|
73
|
+
return nil if :get == method
|
74
|
+
|
75
|
+
parsed_json
|
76
|
+
.fetch("contentType") { raise ArgumentError, "form doesn't specify a content type" }
|
77
|
+
end
|
78
|
+
|
79
|
+
def extract_fields(parsed_json)
|
80
|
+
parsed_json
|
81
|
+
.fetch("fields") { raise ArgumentError, "form doesn't have a field member" }
|
82
|
+
.map { |field_json| Field.new(field_json) }
|
83
|
+
end
|
84
|
+
|
85
|
+
def body(answers)
|
86
|
+
case content_type
|
87
|
+
when /json$/i
|
88
|
+
build_json_body(answers)
|
89
|
+
else
|
90
|
+
raise NotImplementedError, "#{content_type} is not a supported content type"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def build_json_body(answers)
|
95
|
+
fields.reduce({}) { |body_thus_far, field|
|
96
|
+
json_inject_answer(body_thus_far, field.extract_answer(answers), field.path)
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
def json_inject_answer(body, answer, path)
|
101
|
+
patch = Hana::Patch.new [
|
102
|
+
{ 'op' => 'add', 'path' => path, 'value' => answer }
|
103
|
+
]
|
104
|
+
|
105
|
+
patch.apply(body)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -5,6 +5,7 @@ require 'hal_client'
|
|
5
5
|
require 'hal_client/representation_set'
|
6
6
|
require 'hal_client/interpreter'
|
7
7
|
require 'hal_client/anonymous_resource_locator'
|
8
|
+
require 'hal_client/form'
|
8
9
|
|
9
10
|
class HalClient
|
10
11
|
# HAL representation of a single resource. Provides access to
|
@@ -262,6 +263,18 @@ class HalClient
|
|
262
263
|
}
|
263
264
|
end
|
264
265
|
|
266
|
+
# Returns the specified `Form`
|
267
|
+
#
|
268
|
+
# form_id - the string or symbol id of the form of interest. Default: `"default"`
|
269
|
+
#
|
270
|
+
# Raises `KeyError` if the specified form doesn't exist.
|
271
|
+
def form(form_id="default")
|
272
|
+
parsed_form_json = property("_forms").fetch(form_id.to_s)
|
273
|
+
|
274
|
+
Form.new(parsed_form_json, hal_client)
|
275
|
+
end
|
276
|
+
|
277
|
+
|
265
278
|
# Resets this representation such that it will be requested from
|
266
279
|
# the upstream on it's next use.
|
267
280
|
def reset
|
@@ -72,6 +72,27 @@ class HalClient
|
|
72
72
|
first.patch(data, options)
|
73
73
|
end
|
74
74
|
|
75
|
+
|
76
|
+
# Returns the specified `Form`
|
77
|
+
#
|
78
|
+
# form_id - the string or symbol id of the form of interest. Default: `"default"`
|
79
|
+
#
|
80
|
+
# Raises `KeyError` if the specified form doesn't exist, or if there are duplicates.
|
81
|
+
def form(form_id="default")
|
82
|
+
self
|
83
|
+
.map { |r|
|
84
|
+
begin
|
85
|
+
r.form(form_id)
|
86
|
+
rescue KeyError
|
87
|
+
nil
|
88
|
+
end }
|
89
|
+
.compact
|
90
|
+
.tap do |fs|
|
91
|
+
raise KeyError, "Duplicate `#{form_id}` forms exist" if fs.count > 1
|
92
|
+
end
|
93
|
+
.first
|
94
|
+
end
|
95
|
+
|
75
96
|
protected
|
76
97
|
|
77
98
|
attr_reader :reprs
|
data/lib/hal_client/version.rb
CHANGED
@@ -0,0 +1,151 @@
|
|
1
|
+
require "hal_client/form/field"
|
2
|
+
|
3
|
+
RSpec.describe HalClient::Form::Field do
|
4
|
+
describe ".new" do
|
5
|
+
it "works for simplest legal form field" do
|
6
|
+
expect(
|
7
|
+
described_class.new({"name" => "rating", "type" => "string"})
|
8
|
+
).to behave_like_a described_class
|
9
|
+
end
|
10
|
+
|
11
|
+
it "works for complex field" do
|
12
|
+
expect(
|
13
|
+
described_class.new({ "name" => "rating",
|
14
|
+
"type" => "string",
|
15
|
+
"path" => "/foo/bar",
|
16
|
+
"value" => "ok",
|
17
|
+
"displayText" => "Rating",
|
18
|
+
"validations" => {
|
19
|
+
"required" => true,
|
20
|
+
"regex" => "(good)|(ok)|(bad)"
|
21
|
+
},
|
22
|
+
"multiple" => true,
|
23
|
+
"accepted" => {
|
24
|
+
"groupedValues" => [
|
25
|
+
{ "key" => "group1",
|
26
|
+
"displayText" => "Group 1",
|
27
|
+
"values" => [
|
28
|
+
{ "value" => "val1id",
|
29
|
+
"key" => "val1",
|
30
|
+
"displayText" => "Value 1"
|
31
|
+
}
|
32
|
+
]
|
33
|
+
}
|
34
|
+
]
|
35
|
+
}
|
36
|
+
})
|
37
|
+
).to behave_like_a described_class
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#extract_answer(answers)" do
|
42
|
+
context "no default value" do
|
43
|
+
subject { described_class.new(string_field_json) }
|
44
|
+
|
45
|
+
it "handles string answer keys" do
|
46
|
+
expect(
|
47
|
+
subject.extract_answer("rating" => "bad")
|
48
|
+
).to eq "bad"
|
49
|
+
end
|
50
|
+
|
51
|
+
it "coerces non-string answers" do
|
52
|
+
expect(
|
53
|
+
subject.extract_answer("rating" => URI("http://example.com/ratings/good"))
|
54
|
+
).to eq "http://example.com/ratings/good"
|
55
|
+
end
|
56
|
+
|
57
|
+
it "handles symbol answer keys" do
|
58
|
+
expect(
|
59
|
+
subject.extract_answer(rating: "bad")
|
60
|
+
).to eq "bad"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "returns nil if answer is missing" do
|
64
|
+
expect(
|
65
|
+
subject.extract_answer(foo: 1)
|
66
|
+
).to be nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "with default value" do
|
71
|
+
subject {
|
72
|
+
described_class.new(field_json(name: "rating", value: "ok"))
|
73
|
+
}
|
74
|
+
|
75
|
+
it "returns default value when answer is missing" do
|
76
|
+
expect(
|
77
|
+
subject.extract_answer(foo: 1)
|
78
|
+
).to eq "ok"
|
79
|
+
end
|
80
|
+
|
81
|
+
it "returns answer when available" do
|
82
|
+
expect(
|
83
|
+
subject.extract_answer(rating: "bad")
|
84
|
+
).to eq "bad"
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
context "hidden field" do
|
90
|
+
subject {
|
91
|
+
described_class.new(field_json(type: "hidden", name: "rating", value: "ok"))
|
92
|
+
}
|
93
|
+
|
94
|
+
it "ignore answer and use default" do
|
95
|
+
expect(
|
96
|
+
subject.extract_answer(rating: "bad")
|
97
|
+
).to eq "ok"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe "#path" do
|
103
|
+
context "path explicitly specified" do
|
104
|
+
subject { described_class.new(field_json(path: "/rating/value")) }
|
105
|
+
|
106
|
+
it "returns the path" do
|
107
|
+
expect(
|
108
|
+
subject.path
|
109
|
+
).to eq "/rating/value"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "path omitted" do
|
114
|
+
subject { described_class.new(field_json(path: MISSING)) }
|
115
|
+
|
116
|
+
it "returns the path" do
|
117
|
+
expect(
|
118
|
+
subject.path
|
119
|
+
).to be nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
MISSING = Object.new
|
125
|
+
|
126
|
+
def field_json(type: "string", name: "rating", path: MISSING, value: MISSING)
|
127
|
+
{ "name" => name,
|
128
|
+
"type" => type }
|
129
|
+
.tap{|f|
|
130
|
+
f["path"] = path if provided?(path)
|
131
|
+
f["value"] = value if provided?(value)
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
def provided?(thing)
|
136
|
+
MISSING != thing
|
137
|
+
end
|
138
|
+
|
139
|
+
def string_field_json
|
140
|
+
field_json(type: "string")
|
141
|
+
end
|
142
|
+
|
143
|
+
def string_field_json_with_default
|
144
|
+
field_json(type: "string", value: "ok")
|
145
|
+
end
|
146
|
+
|
147
|
+
def hidden_field_json
|
148
|
+
field_json(type: "hidden", value: "ok")
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require "hal_client/form"
|
2
|
+
require "hal-client"
|
3
|
+
|
4
|
+
RSpec.describe HalClient::Form do
|
5
|
+
describe ".new" do
|
6
|
+
specify { expect( HalClient::Form.new(fieldless_get_form, a_client) ).to behave_like_a HalClient::Form }
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#target_url" do
|
10
|
+
context "vanilla target" do
|
11
|
+
subject { HalClient::Form.new(fieldless_get_form, a_client) }
|
12
|
+
|
13
|
+
specify { expect( subject.target_url ).to eq(Addressable::URI.parse("http://example.com")) }
|
14
|
+
end
|
15
|
+
|
16
|
+
context "templated target" do
|
17
|
+
subject { HalClient::Form.new(get_form_with_fields_and_templated_target, a_client) }
|
18
|
+
|
19
|
+
specify { expect( subject.target_url(rating: 4, description: "pretty good" ) )
|
20
|
+
.to eq(Addressable::URI.parse("http://example.com/?description=pretty%20good&rating=4")) }
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#submit" do
|
26
|
+
context "fieldless GET form" do
|
27
|
+
subject { HalClient::Form.new(fieldless_get_form, a_client) }
|
28
|
+
let!(:get_request) { stub_request(:get, subject.target_url).to_return(body: "") }
|
29
|
+
|
30
|
+
specify { expect{ subject.submit }.to make_http_request(get_request) }
|
31
|
+
end
|
32
|
+
|
33
|
+
context "GET form w/ fields and templated target" do
|
34
|
+
subject { HalClient::Form.new(get_form_with_fields_and_templated_target, a_client) }
|
35
|
+
|
36
|
+
specify {
|
37
|
+
expect{
|
38
|
+
subject.submit(rating: 4, description: "pretty good")
|
39
|
+
}.to make_http_request(
|
40
|
+
stub_request(:get, subject.target_url(rating: 4, description: "pretty good"))
|
41
|
+
)
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
context "POST form w/ fields and json content type" do
|
46
|
+
subject { HalClient::Form.new(post_form_with_fields_and_json_content_type, a_client) }
|
47
|
+
|
48
|
+
specify {
|
49
|
+
expect{
|
50
|
+
subject.submit(rating: "ok", description: "pretty good")
|
51
|
+
}.to make_http_request(
|
52
|
+
stub_request(:post, subject.target_url)
|
53
|
+
.with(body: { rating: "ok", description: "pretty good" },
|
54
|
+
headers: { "Content-Type" => "application/json" })
|
55
|
+
)
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
# Background
|
62
|
+
|
63
|
+
def fieldless_get_form
|
64
|
+
{ "_links" => {
|
65
|
+
"target" => {
|
66
|
+
"href" => "http://example.com"
|
67
|
+
}
|
68
|
+
},
|
69
|
+
"method" => "GET",
|
70
|
+
"fields" => []
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_form_with_fields_and_templated_target
|
75
|
+
{ "_links" => {
|
76
|
+
"target" => {
|
77
|
+
"href" => "http://example.com/{?description,rating}",
|
78
|
+
"templated" => true
|
79
|
+
}
|
80
|
+
},
|
81
|
+
"method" => "GET",
|
82
|
+
"fields" => [
|
83
|
+
{ "name" => "rating",
|
84
|
+
"type" => "string" },
|
85
|
+
{ "name" => "description",
|
86
|
+
"type" => "text" }
|
87
|
+
]
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
def post_form_with_fields_and_json_content_type
|
92
|
+
{ "_links" => {
|
93
|
+
"target" => {
|
94
|
+
"href" => "http://example.com/"
|
95
|
+
}
|
96
|
+
},
|
97
|
+
"method" => "POST",
|
98
|
+
"contentType" => "application/json",
|
99
|
+
"fields" => [
|
100
|
+
{ "name" => "rating",
|
101
|
+
"type" => "number",
|
102
|
+
"path" => "/rating" },
|
103
|
+
{ "name" => "description",
|
104
|
+
"type" => "text",
|
105
|
+
"path" => "/description" }
|
106
|
+
]
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
def a_client
|
111
|
+
HalClient.new
|
112
|
+
end
|
113
|
+
end
|
@@ -115,6 +115,124 @@ RSpec.describe HalClient::RepresentationSet do
|
|
115
115
|
end
|
116
116
|
end
|
117
117
|
|
118
|
+
describe "#form" do
|
119
|
+
context "single representation with default form" do
|
120
|
+
subject(:repr_single_set) { described_class.new([repr_with_default_form]) }
|
121
|
+
|
122
|
+
specify { expect(
|
123
|
+
subject.form
|
124
|
+
).to behave_like_a HalClient::Form }
|
125
|
+
|
126
|
+
specify { expect(
|
127
|
+
subject.form
|
128
|
+
).to target "default-form" }
|
129
|
+
|
130
|
+
specify { expect(
|
131
|
+
subject.form("default")
|
132
|
+
).to behave_like_a HalClient::Form }
|
133
|
+
|
134
|
+
specify { expect(
|
135
|
+
subject.form("default")
|
136
|
+
).to target "default-form" }
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
context "multiple representations with default form" do
|
141
|
+
subject(:repr_single_set) { described_class.new([repr_with_default_form,
|
142
|
+
repr_with_default_form]) }
|
143
|
+
|
144
|
+
specify { expect{
|
145
|
+
subject.form
|
146
|
+
}.to raise_error KeyError }
|
147
|
+
|
148
|
+
specify { expect{
|
149
|
+
subject.form("default")
|
150
|
+
}.to raise_error KeyError }
|
151
|
+
|
152
|
+
specify { expect{
|
153
|
+
subject.form(:default)
|
154
|
+
}.to raise_error KeyError }
|
155
|
+
end
|
156
|
+
|
157
|
+
context "multiple representations with unique forms" do
|
158
|
+
subject(:repr_single_set) { described_class.new([repr_with_default_form,
|
159
|
+
repr_with_foo_form]) }
|
160
|
+
|
161
|
+
specify { expect(
|
162
|
+
subject.form
|
163
|
+
).to behave_like_a HalClient::Form }
|
164
|
+
|
165
|
+
specify { expect(
|
166
|
+
subject.form
|
167
|
+
).to target "default-form" }
|
168
|
+
|
169
|
+
specify { expect(
|
170
|
+
subject.form("default")
|
171
|
+
).to behave_like_a HalClient::Form }
|
172
|
+
|
173
|
+
specify { expect(
|
174
|
+
subject.form("default")
|
175
|
+
).to target "default-form" }
|
176
|
+
|
177
|
+
|
178
|
+
specify { expect(
|
179
|
+
subject.form("foo")
|
180
|
+
).to target "foo-form" }
|
181
|
+
|
182
|
+
specify { expect(
|
183
|
+
subject.form("foo")
|
184
|
+
).to behave_like_a HalClient::Form }
|
185
|
+
|
186
|
+
specify { expect(
|
187
|
+
subject.form(:foo)
|
188
|
+
).to target "foo-form" }
|
189
|
+
|
190
|
+
specify { expect(
|
191
|
+
subject.form(:foo)
|
192
|
+
).to behave_like_a HalClient::Form }
|
193
|
+
|
194
|
+
end
|
195
|
+
|
196
|
+
matcher :target do |expected_target_url|
|
197
|
+
match do |actual_form|
|
198
|
+
expect(actual_form.target_url.to_s).to eq expected_target_url
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
let(:repr_with_default_form) {
|
203
|
+
HalClient::Representation.new(
|
204
|
+
hal_client: a_client,
|
205
|
+
parsed_json: inject_form(form_json(target: "default-form"), as: "default",
|
206
|
+
into: {}))
|
207
|
+
}
|
208
|
+
|
209
|
+
let(:repr_with_foo_form) {
|
210
|
+
HalClient::Representation.new(
|
211
|
+
hal_client: a_client,
|
212
|
+
parsed_json: inject_form(form_json(target: "foo-form"), as: "foo",
|
213
|
+
into: {}))
|
214
|
+
}
|
215
|
+
|
216
|
+
def inject_form(form_json, as:, into: )
|
217
|
+
into["_forms"] ||= {}
|
218
|
+
into["_forms"][as] = form_json
|
219
|
+
|
220
|
+
into
|
221
|
+
end
|
222
|
+
|
223
|
+
def form_json(target:)
|
224
|
+
{ "_links" => {
|
225
|
+
"target" => {
|
226
|
+
"href" => target
|
227
|
+
}
|
228
|
+
},
|
229
|
+
"method" => "GET",
|
230
|
+
"fields" => []
|
231
|
+
}
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
|
118
236
|
let(:a_client) { HalClient.new }
|
119
237
|
|
120
238
|
let(:foo_repr) { HalClient::Representation.new hal_client: a_client, parsed_json: MultiJson.load(foo_hal)}
|
@@ -119,6 +119,98 @@ HAL
|
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
122
|
+
describe "#form" do
|
123
|
+
context "default form" do
|
124
|
+
subject(:repr) {
|
125
|
+
HalClient::Representation.new(
|
126
|
+
hal_client: a_client,
|
127
|
+
parsed_json: inject_form(form_json(target: "default-form"), as: "default",
|
128
|
+
into: {}))
|
129
|
+
}
|
130
|
+
|
131
|
+
specify { expect(
|
132
|
+
repr.form
|
133
|
+
).to target "default-form" }
|
134
|
+
|
135
|
+
specify { expect(
|
136
|
+
repr.form
|
137
|
+
).to behave_like_a HalClient::Form }
|
138
|
+
|
139
|
+
specify { expect(
|
140
|
+
repr.form("default")
|
141
|
+
).to target "default-form" }
|
142
|
+
|
143
|
+
specify { expect(
|
144
|
+
repr.form("default")
|
145
|
+
).to behave_like_a HalClient::Form }
|
146
|
+
end
|
147
|
+
|
148
|
+
context "non-default form" do
|
149
|
+
subject(:repr) {
|
150
|
+
HalClient::Representation.new(
|
151
|
+
hal_client: a_client,
|
152
|
+
parsed_json: inject_form(form_json(target: "foo-form"), as: "foo", into: {}))
|
153
|
+
}
|
154
|
+
|
155
|
+
specify { expect(
|
156
|
+
repr.form("foo")
|
157
|
+
).to target "foo-form" }
|
158
|
+
|
159
|
+
specify { expect(
|
160
|
+
repr.form(:foo)
|
161
|
+
).to target "foo-form"
|
162
|
+
}
|
163
|
+
|
164
|
+
specify { expect{
|
165
|
+
repr.form("nonexistent")
|
166
|
+
}.to raise_error KeyError }
|
167
|
+
end
|
168
|
+
|
169
|
+
context "multiple forms" do
|
170
|
+
subject(:repr) {
|
171
|
+
HalClient::Representation.new(
|
172
|
+
hal_client: a_client,
|
173
|
+
parsed_json: {}.tap { |hal|
|
174
|
+
inject_form(form_json(target: "foo-form"), as: "foo", into: hal)
|
175
|
+
inject_form(form_json(target: "bar-form"), as: "bar", into: hal)
|
176
|
+
}
|
177
|
+
)
|
178
|
+
}
|
179
|
+
|
180
|
+
specify { expect(
|
181
|
+
repr.form("foo")
|
182
|
+
).to target "foo-form" }
|
183
|
+
|
184
|
+
specify { expect(
|
185
|
+
repr.form("bar")
|
186
|
+
).to target "bar-form" }
|
187
|
+
end
|
188
|
+
|
189
|
+
matcher :target do |expected_target_url|
|
190
|
+
match do |actual_form|
|
191
|
+
expect(actual_form.target_url.to_s).to eq expected_target_url
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def inject_form(form_json, as:, into: )
|
196
|
+
into["_forms"] ||= {}
|
197
|
+
into["_forms"][as] = form_json
|
198
|
+
|
199
|
+
into
|
200
|
+
end
|
201
|
+
|
202
|
+
def form_json(target:)
|
203
|
+
{ "_links" => {
|
204
|
+
"target" => {
|
205
|
+
"href" => target
|
206
|
+
}
|
207
|
+
},
|
208
|
+
"method" => "GET",
|
209
|
+
"fields" => []
|
210
|
+
}
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
122
214
|
context "equality and hash" do
|
123
215
|
let(:repr_same_href) { described_class.new(hal_client: a_client,
|
124
216
|
parsed_json: MultiJson.load(<<-HAL)) }
|
@@ -1,5 +1,10 @@
|
|
1
1
|
module CustomMatchers
|
2
2
|
extend RSpec::Matchers::DSL
|
3
|
+
matcher :behave_like_a do |expected_class|
|
4
|
+
match do |actual_instance|
|
5
|
+
(expected_class.instance_methods - actual_instance.class.instance_methods).empty?
|
6
|
+
end
|
7
|
+
end
|
3
8
|
|
4
9
|
matcher :be_equivalent_json_to do |expected_json|
|
5
10
|
match do |actual_json|
|
@@ -16,4 +21,15 @@ module CustomMatchers
|
|
16
21
|
end
|
17
22
|
end
|
18
23
|
end
|
24
|
+
|
25
|
+
matcher :make_http_request do |expected_request|
|
26
|
+
match do |actual_code_under_test|
|
27
|
+
actual_code_under_test.call()
|
28
|
+
expect(expected_request).to have_been_made
|
29
|
+
end
|
30
|
+
|
31
|
+
def supports_block_expectations?
|
32
|
+
true
|
33
|
+
end
|
34
|
+
end
|
19
35
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hal-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-06-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http
|
@@ -58,6 +58,20 @@ dependencies:
|
|
58
58
|
- - "~>"
|
59
59
|
- !ruby/object:Gem::Version
|
60
60
|
version: '1.9'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: hana
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '1.3'
|
68
|
+
type: :runtime
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '1.3'
|
61
75
|
- !ruby/object:Gem::Dependency
|
62
76
|
name: bundler
|
63
77
|
requirement: !ruby/object:Gem::Requirement
|
@@ -172,6 +186,8 @@ files:
|
|
172
186
|
- lib/hal_client/collection.rb
|
173
187
|
- lib/hal_client/curie_resolver.rb
|
174
188
|
- lib/hal_client/errors.rb
|
189
|
+
- lib/hal_client/form.rb
|
190
|
+
- lib/hal_client/form/field.rb
|
175
191
|
- lib/hal_client/interpreter.rb
|
176
192
|
- lib/hal_client/link.rb
|
177
193
|
- lib/hal_client/links_section.rb
|
@@ -183,6 +199,8 @@ files:
|
|
183
199
|
- lib/hal_client/version.rb
|
184
200
|
- spec/hal_client/collection_spec.rb
|
185
201
|
- spec/hal_client/curie_resolver_spec.rb
|
202
|
+
- spec/hal_client/form/field_spec.rb
|
203
|
+
- spec/hal_client/form_spec.rb
|
186
204
|
- spec/hal_client/interpreter_spec.rb
|
187
205
|
- spec/hal_client/link_spec.rb
|
188
206
|
- spec/hal_client/links_section_spec.rb
|
@@ -220,6 +238,8 @@ summary: Use HAL APIs easily
|
|
220
238
|
test_files:
|
221
239
|
- spec/hal_client/collection_spec.rb
|
222
240
|
- spec/hal_client/curie_resolver_spec.rb
|
241
|
+
- spec/hal_client/form/field_spec.rb
|
242
|
+
- spec/hal_client/form_spec.rb
|
223
243
|
- spec/hal_client/interpreter_spec.rb
|
224
244
|
- spec/hal_client/link_spec.rb
|
225
245
|
- spec/hal_client/links_section_spec.rb
|