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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 822989741d47ce52f9dcc8ff158a16248c8236fc
4
- data.tar.gz: 7466b6387ebcd3afeb1ec161854a8f992b0e3552
3
+ metadata.gz: 8bfaa5d8d4e7d292a1451a99fc57242f11bdea8a
4
+ data.tar.gz: 5d94cd3f5b0dbfadd72fb69500961bc3ae7de871
5
5
  SHA512:
6
- metadata.gz: 35cd57faa426d38a11f0a8d0019a9c23349517fdb2c34455e4b2b27eef3e992d2fdafc8519914e9686033d789ba296831a1bb8371e8987555c78301b666fcd2d
7
- data.tar.gz: 7884c47f72801c74d420b12f0a2ad10f7b53efe962a66e17d599878df37fd619d8e21c41cf6374b1e12def7ff6a9b9adaee7975fab3a39a7ff31490d58aa255a
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.6
14
- - 2.3.2
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`.
@@ -0,0 +1,12 @@
1
+ class HalClient
2
+ class AnonymousResourceLocator
3
+ def to_s
4
+ "ANONYMOUS(#{object_id})"
5
+ end
6
+ alias_method :to_str, :to_s
7
+
8
+ def anonymous?
9
+ true
10
+ end
11
+ end
12
+ end
@@ -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
@@ -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
- templated? ? template.pattern : target.href
15
+ raise NotImplementedError
104
16
  end
105
17
 
106
- def fully_qualified_rel
107
- curie_resolver.resolve(literal_rel)
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
- !template.nil?
27
+ raise NotImplementedError
113
28
  end
114
29
 
115
- # Links with the same href, same rel value, and the same 'templated' value are considered equal
116
- # Otherwise, they are considered unequal
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 other.respond_to?(:raw_href) &&
119
- other.respond_to?(:fully_qualified_rel) &&
120
- other.respond_to?(:templated?)
121
- (raw_href == other.raw_href) &&
122
- (fully_qualified_rel == other.fully_qualified_rel) &&
123
- (templated? == other.templated?)
124
- else
125
- false
126
- end
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
- # Differing Representations or Addressable::Templates with matching hrefs will get matching hash
132
- # values, since we are using raw_href and not the objects themselves when computing hash
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, raw_href, templated?].hash
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
- def self.self_href(embedded_repr)
141
- embedded_repr
142
- .fetch('_links', {})
143
- .fetch('self', {})
144
- .fetch('href', nil)
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
- (fail InvalidRepresentationError, "Invalid HAL representation: #{raw.inspect}") if
48
- @raw && ! hashish?(@raw)
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
- raw.key? name
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
- raw.fetch(name.to_s, &default_proc)
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
- def properties
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",nil)
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
- !!(linked(link_rel) { false } || embedded(link_rel) { false })
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
- embedded = embedded(link_rel) { nil }
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
- RepresentationSet.new (Array(embedded) + Array(linked))
186
- end
191
+ related = links_by_rel
192
+ .fetch(link_rel) { return default_proc.call(link_rel) }
193
+ .map { |l| l.target(options) }
187
194
 
188
- def all_links
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
- embedded = embedded(link_rel) { nil }
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
- Array(linked) + Array(embedded).map(&:href)
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 + ": " + (href || "ANONYMOUS") + ">"
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
- # Internal: Returns parsed json document
305
+ # Returns raw parsed json.
308
306
  def raw
309
- if @raw.nil? && @href
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
- # Internal: Returns the HalClient used to retrieve this
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
- MISSING = Object.new
317
+ attr_reader :links_by_rel
318
+ attr_writer :hal_client
333
319
 
334
- def flatten_section(section_hash)
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
- def linked(link_rel, options = {}, &default_proc)
367
- default_proc ||= NO_LINK_FOUND
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
- relations = links.hrefs(link_rel) { MISSING }
370
- return default_proc.call(link_rel, options) if relations == MISSING || relations.compact.empty?
329
+ response = hal_client.get(@href)
371
330
 
372
- relations
373
- .map {|url_or_tmpl|
374
- if url_or_tmpl.respond_to? :expand
375
- url_or_tmpl.expand(options).to_s
376
- else
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
- rescue InvalidRepresentationError
382
- fail InvalidRepresentationError, "/_links/#{jpointer_esc(link_rel)} is not a valid link"
337
+ interpret response.raw
383
338
  end
384
339
 
385
- def jpointer_esc(str)
386
- str.gsub "/", "~1"
387
- end
340
+ def interpret(parsed_json)
341
+ @raw = parsed_json
388
342
 
389
- def boxed(list_hash_or_nil)
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
- fail InvalidRepresentationError
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
- def hashish?(thing)
409
- thing.respond_to?(:fetch) && thing.respond_to?(:key?)
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]
@@ -1,3 +1,3 @@
1
1
  class HalClient
2
- VERSION = "3.18.0"
2
+ VERSION = "4.1.0"
3
3
  end
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) { described_class.new(rel: rel_1, target: repr_1) }
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::Link.new(rel: rel_1) }.to raise_error(ArgumentError)
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::Link.new(rel: rel_1, target: repr_1, template: template_1)
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::Link.new(rel: rel_1, target: template_1)
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::Link.new(rel: rel_1, template: repr_1)
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).to eq('http://example.com/people{?name}') }
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::Link.new(target: repr_1, rel: rel_1) }
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::Link.new(target: repr_1, rel: rel_2) }
101
- let(:link_diff_target_same_rel) { HalClient::Link.new(target: repr_2, rel: rel_1) }
102
- let(:link_diff_target_diff_rel) { HalClient::Link.new(target: repr_2, rel: rel_2) }
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::Link.new(target: href_only_repr, rel: rel_1) }
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::Link.new(rel: 'templated_link',
108
- template: Addressable::Template.new('http://example.com/people{?name}'))
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::Link.new(rel: 'templated_link',
113
- template: Addressable::Template.new('http://example.com/places{?name}'))
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::Link.new(rel: 'rel_1',
118
- template: Addressable::Template.new('http://example.com/href_1'))
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::Link.new(rel: full_uri_rel_1, target: repr_1) }
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::Link.new(rel: curied_rel_1, target: repr_1, curie_resolver: curie_resolver)
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::Link.new(rel: 'templated_link', template: template_1) }
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 eq "#<HalClient::Representation: http://example.com/foo>" }
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 eq "#<HalClient::Representation: ANONYMOUS>" }
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).to eq repr_no_href.raw.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.any? { |item| item.target['dupProperty'] == 'foo' }).to be true }
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::Link.new(rel: 'link1', target: link1_repr)
432
+ HalClient::SimpleLink.new(rel: 'link1', target: link1_repr)
433
433
  end
434
434
 
435
435
  let(:link2_link) do
436
- HalClient::Link.new(rel: 'link2', target: link1_repr)
436
+ HalClient::SimpleLink.new(rel: 'link2', target: link1_repr)
437
437
  end
438
438
 
439
439
  let(:templated_link) do
440
- HalClient::Link.new(rel: 'templated',
441
- template: Addressable::Template.new('http://example.com/people{?name}'))
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::Link.new(rel: 'link3', target: link3a_repr)
446
+ HalClient::SimpleLink.new(rel: 'link3', target: link3a_repr)
446
447
  end
447
448
 
448
449
  let(:link3b_link) do
449
- HalClient::Link.new(rel: 'link3', target: link3b_repr)
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 = true
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: 3.18.0
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: 2016-11-22 00:00:00.000000000 Z
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