hal-client 3.18.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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