hal-client 3.18.0 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -3
- data/README.md +4 -0
- data/lib/hal_client/anonymous_resource_locator.rb +12 -0
- data/lib/hal_client/collection.rb +0 -1
- data/lib/hal_client/interpreter.rb +147 -0
- data/lib/hal_client/link.rb +137 -119
- data/lib/hal_client/representation.rb +74 -129
- data/lib/hal_client/representation_editor.rb +19 -1
- data/lib/hal_client/version.rb +1 -1
- data/lib/hal_client.rb +16 -0
- data/spec/hal_client/collection_spec.rb +0 -6
- data/spec/hal_client/interpreter_spec.rb +117 -0
- data/spec/hal_client/link_spec.rb +28 -73
- data/spec/hal_client/representation_spec.rb +16 -15
- data/spec/spec_helper.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8bfaa5d8d4e7d292a1451a99fc57242f11bdea8a
|
4
|
+
data.tar.gz: 5d94cd3f5b0dbfadd72fb69500961bc3ae7de871
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 509d532e384249bfc7a2c1be053cf52c71dc04bec2d5328520cdbda229a6e637a0ce094b50f7e4e9e8fcbf064ac661c543be8ea51cd48a93fabe1a48c0322d98
|
7
|
+
data.tar.gz: b2421ace2e732764564dade36b67ebf83459877f3c001bb6afe2032c05c9a6ab290e00669f31b82e79c87d40c93938b658d142d658a8ab74bcf0e7f44190221c
|
data/.travis.yml
CHANGED
@@ -8,10 +8,9 @@ before_install:
|
|
8
8
|
- gem install bundler
|
9
9
|
|
10
10
|
rvm:
|
11
|
-
- 2.0
|
12
11
|
- 2.1
|
13
|
-
- 2.2
|
14
|
-
- 2.3
|
12
|
+
- 2.2
|
13
|
+
- 2.3
|
15
14
|
- jruby-9.1.6.0
|
16
15
|
- rbx-2.7
|
17
16
|
|
@@ -20,3 +19,7 @@ gemfile:
|
|
20
19
|
- Gemfile-http-0.8
|
21
20
|
- Gemfile-http-0.9
|
22
21
|
- Gemfile-http-1.0
|
22
|
+
|
23
|
+
matrix:
|
24
|
+
allow_failures:
|
25
|
+
- rvm: rbx-2.7
|
data/README.md
CHANGED
@@ -173,6 +173,10 @@ Or install it yourself as:
|
|
173
173
|
|
174
174
|
$ gem install hal-client
|
175
175
|
|
176
|
+
## Upgrading from 3.x to 4.x
|
177
|
+
|
178
|
+
Support for ruby 2.0 has been removed. Please upgrade to ruby 2.1 or later. No other breaking changes were made.
|
179
|
+
|
176
180
|
## Upgrading from 2.x to 3.x
|
177
181
|
|
178
182
|
For most uses no change to client code is required. At 3.0 the underlying HTTP library changed to <https://rubygems.org/gems/http> to better support our parallelism needs. This changes the interface of `#get` and `#post` on `HalClient` and `HalClient::Representation` in the situation where the response is not a valid HAL document. In those situations the return value is now a `HTTP::Response` object, rather than a `RestClient::Response`.
|
@@ -20,7 +20,6 @@ class HalClient
|
|
20
20
|
# Raises ArgumentError if `first_page` is some page other than
|
21
21
|
# the first of the collection.
|
22
22
|
def initialize(first_page)
|
23
|
-
(fail NotACollectionError) unless first_page.has_related? "item"
|
24
23
|
(fail ArgumentError, "Not the first page of the collection") if first_page.has_related? "prev"
|
25
24
|
|
26
25
|
@first_page = first_page
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'addressable/template'
|
3
|
+
require 'hal_client/representation'
|
4
|
+
require 'hal_client/link'
|
5
|
+
require 'hal_client/curie_resolver'
|
6
|
+
|
7
|
+
class HalClient
|
8
|
+
# Interprets parsed JSON
|
9
|
+
class Interpreter
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
# Collection of reserved properties
|
13
|
+
# https://tools.ietf.org/html/draft-kelly-json-hal-07#section-4.1
|
14
|
+
RESERVED_PROPERTIES = ['_links', '_embedded'].freeze
|
15
|
+
|
16
|
+
def initialize(parsed_json, hal_client)
|
17
|
+
(fail InvalidRepresentationError,
|
18
|
+
"Invalid HAL representation: #{parsed_json.inspect}") unless
|
19
|
+
hashish?(parsed_json)
|
20
|
+
|
21
|
+
@raw = parsed_json
|
22
|
+
@hal_client = hal_client
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns hash of properties from `parsed_json`
|
26
|
+
def extract_props()
|
27
|
+
raw.reject{|k,_| RESERVED_PROPERTIES.include?(k) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def extract_links()
|
31
|
+
extract_embedded_links + extract_basic_links
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
attr_reader :raw, :hal_client
|
37
|
+
|
38
|
+
def hashish?(obj)
|
39
|
+
obj.respond_to?(:[]) &&
|
40
|
+
obj.respond_to?(:map)
|
41
|
+
end
|
42
|
+
|
43
|
+
def extract_basic_links
|
44
|
+
raw
|
45
|
+
.fetch("_links") { Hash.new }
|
46
|
+
.flat_map { |rel, target_info|
|
47
|
+
build_links(rel, target_info)
|
48
|
+
}
|
49
|
+
.compact
|
50
|
+
.to_set
|
51
|
+
end
|
52
|
+
|
53
|
+
def extract_embedded_links
|
54
|
+
raw
|
55
|
+
.fetch("_embedded") { Hash.new }
|
56
|
+
.flat_map { |rel, embedded_json|
|
57
|
+
build_embedded_links(rel, embedded_json)
|
58
|
+
}
|
59
|
+
.compact
|
60
|
+
.to_set
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_links(rel, target_info)
|
64
|
+
arrayify(target_info)
|
65
|
+
.map { |info|
|
66
|
+
if info["templated"]
|
67
|
+
build_templated_link(rel, info)
|
68
|
+
else
|
69
|
+
build_direct_link(rel, info)
|
70
|
+
end
|
71
|
+
}
|
72
|
+
.compact
|
73
|
+
end
|
74
|
+
|
75
|
+
def build_embedded_links(rel, targets_json)
|
76
|
+
arrayify(targets_json)
|
77
|
+
.map{ |target_json|
|
78
|
+
target_repr = Representation.new(parsed_json: target_json, hal_client: hal_client)
|
79
|
+
|
80
|
+
SimpleLink.new(rel: rel,
|
81
|
+
target: target_repr,
|
82
|
+
curie_resolver: curie_resolver)
|
83
|
+
}
|
84
|
+
|
85
|
+
rescue InvalidRepresentationError
|
86
|
+
MalformedLink.new(rel: rel,
|
87
|
+
msg: "/_embedded/#{jpointer_esc(rel)} is invalid",
|
88
|
+
curie_resolver: curie_resolver)
|
89
|
+
end
|
90
|
+
|
91
|
+
def build_templated_link(rel, info)
|
92
|
+
fail(InvalidRepresentationError) unless hashish?(info)
|
93
|
+
|
94
|
+
target_pattern = info.fetch("href") { fail InvalidRepresentationError }
|
95
|
+
|
96
|
+
TemplatedLink.new(rel: rel,
|
97
|
+
template: Addressable::Template.new(target_pattern),
|
98
|
+
curie_resolver: curie_resolver,
|
99
|
+
hal_client: hal_client)
|
100
|
+
|
101
|
+
rescue InvalidRepresentationError
|
102
|
+
MalformedLink.new(rel: rel,
|
103
|
+
msg: "/_links/#{jpointer_esc(rel)} is invalid",
|
104
|
+
curie_resolver: curie_resolver)
|
105
|
+
end
|
106
|
+
|
107
|
+
def build_direct_link(rel, info)
|
108
|
+
fail InvalidRepresentationError unless hashish?(info)
|
109
|
+
|
110
|
+
target_url = info.fetch("href") { fail InvalidRepresentationError }
|
111
|
+
return nil unless target_url
|
112
|
+
|
113
|
+
target_repr = Representation.new(href: target_url, hal_client: hal_client)
|
114
|
+
|
115
|
+
SimpleLink.new(rel: rel,
|
116
|
+
target: target_repr,
|
117
|
+
curie_resolver: curie_resolver)
|
118
|
+
|
119
|
+
rescue InvalidRepresentationError
|
120
|
+
MalformedLink.new(rel: rel,
|
121
|
+
msg: "/_links/#{jpointer_esc(rel)} is invalid",
|
122
|
+
curie_resolver: curie_resolver)
|
123
|
+
end
|
124
|
+
|
125
|
+
def curie_resolver
|
126
|
+
@curie_resolver ||= CurieResolver.new(raw.fetch("_links", {}).fetch("curies", []))
|
127
|
+
end
|
128
|
+
|
129
|
+
def hashish?(thing)
|
130
|
+
thing.kind_of?(Hash) ||
|
131
|
+
thing.respond_to?(:fetch) && thing.respond_to?(:key?)
|
132
|
+
end
|
133
|
+
|
134
|
+
def arrayify(obj)
|
135
|
+
if Array === obj
|
136
|
+
obj
|
137
|
+
else
|
138
|
+
[obj]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def jpointer_esc(str)
|
143
|
+
str.gsub "/", "~1"
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
data/lib/hal_client/link.rb
CHANGED
@@ -4,144 +4,162 @@ class HalClient
|
|
4
4
|
|
5
5
|
# HAL representation of a single link. Provides access to an embedded representation.
|
6
6
|
class Link
|
7
|
+
protected def initialize(rel:, curie_resolver: CurieResolver.new([]), **opts)
|
8
|
+
@literal_rel = rel
|
9
|
+
@curie_resolver = curie_resolver
|
10
|
+
|
11
|
+
post_initialize(opts)
|
12
|
+
end
|
7
13
|
|
8
|
-
# Create a new Link
|
9
|
-
#
|
10
|
-
# options - name parameters
|
11
|
-
# :rel - This Link's rel property
|
12
|
-
# :target - An instance of Representation
|
13
|
-
# :template - A URI template ( https://www.rfc-editor.org/rfc/rfc6570.txt )
|
14
|
-
# :curie_resolver - An instance of CurieResolver (used to resolve curied rels)
|
15
|
-
def initialize(options)
|
16
|
-
@literal_rel = options[:rel]
|
17
|
-
@target = options[:target]
|
18
|
-
@template = options[:template]
|
19
|
-
@curie_resolver = options[:curie_resolver] || CurieResolver.new([])
|
20
|
-
|
21
|
-
(fail ArgumentError, "A rel must be provided") if @literal_rel.nil?
|
22
|
-
|
23
|
-
if @target.nil? && @template.nil?
|
24
|
-
(fail ArgumentError, "A target or template must be provided")
|
25
|
-
end
|
26
|
-
|
27
|
-
if @target && @template
|
28
|
-
(fail ArgumentError, "Cannot provide both a target and a template")
|
29
|
-
end
|
30
|
-
|
31
|
-
if @target && !@target.kind_of?(Representation)
|
32
|
-
(fail ArgumentError, "Invalid HAL representation: #{target.inspect}")
|
33
|
-
end
|
34
|
-
|
35
|
-
if @template && !@template.kind_of?(Addressable::Template)
|
36
|
-
(fail ArgumentError, "Invalid Addressable::Template: #{template.inspect}")
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
attr_accessor :literal_rel, :target, :template, :curie_resolver
|
41
|
-
|
42
|
-
|
43
|
-
# Create a new Link using an entry from the '_links' section of a HAL document
|
44
|
-
#
|
45
|
-
# options - name parameters
|
46
|
-
# :hash_entry - a hash containing keys :rel (string) and :data (hash from a '_links' entry)
|
47
|
-
# :hal_client - an instance of HalClient
|
48
|
-
# :curie_resolver - An instance of CurieResolver (used to resolve curied rels)
|
49
|
-
# :base_url - Base url for resolving relative links in hash_entry (probably the parent
|
50
|
-
# document's "self" link)
|
51
|
-
def self.new_from_link_entry(options)
|
52
|
-
hash_entry = options[:hash_entry]
|
53
|
-
hal_client = options[:hal_client]
|
54
|
-
curie_resolver = options[:curie_resolver]
|
55
|
-
base_url = options[:base_url]
|
56
|
-
|
57
|
-
rel = hash_entry[:rel]
|
58
|
-
hash_data = hash_entry[:data]
|
59
|
-
return nil unless hash_data['href']
|
60
|
-
href = (base_url + hash_data['href']).to_s
|
61
|
-
|
62
|
-
if hash_data['templated']
|
63
|
-
Link.new(rel: rel,
|
64
|
-
template: Addressable::Template.new(href),
|
65
|
-
curie_resolver: curie_resolver)
|
66
|
-
else
|
67
|
-
Link.new(rel: rel,
|
68
|
-
target: Representation.new(hal_client: hal_client, href: href),
|
69
|
-
curie_resolver: curie_resolver)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
|
74
|
-
# Create a new Link using an entry from the '_embedded' section of a HAL document
|
75
|
-
#
|
76
|
-
# options - name parameters
|
77
|
-
# :hash_entry - a hash containing keys :rel (string) and :data (hash from a '_embedded' entry)
|
78
|
-
# :hal_client - an instance of HalClient
|
79
|
-
# :curie_resolver - An instance of CurieResolver (used to resolve curied rels)
|
80
|
-
# :base_url - Base url for resolving relative links in hash_entry (probably the parent
|
81
|
-
# document's "self" link)
|
82
|
-
def self.new_from_embedded_entry(options)
|
83
|
-
hash_entry = options[:hash_entry]
|
84
|
-
hal_client = options[:hal_client]
|
85
|
-
curie_resolver = options[:curie_resolver]
|
86
|
-
base_url = options[:base_url]
|
87
|
-
|
88
|
-
rel = hash_entry[:rel]
|
89
|
-
hash_data = hash_entry[:data]
|
90
|
-
|
91
|
-
explicit_url = self_href(hash_data)
|
92
|
-
hash_data['_links']['self']['href'] = (base_url + explicit_url).to_s if explicit_url
|
93
|
-
|
94
|
-
Link.new(rel: rel,
|
95
|
-
target: Representation.new(hal_client: hal_client, parsed_json: hash_data),
|
96
|
-
curie_resolver: curie_resolver)
|
97
|
-
end
|
98
|
-
|
99
|
-
|
100
|
-
# Returns the URL of the resource this link references.
|
101
|
-
# In the case of a templated link, this is the unresolved url template pattern.
|
102
14
|
def raw_href
|
103
|
-
|
15
|
+
raise NotImplementedError
|
104
16
|
end
|
105
17
|
|
106
|
-
def
|
107
|
-
|
18
|
+
def target_url(vars = {})
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
def target(vars = {})
|
23
|
+
raise NotImplementedError
|
108
24
|
end
|
109
25
|
|
110
|
-
# Returns true for a templated link, false for an ordinary (non-templated) link
|
111
26
|
def templated?
|
112
|
-
|
27
|
+
raise NotImplementedError
|
113
28
|
end
|
114
29
|
|
115
|
-
|
116
|
-
|
30
|
+
attr_reader :literal_rel, :curie_resolver
|
31
|
+
|
32
|
+
def fully_qualified_rel
|
33
|
+
curie_resolver.resolve(literal_rel)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Links with the same href, same rel value, and the same 'templated' value
|
37
|
+
# are considered equal. Otherwise, they are considered unequal. Links
|
38
|
+
# without a href (for example anonymous embedded links, are never equal to
|
39
|
+
# one another.
|
117
40
|
def ==(other)
|
118
|
-
if
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
41
|
+
return false if raw_href.nil?
|
42
|
+
|
43
|
+
return false unless other.respond_to?(:raw_href) &&
|
44
|
+
other.respond_to?(:fully_qualified_rel) &&
|
45
|
+
other.respond_to?(:templated?)
|
46
|
+
|
47
|
+
|
48
|
+
(raw_href == other.raw_href) &&
|
49
|
+
(fully_qualified_rel == other.fully_qualified_rel) &&
|
50
|
+
(templated? == other.templated?)
|
127
51
|
end
|
128
52
|
alias :eql? :==
|
129
53
|
|
130
|
-
|
131
|
-
#
|
132
|
-
#
|
54
|
+
# Differing Representations or Addressable::Templates with matching hrefs
|
55
|
+
# will get matching hash values, since we are using raw_href and not the
|
56
|
+
# objects themselves when computing hash
|
133
57
|
def hash
|
134
|
-
[fully_qualified_rel,
|
58
|
+
[fully_qualified_rel,
|
59
|
+
raw_href,
|
60
|
+
templated?].hash
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def post_initialize(opts)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
# Links that are not templated.
|
71
|
+
class SimpleLink < Link
|
72
|
+
|
73
|
+
protected def post_initialize(target:)
|
74
|
+
fail(ArgumentError) unless target.kind_of?(HalClient::Representation)
|
75
|
+
|
76
|
+
@target = target
|
77
|
+
end
|
78
|
+
|
79
|
+
def raw_href
|
80
|
+
target.href
|
81
|
+
end
|
82
|
+
|
83
|
+
def templated?
|
84
|
+
false
|
85
|
+
end
|
86
|
+
|
87
|
+
def target_url(_vars = {})
|
88
|
+
target.href
|
135
89
|
end
|
136
90
|
|
91
|
+
def target(_vars = {})
|
92
|
+
@target
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
# Links that are templated.
|
98
|
+
class TemplatedLink < Link
|
99
|
+
|
100
|
+
protected def post_initialize(template:, hal_client:)
|
101
|
+
fail(ArgumentError) unless template.kind_of? Addressable::Template
|
102
|
+
@tmpl = template
|
103
|
+
@hal_client = hal_client
|
104
|
+
end
|
105
|
+
|
106
|
+
def raw_href
|
107
|
+
tmpl
|
108
|
+
end
|
109
|
+
|
110
|
+
def templated?
|
111
|
+
true
|
112
|
+
end
|
113
|
+
|
114
|
+
def target_url(vars = {})
|
115
|
+
tmpl.expand(vars)
|
116
|
+
end
|
117
|
+
|
118
|
+
def target(vars = {})
|
119
|
+
hal_client.get(target_url(vars))
|
120
|
+
end
|
121
|
+
|
122
|
+
# Differing Representations or Addressable::Templates with matching hrefs
|
123
|
+
# will get matching hash values, since we are using raw_href and not the
|
124
|
+
# objects themselves when computing hash
|
125
|
+
def hash
|
126
|
+
[fully_qualified_rel,
|
127
|
+
tmpl.pattern,
|
128
|
+
templated?].hash
|
129
|
+
end
|
137
130
|
|
138
131
|
protected
|
139
132
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
133
|
+
attr_reader :tmpl, :hal_client
|
134
|
+
end
|
135
|
+
|
136
|
+
# A link that was malformed in the JSON. This class is used to delay
|
137
|
+
# presenting interpretation errors so that consumers can ignore malformedness
|
138
|
+
# that does not block their goal. For example, busted links that they will not
|
139
|
+
# use anyway.
|
140
|
+
class MalformedLink < Link
|
141
|
+
|
142
|
+
protected def post_initialize(msg:)
|
143
|
+
@msg = msg
|
144
|
+
end
|
145
|
+
|
146
|
+
def raise_invalid(**)
|
147
|
+
raise InvalidRepresentationError, msg
|
148
|
+
end
|
149
|
+
|
150
|
+
alias_method :raw_href, :raise_invalid
|
151
|
+
alias_method :target_url, :raise_invalid
|
152
|
+
alias_method :target, :raise_invalid
|
153
|
+
alias_method :templated?, :raise_invalid
|
154
|
+
|
155
|
+
def hash
|
156
|
+
fully_qualified_rel.hash
|
145
157
|
end
|
158
|
+
|
159
|
+
protected
|
160
|
+
|
161
|
+
attr_reader :msg
|
146
162
|
end
|
163
|
+
|
164
|
+
|
147
165
|
end
|
@@ -3,18 +3,19 @@ require 'addressable/template'
|
|
3
3
|
|
4
4
|
require 'hal_client'
|
5
5
|
require 'hal_client/representation_set'
|
6
|
+
require 'hal_client/interpreter'
|
7
|
+
require 'hal_client/anonymous_resource_locator'
|
6
8
|
|
7
9
|
class HalClient
|
8
|
-
|
9
10
|
# HAL representation of a single resource. Provides access to
|
10
11
|
# properties, links and embedded representations.
|
12
|
+
#
|
13
|
+
# Operations on a representation are not thread-safe. If you'd like to
|
14
|
+
# use representations in a threaded environment, consider using the method
|
15
|
+
# #clone_for_use_in_different_thread to create a copy for each new thread
|
11
16
|
class Representation
|
12
17
|
extend Forwardable
|
13
18
|
|
14
|
-
# Collection of reserved properties
|
15
|
-
# https://tools.ietf.org/html/draft-kelly-json-hal-07#section-4.1
|
16
|
-
RESERVED_PROPERTIES = ['_links', '_embedded'].freeze
|
17
|
-
|
18
19
|
NO_RELATED_RESOURCE = ->(link_rel) {
|
19
20
|
raise KeyError, "No resources are related via `#{link_rel}`"
|
20
21
|
}
|
@@ -37,15 +38,21 @@ class HalClient
|
|
37
38
|
# :href - The href of this representation.
|
38
39
|
# :hal_client - The HalClient instance to use when navigating.
|
39
40
|
def initialize(options)
|
40
|
-
@raw = options[:parsed_json]
|
41
41
|
@hal_client = options[:hal_client]
|
42
42
|
@href = options[:href]
|
43
43
|
|
44
|
+
interpret options[:parsed_json] if options[:parsed_json]
|
45
|
+
|
44
46
|
(fail ArgumentError, "Either parsed_json or href must be provided") if
|
45
47
|
@raw.nil? && @href.nil?
|
48
|
+
end
|
46
49
|
|
47
|
-
|
48
|
-
|
50
|
+
# Returns a copy of this instance that is safe to use in threaded
|
51
|
+
# environments
|
52
|
+
def clone_for_use_in_different_thread
|
53
|
+
clone.tap do |c|
|
54
|
+
c.hal_client = c.hal_client.clone_for_use_in_different_thread
|
55
|
+
end
|
49
56
|
end
|
50
57
|
|
51
58
|
# Posts a `Representation` or `String` to this resource. Causes
|
@@ -86,7 +93,8 @@ class HalClient
|
|
86
93
|
#
|
87
94
|
# name - the name of the property to check
|
88
95
|
def property?(name)
|
89
|
-
|
96
|
+
ensure_reified
|
97
|
+
properties.key? name
|
90
98
|
end
|
91
99
|
alias_method :has_property?, :property?
|
92
100
|
|
@@ -103,24 +111,24 @@ class HalClient
|
|
103
111
|
# Raises KeyError if the specified property does not exist
|
104
112
|
# and no default nor default_proc is provided.
|
105
113
|
def property(name, default=MISSING, &default_proc)
|
114
|
+
ensure_reified
|
115
|
+
|
106
116
|
default_proc ||= ->(_){ default} if default != MISSING
|
107
117
|
|
108
|
-
|
118
|
+
properties.fetch(name.to_s, &default_proc)
|
109
119
|
end
|
110
120
|
|
111
121
|
# Returns a Hash including the key-value pairs of all the properties
|
112
122
|
# in the resource. It does not include HAL's reserved
|
113
123
|
# properties (`_links` and `_embedded`).
|
114
|
-
|
115
|
-
raw.reject { |k, _| RESERVED_PROPERTIES.include? k }
|
116
|
-
end
|
124
|
+
attr_reader :properties
|
117
125
|
|
118
126
|
# Returns the URL of the resource this representation represents.
|
119
127
|
def href
|
120
128
|
@href ||= raw
|
121
129
|
.fetch("_links",{})
|
122
130
|
.fetch("self",{})
|
123
|
-
.fetch("href",
|
131
|
+
.fetch("href", AnonymousResourceLocator.new)
|
124
132
|
end
|
125
133
|
|
126
134
|
# Returns the value of the specified property or representations
|
@@ -159,7 +167,7 @@ class HalClient
|
|
159
167
|
#
|
160
168
|
# link_rel - The link rel of interest
|
161
169
|
def related?(link_rel)
|
162
|
-
|
170
|
+
links_by_rel.key?(link_rel)
|
163
171
|
end
|
164
172
|
alias_method :has_related?, :related?
|
165
173
|
|
@@ -178,33 +186,13 @@ class HalClient
|
|
178
186
|
def related(link_rel, options = {}, &default_proc)
|
179
187
|
default_proc ||= NO_RELATED_RESOURCE
|
180
188
|
|
181
|
-
|
182
|
-
linked = linked(link_rel, options) { nil }
|
183
|
-
return default_proc.call(link_rel) if embedded.nil? and linked.nil?
|
189
|
+
ensure_reified
|
184
190
|
|
185
|
-
|
186
|
-
|
191
|
+
related = links_by_rel
|
192
|
+
.fetch(link_rel) { return default_proc.call(link_rel) }
|
193
|
+
.map { |l| l.target(options) }
|
187
194
|
|
188
|
-
|
189
|
-
result = Set.new
|
190
|
-
base_url = Addressable::URI.parse(href || "")
|
191
|
-
|
192
|
-
embedded_entries = flatten_section(raw.fetch("_embedded", {}))
|
193
|
-
result.merge(embedded_entries.map do |entry|
|
194
|
-
Link.new_from_embedded_entry(hash_entry: entry,
|
195
|
-
hal_client: hal_client,
|
196
|
-
curie_resolver: namespaces,
|
197
|
-
base_url: base_url)
|
198
|
-
end)
|
199
|
-
|
200
|
-
link_entries = flatten_section(raw.fetch("_links", {}))
|
201
|
-
result.merge(link_entries.map { |entry|
|
202
|
-
Link.new_from_link_entry(hash_entry: entry,
|
203
|
-
hal_client: hal_client,
|
204
|
-
curie_resolver: namespaces,
|
205
|
-
base_url: base_url) })
|
206
|
-
|
207
|
-
result
|
195
|
+
RepresentationSet.new(related)
|
208
196
|
end
|
209
197
|
|
210
198
|
# Returns urls of resources related via the specified
|
@@ -241,11 +229,11 @@ class HalClient
|
|
241
229
|
def raw_related_hrefs(link_rel, &default_proc)
|
242
230
|
default_proc ||= NO_RELATED_RESOURCE
|
243
231
|
|
244
|
-
|
245
|
-
linked = links.hrefs(link_rel) { nil }
|
246
|
-
return default_proc.call(link_rel) if embedded.nil? and linked.nil?
|
232
|
+
ensure_reified
|
247
233
|
|
248
|
-
|
234
|
+
links_by_rel
|
235
|
+
.fetch(link_rel) { return default_proc.call(link_rel) }
|
236
|
+
.map { |l| l.raw_href }
|
249
237
|
end
|
250
238
|
|
251
239
|
# Returns an Enumerable of the items in this collection resource
|
@@ -266,6 +254,16 @@ class HalClient
|
|
266
254
|
as_enum.to_enum(method, *args, &blk)
|
267
255
|
end
|
268
256
|
|
257
|
+
# Returns set of all links in this representation.
|
258
|
+
def all_links
|
259
|
+
links_by_rel
|
260
|
+
.reduce(Set.new) { |result, kv|
|
261
|
+
_,links = *kv
|
262
|
+
links.each { |l| result << l }
|
263
|
+
result
|
264
|
+
}
|
265
|
+
end
|
266
|
+
|
269
267
|
# Resets this representation such that it will be requested from
|
270
268
|
# the upstream on it's next use.
|
271
269
|
def reset
|
@@ -276,7 +274,7 @@ class HalClient
|
|
276
274
|
# Returns a short human readable description of this
|
277
275
|
# representation.
|
278
276
|
def to_s
|
279
|
-
"#<" + self.class.name + ": " +
|
277
|
+
"#<" + self.class.name + ": " + href.to_s + ">"
|
280
278
|
end
|
281
279
|
|
282
280
|
# Returns the raw json representation of this representation
|
@@ -304,109 +302,56 @@ class HalClient
|
|
304
302
|
end
|
305
303
|
alias :eql? :==
|
306
304
|
|
307
|
-
#
|
305
|
+
# Returns raw parsed json.
|
308
306
|
def raw
|
309
|
-
|
310
|
-
(fail "unable to make requests due to missing hal client") unless hal_client
|
311
|
-
|
312
|
-
response = hal_client.get(@href)
|
313
|
-
|
314
|
-
unless response.is_a?(Representation)
|
315
|
-
error_message = "Response body wasn't a valid HAL document:\n\n"
|
316
|
-
error_message += response.body
|
317
|
-
raise InvalidRepresentationError.new(error_message)
|
318
|
-
end
|
319
|
-
|
320
|
-
@raw ||= response.raw
|
321
|
-
end
|
307
|
+
ensure_reified
|
322
308
|
|
323
309
|
@raw
|
324
310
|
end
|
325
311
|
|
326
|
-
#
|
327
|
-
# representation
|
312
|
+
# Return the HalClient used to retrieve this representation
|
328
313
|
attr_reader :hal_client
|
329
314
|
|
330
315
|
protected
|
331
316
|
|
332
|
-
|
317
|
+
attr_reader :links_by_rel
|
318
|
+
attr_writer :hal_client
|
333
319
|
|
334
|
-
|
335
|
-
section_hash
|
336
|
-
.each_pair
|
337
|
-
.flat_map { |rel, some_link_info|
|
338
|
-
[some_link_info].flatten
|
339
|
-
.map { |a_link_info| { rel: rel, data: a_link_info } }
|
340
|
-
}
|
341
|
-
end
|
342
|
-
|
343
|
-
def links
|
344
|
-
@links ||= LinksSection.new((raw.fetch("_links"){{}}),
|
345
|
-
base_url: Addressable::URI.parse(href || ""))
|
346
|
-
end
|
347
|
-
|
348
|
-
def embedded_section
|
349
|
-
embedded = raw.fetch("_embedded", {})
|
350
|
-
|
351
|
-
@embedded_section ||= embedded.merge fully_qualified(embedded)
|
352
|
-
end
|
353
|
-
|
354
|
-
def embedded(link_rel, &default_proc)
|
355
|
-
default_proc ||= NO_EMBED_FOUND
|
356
|
-
|
357
|
-
relations = embedded_section.fetch(link_rel) { MISSING }
|
358
|
-
return default_proc.call(link_rel) if relations == MISSING
|
359
|
-
|
360
|
-
(boxed relations).map{|it| Representation.new hal_client: hal_client, parsed_json: it}
|
361
|
-
|
362
|
-
rescue InvalidRepresentationError
|
363
|
-
fail InvalidRepresentationError, "/_embedded/#{jpointer_esc(link_rel)} is not a valid representation"
|
364
|
-
end
|
320
|
+
MISSING = Object.new
|
365
321
|
|
366
|
-
|
367
|
-
|
322
|
+
# Fetch the representation from origin server if that has not already
|
323
|
+
# happened.
|
324
|
+
def ensure_reified
|
325
|
+
return if @raw
|
326
|
+
(fail "unable to make requests due to missing hal client") unless hal_client
|
327
|
+
(fail "unable to make requests due to missing href") unless @href
|
368
328
|
|
369
|
-
|
370
|
-
return default_proc.call(link_rel, options) if relations == MISSING || relations.compact.empty?
|
329
|
+
response = hal_client.get(@href)
|
371
330
|
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
url_or_tmpl
|
378
|
-
end }
|
379
|
-
.map {|href| Representation.new href: href, hal_client: hal_client }
|
331
|
+
unless response.is_a?(Representation)
|
332
|
+
error_message = "Response body wasn't a valid HAL document:\n\n"
|
333
|
+
error_message += response.body
|
334
|
+
raise InvalidRepresentationError.new(error_message)
|
335
|
+
end
|
380
336
|
|
381
|
-
|
382
|
-
fail InvalidRepresentationError, "/_links/#{jpointer_esc(link_rel)} is not a valid link"
|
337
|
+
interpret response.raw
|
383
338
|
end
|
384
339
|
|
385
|
-
def
|
386
|
-
|
387
|
-
end
|
340
|
+
def interpret(parsed_json)
|
341
|
+
@raw = parsed_json
|
388
342
|
|
389
|
-
|
390
|
-
if hashish? list_hash_or_nil
|
391
|
-
[list_hash_or_nil]
|
392
|
-
elsif list_hash_or_nil.respond_to? :map
|
393
|
-
list_hash_or_nil
|
394
|
-
else
|
395
|
-
# The only valid values for a link/embedded set are hashes or
|
396
|
-
# array-ish things.
|
343
|
+
interpreter = HalClient::Interpreter.new(parsed_json, hal_client)
|
397
344
|
|
398
|
-
|
399
|
-
end
|
400
|
-
end
|
401
|
-
|
402
|
-
def fully_qualified(relations_section)
|
403
|
-
Hash[relations_section.map {|rel, link_info|
|
404
|
-
[(namespaces.resolve rel), link_info]
|
405
|
-
}]
|
406
|
-
end
|
345
|
+
@properties = interpreter.extract_props
|
407
346
|
|
408
|
-
|
409
|
-
|
347
|
+
@links_by_rel =
|
348
|
+
interpreter
|
349
|
+
.extract_links
|
350
|
+
.reduce(Hash.new { |h,k| h[k] = Set.new }) { |links_tab, link|
|
351
|
+
links_tab[link.literal_rel] << link
|
352
|
+
links_tab[link.fully_qualified_rel] << link
|
353
|
+
links_tab
|
354
|
+
}
|
410
355
|
end
|
411
356
|
|
412
357
|
def_delegators :links, :namespaces
|
@@ -35,11 +35,17 @@ class HalClient
|
|
35
35
|
|
36
36
|
# Returns true if this, or any previous, editor actually changed the hal
|
37
37
|
# representation.
|
38
|
+
#
|
39
|
+
# ---
|
40
|
+
#
|
41
|
+
# Anonymous entries are hard to deal with in a logically clean way. We fudge
|
42
|
+
# it a bit by treating anonymous resources with the same raw value as equal.
|
38
43
|
def dirty?
|
39
44
|
new_repr = Representation.new(parsed_json: raw)
|
40
45
|
|
41
46
|
orig_repr.properties != new_repr.properties ||
|
42
|
-
orig_repr.all_links != new_repr.all_links
|
47
|
+
sans_anon(orig_repr.all_links) != sans_anon(new_repr.all_links) ||
|
48
|
+
raw_anons(orig_repr.all_links) != raw_anons(new_repr.all_links)
|
43
49
|
end
|
44
50
|
|
45
51
|
# Returns the raw json representation of this representation
|
@@ -140,6 +146,18 @@ class HalClient
|
|
140
146
|
|
141
147
|
attr_reader :orig_repr
|
142
148
|
|
149
|
+
def sans_anon(links)
|
150
|
+
links.reject { |l| AnonymousResourceLocator === l.raw_href}
|
151
|
+
.to_set
|
152
|
+
end
|
153
|
+
|
154
|
+
def raw_anons(links)
|
155
|
+
links
|
156
|
+
.select { |l| AnonymousResourceLocator === l.raw_href}
|
157
|
+
.map!{ |l| l.target.raw }
|
158
|
+
.to_set
|
159
|
+
end
|
160
|
+
|
143
161
|
def Array(thing)
|
144
162
|
if Hash === thing
|
145
163
|
[thing]
|
data/lib/hal_client/version.rb
CHANGED
data/lib/hal_client.rb
CHANGED
@@ -4,6 +4,11 @@ require 'multi_json'
|
|
4
4
|
require 'benchmark'
|
5
5
|
|
6
6
|
# Adapter used to access resources.
|
7
|
+
#
|
8
|
+
# Operations on a HalClient instance are not thread-safe. If you'd like to
|
9
|
+
# use a HalClient instance in a threaded environment, consider using the
|
10
|
+
# method #clone_for_use_in_different_thread to create a copy for each new
|
11
|
+
# thread
|
7
12
|
class HalClient
|
8
13
|
autoload :Representation, 'hal_client/representation'
|
9
14
|
autoload :RepresentationSet, 'hal_client/representation_set'
|
@@ -76,6 +81,11 @@ class HalClient
|
|
76
81
|
end
|
77
82
|
protected :initialize
|
78
83
|
|
84
|
+
# Returns a copy of this instance that is safe to use in threaded environments
|
85
|
+
def clone_for_use_in_different_thread
|
86
|
+
clone.tap { |c| c.clear_clients! }
|
87
|
+
end
|
88
|
+
|
79
89
|
# Returns a `Representation` of the resource identified by `url`.
|
80
90
|
#
|
81
91
|
# url - The URL of the resource of interest.
|
@@ -229,6 +239,12 @@ class HalClient
|
|
229
239
|
base_client_with_headers(headers)
|
230
240
|
end
|
231
241
|
|
242
|
+
# Resets memoized HTTP clients
|
243
|
+
def clear_clients!
|
244
|
+
@base_client = nil
|
245
|
+
@base_client_with_headers = {}
|
246
|
+
end
|
247
|
+
|
232
248
|
# Returns an HTTP client.
|
233
249
|
def base_client
|
234
250
|
@base_client ||= begin
|
@@ -37,17 +37,11 @@ RSpec.describe HalClient::Collection do
|
|
37
37
|
.not_to raise_error
|
38
38
|
end
|
39
39
|
|
40
|
-
specify do
|
41
|
-
expect { described_class.new(non_collection_repr) }
|
42
|
-
.to raise_error HalClient::NotACollectionError
|
43
|
-
end
|
44
|
-
|
45
40
|
specify do
|
46
41
|
expect { described_class.new(non_first_page) }
|
47
42
|
.to raise_error ArgumentError, /first page/
|
48
43
|
end
|
49
44
|
|
50
|
-
let(:non_collection_repr) { repr({}) }
|
51
45
|
let(:non_first_page) { collection_page(prev_href: "http://example.com/p1") }
|
52
46
|
end
|
53
47
|
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'hal_client/interpreter'
|
2
|
+
|
3
|
+
RSpec.describe HalClient::Interpreter do
|
4
|
+
let(:hal_client) { HalClient.new }
|
5
|
+
|
6
|
+
describe ".new" do
|
7
|
+
specify { expect(described_class.new({}, hal_client)).to be_kind_of described_class }
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
describe "#extract_props" do
|
12
|
+
subject { described_class.new(parsed_json, hal_client) }
|
13
|
+
let(:parsed_json) { {"num" => 1,
|
14
|
+
"_links" => {},
|
15
|
+
"_embedded" => {}
|
16
|
+
} }
|
17
|
+
|
18
|
+
specify { expect(subject.extract_props).to be_kind_of Hash }
|
19
|
+
specify { expect(subject.extract_props).to include("num") }
|
20
|
+
specify { expect(subject.extract_props).not_to include("_links") }
|
21
|
+
specify { expect(subject.extract_props).not_to include("_embedded") }
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
describe "#extract_links" do
|
26
|
+
subject { described_class.new({}, hal_client) }
|
27
|
+
|
28
|
+
specify { expect(subject.extract_links).to be_kind_of Enumerable }
|
29
|
+
|
30
|
+
context "with out links" do
|
31
|
+
subject { described_class.new({}, hal_client) }
|
32
|
+
|
33
|
+
specify { expect(subject.extract_links).to be_empty }
|
34
|
+
end
|
35
|
+
|
36
|
+
context "with links" do
|
37
|
+
subject { described_class.new(links_json, hal_client) }
|
38
|
+
let(:links_json) { {"_links" => {
|
39
|
+
"foo" => { "href" => "http://example.com/foo" },
|
40
|
+
"bar" => [ { "href" => "http://example.com/bar1" },
|
41
|
+
{ "href" => "http://example.com/bar2" } ],
|
42
|
+
"tmpl" => { "href" => "http://example.com/foo{?q}",
|
43
|
+
"templated" => true},
|
44
|
+
"mixed" => [ { "href" => "http://example.com/foo" },
|
45
|
+
{ "href" => "http://example.com/foo{?q}",
|
46
|
+
"templated" => true } ]
|
47
|
+
} } }
|
48
|
+
|
49
|
+
specify { expect(subject.extract_links).not_to be_empty }
|
50
|
+
specify { expect(subject.extract_links)
|
51
|
+
.to include link_matching("foo", "http://example.com/foo") }
|
52
|
+
specify { expect(subject.extract_links)
|
53
|
+
.to include link_matching("bar", "http://example.com/bar1") }
|
54
|
+
specify { expect(subject.extract_links)
|
55
|
+
.to include link_matching("bar", "http://example.com/bar2") }
|
56
|
+
specify { expect(subject.extract_links)
|
57
|
+
.to include link_matching("mixed", "http://example.com/foo") }
|
58
|
+
|
59
|
+
specify { expect(subject.extract_links)
|
60
|
+
.to include templated_link_matching("tmpl", "http://example.com/foo{?q}") }
|
61
|
+
specify { expect(subject.extract_links)
|
62
|
+
.to include templated_link_matching("mixed", "http://example.com/foo{?q}") }
|
63
|
+
end
|
64
|
+
|
65
|
+
context "with embedded" do
|
66
|
+
subject { described_class.new(embedded_json, hal_client) }
|
67
|
+
let(:embedded_json) { {"_links" => {
|
68
|
+
"foo" => { "href" => "http://example.com/foo" }
|
69
|
+
},
|
70
|
+
"_embedded" => {
|
71
|
+
"bar" => { "_links" => { "self" => { "href" => "http://example.com/bar" } } },
|
72
|
+
"baz" => [ { "_links" => { "self" => { "href" => "http://example.com/baz1" } } },
|
73
|
+
{ "_links" => { "self" => { "href" => "http://example.com/baz2" } } } ]
|
74
|
+
}
|
75
|
+
} }
|
76
|
+
|
77
|
+
specify { expect(subject.extract_links).not_to be_empty }
|
78
|
+
specify { expect(subject.extract_links).to include link_matching("foo", "http://example.com/foo") }
|
79
|
+
specify { expect(subject.extract_links).to include link_matching("bar", "http://example.com/bar") }
|
80
|
+
specify { expect(subject.extract_links).to include link_matching("baz", "http://example.com/baz1") }
|
81
|
+
specify { expect(subject.extract_links).to include link_matching("baz", "http://example.com/baz2") }
|
82
|
+
end
|
83
|
+
|
84
|
+
context "curies" do
|
85
|
+
let(:raw_repr) { <<-HAL }
|
86
|
+
{ "_links": {
|
87
|
+
"self": { "href": "http://example.com/foo" }
|
88
|
+
,"ex:bar": { "href": "http://example.com/bar" }
|
89
|
+
,"curies": [{"name": "ex", "href": "http://example.com/rels/{rel}", "templated": true}]
|
90
|
+
}
|
91
|
+
}
|
92
|
+
HAL
|
93
|
+
|
94
|
+
subject { described_class.new(MultiJson.load(raw_repr), hal_client) }
|
95
|
+
|
96
|
+
specify { expect(subject.extract_links).to include link_matching("ex:bar", "http://example.com/bar") }
|
97
|
+
specify { expect(subject.extract_links).to include link_matching("http://example.com/rels/bar", "http://example.com/bar") }
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
matcher :link_matching do |rel, target_url|
|
103
|
+
match { |actual_link|
|
104
|
+
(actual_link.literal_rel == rel || actual_link.fully_qualified_rel == rel) &&
|
105
|
+
actual_link.target_url == target_url
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
matcher :templated_link_matching do |rel, target_url|
|
110
|
+
match { |actual_link|
|
111
|
+
(actual_link.literal_rel == rel || actual_link.fully_qualified_rel == rel) &&
|
112
|
+
actual_link.raw_href == Addressable::Template.new(target_url) &&
|
113
|
+
actual_link.templated?
|
114
|
+
}
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
@@ -4,89 +4,37 @@ require "hal_client/link"
|
|
4
4
|
|
5
5
|
RSpec.describe HalClient::Link do
|
6
6
|
|
7
|
-
subject(:link) {
|
7
|
+
subject(:link) { HalClient::SimpleLink.new(rel: rel_1, target: repr_1) }
|
8
8
|
|
9
9
|
describe "#initialize" do
|
10
10
|
it "requires a target" do
|
11
|
-
expect { HalClient::
|
11
|
+
expect { HalClient::SimpleLink.new(rel: rel_1) }.to raise_error(ArgumentError)
|
12
12
|
end
|
13
13
|
|
14
14
|
it "doesn't allow both target and template" do
|
15
15
|
expect {
|
16
|
-
HalClient::
|
16
|
+
HalClient::SimpleLink.new(rel: rel_1, target: repr_1, template: template_1)
|
17
17
|
}.to raise_error(ArgumentError)
|
18
18
|
end
|
19
19
|
|
20
20
|
it "requires target to be a Representation" do
|
21
21
|
expect {
|
22
|
-
HalClient::
|
22
|
+
HalClient::SimpleLink.new(rel: rel_1, target: template_1)
|
23
23
|
}.to raise_error(ArgumentError)
|
24
24
|
end
|
25
25
|
|
26
26
|
it "requires template to be an Addressable::Template" do
|
27
27
|
expect {
|
28
|
-
HalClient::
|
28
|
+
HalClient::TemplatedLink.new(rel: rel_1, template: repr_1)
|
29
29
|
}.to raise_error(ArgumentError)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
describe ".new_from_link_entry" do
|
34
|
-
it "creates an instance of Link" do
|
35
|
-
my_link = described_class.new_from_link_entry(hash_entry: link_entry_hash(href: href_1),
|
36
|
-
hal_client: a_client,
|
37
|
-
curie_resolver: curie_resolver,
|
38
|
-
base_url: href_1)
|
39
|
-
expect(my_link).to be_a(HalClient::Link)
|
40
|
-
end
|
41
|
-
|
42
|
-
it "handles relative hrefs" do
|
43
|
-
input_hash = link_entry_hash(href: relative_href_1)
|
44
|
-
base_url = Addressable::URI.parse(href_1)
|
45
|
-
|
46
|
-
my_link = described_class.new_from_link_entry(hash_entry: input_hash,
|
47
|
-
hal_client: a_client,
|
48
|
-
curie_resolver: curie_resolver,
|
49
|
-
base_url: base_url)
|
50
|
-
expect(my_link.raw_href).to eq((base_url + relative_href_1).to_s)
|
51
|
-
end
|
52
|
-
|
53
|
-
it "handles hrefs with a nil value" do
|
54
|
-
input_hash = link_entry_hash(href: nil)
|
55
|
-
base_url = Addressable::URI.parse(href_1)
|
56
|
-
|
57
|
-
my_link = described_class.new_from_link_entry(hash_entry: input_hash,
|
58
|
-
hal_client: a_client,
|
59
|
-
curie_resolver: curie_resolver,
|
60
|
-
base_url: base_url)
|
61
|
-
|
62
|
-
expect(my_link.raw_href).to eq(base_url.to_s)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
describe ".new_from_embedded_entry" do
|
67
|
-
it "creates an instance of Link" do
|
68
|
-
my_link = described_class.new_from_embedded_entry(hash_entry: embedded_entry_hash,
|
69
|
-
hal_client: a_client,
|
70
|
-
curie_resolver: curie_resolver,
|
71
|
-
base_url: href_1)
|
72
|
-
expect(my_link).to be_a(HalClient::Link)
|
73
|
-
end
|
74
|
-
|
75
|
-
it "handles relative hrefs" do
|
76
|
-
input_hash = embedded_entry_hash(href: relative_href_1)
|
77
|
-
base_url = Addressable::URI.parse(href_1)
|
78
|
-
|
79
|
-
my_link = described_class.new_from_embedded_entry(hash_entry: input_hash,
|
80
|
-
hal_client: a_client,
|
81
|
-
curie_resolver: curie_resolver,
|
82
|
-
base_url: base_url)
|
83
|
-
expect(my_link.raw_href).to eq((base_url + relative_href_1).to_s)
|
84
|
-
end
|
85
|
-
end
|
86
33
|
|
87
34
|
describe "#href" do
|
88
35
|
specify { expect(link.raw_href).to eq('http://example.com/href_1') }
|
89
|
-
specify { expect(templated_link1.raw_href)
|
36
|
+
specify { expect(templated_link1.raw_href)
|
37
|
+
.to eq Addressable::Template.new('http://example.com/people{?name}') }
|
90
38
|
end
|
91
39
|
|
92
40
|
describe "#templated?" do
|
@@ -95,27 +43,33 @@ RSpec.describe HalClient::Link do
|
|
95
43
|
end
|
96
44
|
|
97
45
|
context "equality and hash" do
|
98
|
-
let(:link_same_target_same_rel) { HalClient::
|
46
|
+
let(:link_same_target_same_rel) { HalClient::SimpleLink.new(target: repr_1, rel: rel_1) }
|
99
47
|
|
100
|
-
let(:link_same_target_diff_rel) { HalClient::
|
101
|
-
let(:link_diff_target_same_rel) { HalClient::
|
102
|
-
let(:link_diff_target_diff_rel) { HalClient::
|
48
|
+
let(:link_same_target_diff_rel) { HalClient::SimpleLink.new(target: repr_1, rel: rel_2) }
|
49
|
+
let(:link_diff_target_same_rel) { HalClient::SimpleLink.new(target: repr_2, rel: rel_1) }
|
50
|
+
let(:link_diff_target_diff_rel) { HalClient::SimpleLink.new(target: repr_2, rel: rel_2) }
|
103
51
|
|
104
|
-
let(:link_same_non_fetched) { HalClient::
|
52
|
+
let(:link_same_non_fetched) { HalClient::SimpleLink.new(target: href_only_repr, rel: rel_1) }
|
105
53
|
|
106
54
|
let(:same_as_templated_link1) do
|
107
|
-
HalClient::
|
108
|
-
|
55
|
+
HalClient::TemplatedLink
|
56
|
+
.new(rel: 'templated_link',
|
57
|
+
template: Addressable::Template.new('http://example.com/people{?name}'),
|
58
|
+
hal_client: a_client)
|
109
59
|
end
|
110
60
|
|
111
61
|
let(:templated_link2) do
|
112
|
-
HalClient::
|
113
|
-
|
62
|
+
HalClient::TemplatedLink
|
63
|
+
.new(rel: 'templated_link',
|
64
|
+
template: Addressable::Template.new('http://example.com/places{?name}'),
|
65
|
+
hal_client: a_client)
|
114
66
|
end
|
115
67
|
|
116
68
|
let(:template_but_not_a_template) do
|
117
|
-
HalClient::
|
118
|
-
|
69
|
+
HalClient::TemplatedLink
|
70
|
+
.new(rel: 'rel_1',
|
71
|
+
template: Addressable::Template.new('http://example.com/href_1'),
|
72
|
+
hal_client: a_client)
|
119
73
|
end
|
120
74
|
|
121
75
|
describe "#==" do
|
@@ -181,7 +135,7 @@ RSpec.describe HalClient::Link do
|
|
181
135
|
let(:rel_2) { 'rel_2' }
|
182
136
|
|
183
137
|
let(:full_uri_rel_1) { 'http://example.com/rels/rel_1' }
|
184
|
-
let(:full_uri_link_1) { HalClient::
|
138
|
+
let(:full_uri_link_1) { HalClient::SimpleLink.new(rel: full_uri_rel_1, target: repr_1) }
|
185
139
|
|
186
140
|
let(:curie_resolver) do
|
187
141
|
HalClient::CurieResolver.new({
|
@@ -194,7 +148,7 @@ RSpec.describe HalClient::Link do
|
|
194
148
|
let(:curied_rel_1) { 'ex:rel_1' }
|
195
149
|
|
196
150
|
let(:curied_link_1) do
|
197
|
-
HalClient::
|
151
|
+
HalClient::SimpleLink.new(rel: curied_rel_1, target: repr_1, curie_resolver: curie_resolver)
|
198
152
|
end
|
199
153
|
|
200
154
|
let(:href_1) { 'http://example.com/href_1' }
|
@@ -281,6 +235,7 @@ RSpec.describe HalClient::Link do
|
|
281
235
|
|
282
236
|
let(:template_1) { Addressable::Template.new('http://example.com/people{?name}') }
|
283
237
|
|
284
|
-
let(:templated_link1) { HalClient::
|
238
|
+
let(:templated_link1) { HalClient::TemplatedLink.new(rel: 'templated_link', template: template_1, hal_client: a_client) }
|
285
239
|
|
240
|
+
let(:a_client) { HalClient.new }
|
286
241
|
end
|
@@ -107,13 +107,15 @@ HAL
|
|
107
107
|
describe "#to_s" do
|
108
108
|
subject(:return_val) { repr.to_s }
|
109
109
|
|
110
|
-
it { is_expected.to
|
110
|
+
it { is_expected.to match %{#<HalClient::Representation:} }
|
111
|
+
it { is_expected.to match %r{http://example.com/foo} }
|
111
112
|
|
112
113
|
context "anonymous" do
|
113
114
|
let(:repr) { described_class.new(hal_client: a_client,
|
114
115
|
parsed_json: MultiJson.load("{}")) }
|
115
116
|
|
116
|
-
it { is_expected.to
|
117
|
+
it { is_expected.to match %{#<HalClient::Representation:} }
|
118
|
+
it { is_expected.to match /ANONYMOUS/i }
|
117
119
|
end
|
118
120
|
end
|
119
121
|
|
@@ -152,7 +154,7 @@ HAL
|
|
152
154
|
|
153
155
|
describe "hash" do
|
154
156
|
specify{ expect(repr.hash).to eq repr.href.hash }
|
155
|
-
specify{ expect(repr_no_href.hash).
|
157
|
+
specify{ expect(repr_no_href.hash).not_to eq repr_no_href.raw.hash }
|
156
158
|
end
|
157
159
|
end
|
158
160
|
|
@@ -279,7 +281,10 @@ HAL
|
|
279
281
|
specify { expect(subject).to include(link3a_link) }
|
280
282
|
specify { expect(subject).to include(link3b_link) }
|
281
283
|
|
282
|
-
specify { expect(subject
|
284
|
+
specify { expect(subject
|
285
|
+
.find { |l| l.literal_rel == "dup" }
|
286
|
+
.target['dupProperty'])
|
287
|
+
.to eq "foo" }
|
283
288
|
end
|
284
289
|
|
285
290
|
specify { expect(repr.related_hrefs "link1")
|
@@ -408,11 +413,6 @@ HAL
|
|
408
413
|
specify { expect( repr.to_enum ).to have(2).items }
|
409
414
|
end
|
410
415
|
|
411
|
-
context "non-collection" do
|
412
|
-
specify { expect{repr.as_enum}.to raise_error(HalClient::NotACollectionError) }
|
413
|
-
specify { expect{repr.to_enum}.to raise_error(HalClient::NotACollectionError) }
|
414
|
-
end
|
415
|
-
|
416
416
|
# Background
|
417
417
|
|
418
418
|
let(:link1_repr) do
|
@@ -429,24 +429,25 @@ HAL
|
|
429
429
|
|
430
430
|
|
431
431
|
let(:link1_link) do
|
432
|
-
HalClient::
|
432
|
+
HalClient::SimpleLink.new(rel: 'link1', target: link1_repr)
|
433
433
|
end
|
434
434
|
|
435
435
|
let(:link2_link) do
|
436
|
-
HalClient::
|
436
|
+
HalClient::SimpleLink.new(rel: 'link2', target: link1_repr)
|
437
437
|
end
|
438
438
|
|
439
439
|
let(:templated_link) do
|
440
|
-
HalClient::
|
441
|
-
|
440
|
+
HalClient::TemplatedLink.new(rel: 'templated',
|
441
|
+
template: Addressable::Template.new('http://example.com/people{?name}'),
|
442
|
+
hal_client: a_client)
|
442
443
|
end
|
443
444
|
|
444
445
|
let(:link3a_link) do
|
445
|
-
HalClient::
|
446
|
+
HalClient::SimpleLink.new(rel: 'link3', target: link3a_repr)
|
446
447
|
end
|
447
448
|
|
448
449
|
let(:link3b_link) do
|
449
|
-
HalClient::
|
450
|
+
HalClient::SimpleLink.new(rel: 'link3', target: link3b_repr)
|
450
451
|
end
|
451
452
|
|
452
453
|
|
data/spec/spec_helper.rb
CHANGED
@@ -39,7 +39,7 @@ RSpec.configure do |config|
|
|
39
39
|
|
40
40
|
# This setting enables warnings. It's recommended, but in some cases may
|
41
41
|
# be too noisy due to issues in dependencies.
|
42
|
-
config.warnings =
|
42
|
+
config.warnings = false
|
43
43
|
|
44
44
|
# Many RSpec users commonly either run the entire suite or an individual
|
45
45
|
# file, and it's useful to allow more verbose output when running an
|
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
|
+
version: 4.1.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: 2017-05-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http
|
@@ -168,9 +168,11 @@ files:
|
|
168
168
|
- hal-client.gemspec
|
169
169
|
- lib/hal-client.rb
|
170
170
|
- lib/hal_client.rb
|
171
|
+
- lib/hal_client/anonymous_resource_locator.rb
|
171
172
|
- lib/hal_client/collection.rb
|
172
173
|
- lib/hal_client/curie_resolver.rb
|
173
174
|
- lib/hal_client/errors.rb
|
175
|
+
- lib/hal_client/interpreter.rb
|
174
176
|
- lib/hal_client/link.rb
|
175
177
|
- lib/hal_client/links_section.rb
|
176
178
|
- lib/hal_client/representation.rb
|
@@ -179,6 +181,7 @@ files:
|
|
179
181
|
- lib/hal_client/version.rb
|
180
182
|
- spec/hal_client/collection_spec.rb
|
181
183
|
- spec/hal_client/curie_resolver_spec.rb
|
184
|
+
- spec/hal_client/interpreter_spec.rb
|
182
185
|
- spec/hal_client/link_spec.rb
|
183
186
|
- spec/hal_client/links_section_spec.rb
|
184
187
|
- spec/hal_client/representation_editor_spec.rb
|
@@ -214,6 +217,7 @@ summary: Use HAL APIs easily
|
|
214
217
|
test_files:
|
215
218
|
- spec/hal_client/collection_spec.rb
|
216
219
|
- spec/hal_client/curie_resolver_spec.rb
|
220
|
+
- spec/hal_client/interpreter_spec.rb
|
217
221
|
- spec/hal_client/link_spec.rb
|
218
222
|
- spec/hal_client/links_section_spec.rb
|
219
223
|
- spec/hal_client/representation_editor_spec.rb
|