hyperclient 0.8.5 → 1.0.1

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.
@@ -15,7 +15,9 @@ module Hyperclient
15
15
  # curies - The Hash with link curies.
16
16
  # entry_point - The EntryPoint object to inject the configuration.
17
17
  def initialize(collection, curies, entry_point)
18
- raise "Invalid response for LinkCollection. The response was: #{collection.inspect}" if collection && !collection.respond_to?(:collect)
18
+ if collection && !collection.respond_to?(:collect)
19
+ raise "Invalid response for LinkCollection. The response was: #{collection.inspect}"
20
+ end
19
21
 
20
22
  @_curies = (curies || {}).reduce({}) do |hash, curie_hash|
21
23
  curie = build_curie(curie_hash, entry_point)
@@ -1,6 +1,17 @@
1
1
  require 'forwardable'
2
2
 
3
3
  module Hyperclient
4
+ # Public: Exception that is raised when passing in invalid representation data
5
+ # for the resource.
6
+ class InvalidRepresentationError < ArgumentError
7
+ attr_reader :representation
8
+
9
+ def initialize(error_description, representation)
10
+ super(error_description)
11
+ @representation = representation
12
+ end
13
+ end
14
+
4
15
  # Public: Represents a resource from your API. Its responsability is to
5
16
  # ease the way you access its attributes, links and embedded resources.
6
17
  class Resource
@@ -29,8 +40,9 @@ module Hyperclient
29
40
  # representation - The hash with the HAL representation of the Resource.
30
41
  # entry_point - The EntryPoint object to inject the configutation.
31
42
  def initialize(representation, entry_point, response = nil)
32
- representation = representation ? representation.dup : {}
43
+ representation = validate(representation)
33
44
  links = representation['_links'] || {}
45
+
34
46
  @_links = LinkCollection.new(links, links['curies'], entry_point)
35
47
  @_embedded = ResourceCollection.new(representation['_embedded'], entry_point)
36
48
  @_attributes = Attributes.new(representation)
@@ -43,11 +55,11 @@ module Hyperclient
43
55
  end
44
56
 
45
57
  def _success?
46
- _response && _response.success?
58
+ _response&.success?
47
59
  end
48
60
 
49
61
  def _status
50
- _response && _response.status
62
+ _response&.status
51
63
  end
52
64
 
53
65
  def [](name)
@@ -68,6 +80,21 @@ module Hyperclient
68
80
 
69
81
  private
70
82
 
83
+ # Internal: Ensures the received representation is a valid Hash-lookalike.
84
+ def validate(representation)
85
+ return {} if representation.nil? || representation.empty?
86
+
87
+ if representation.respond_to?(:to_hash)
88
+ representation.to_hash.dup
89
+ else
90
+ raise InvalidRepresentationError.new(
91
+ "Invalid representation for resource (got #{representation.class}, expected Hash). " \
92
+ "Is your web server returning JSON HAL data with a 'Content-Type: application/hal+json' header?",
93
+ representation
94
+ )
95
+ end
96
+ end
97
+
71
98
  # Internal: Returns the self Link of the Resource. Used to handle the HTTP
72
99
  # methods.
73
100
  def _self_link
@@ -82,9 +109,11 @@ module Hyperclient
82
109
  if args.any? && args.first.is_a?(Hash)
83
110
  _links.send(method, [], &block)._expand(*args)
84
111
  elsif !Array.method_defined?(method)
85
- [:_attributes, :_embedded, :_links].each do |target|
112
+ %i[_attributes _embedded _links].each do |target|
86
113
  target = send(target)
87
- return target.send(method, *args, &block) if target.respond_to?(method.to_s)
114
+ if target.respond_to?(method.to_s)
115
+ return target.send(method, *args, &block)
116
+ end
88
117
  end
89
118
  super
90
119
  end
@@ -93,7 +122,7 @@ module Hyperclient
93
122
  # Internal: Accessory method to allow the resource respond to
94
123
  # methods that will hit method_missing.
95
124
  def respond_to_missing?(method, include_private = false)
96
- [:_attributes, :_embedded, :_links].each do |target|
125
+ %i[_attributes _embedded _links].each do |target|
97
126
  return true if send(target).respond_to?(method, include_private)
98
127
  end
99
128
  false
@@ -23,7 +23,9 @@ module Hyperclient
23
23
  private
24
24
 
25
25
  def build_resource(representation)
26
- return representation.map(&method(:build_resource)) if representation.is_a?(Array)
26
+ if representation.is_a?(Array)
27
+ return representation.map(&method(:build_resource))
28
+ end
27
29
 
28
30
  Resource.new(representation, @entry_point)
29
31
  end
@@ -1,3 +1,3 @@
1
1
  module Hyperclient
2
- VERSION = '0.8.5'.freeze
2
+ VERSION = '1.0.1'.freeze
3
3
  end
@@ -3,8 +3,8 @@
3
3
  "self": {
4
4
  "href": "/productions/1"
5
5
  },
6
- "filter": {
7
- "href": "/productions/1?categories={filter}",
6
+ "search": {
7
+ "href": "/productions/1?categories={search}",
8
8
  "templated": true
9
9
  },
10
10
  "gizmos": [
@@ -15,17 +15,17 @@
15
15
  "href": "/gizmos/2"
16
16
  }
17
17
  ],
18
- "curies" : [
18
+ "curies": [
19
19
  {
20
- "name": "image",
21
- "href": "/docs/images/{rel}",
22
- "templated": true
20
+ "name": "image",
21
+ "href": "/docs/images/{rel}",
22
+ "templated": true
23
23
  }
24
24
  ],
25
25
  "null_link": null,
26
26
  "image:thumbnail": {
27
- "href": "/images/thumbnails/{version}.jpg",
28
- "templated": true
27
+ "href": "/images/thumbnails/{version}.jpg",
28
+ "templated": true
29
29
  }
30
30
  },
31
31
  "title": "Real World ASP.NET MVC3",
@@ -48,9 +48,9 @@
48
48
  "href": "/episodes/1"
49
49
  },
50
50
  "media": {
51
- "type": "video/webm; codecs='vp8.0, vorbis'",
52
- "href": "/media/1"
53
- }
51
+ "type": "video/webm; codecs='vp8.0, vorbis'",
52
+ "href": "/media/1"
53
+ }
54
54
  },
55
55
  "title": "Foundations",
56
56
  "description": "In this episode we talk about what it is we're doing: building our startup and getting ourselves off the ground. We take..",
@@ -62,9 +62,9 @@
62
62
  "href": "/episodes/2"
63
63
  },
64
64
  "media": {
65
- "type": "video/ogg; codecs='theora, vorbis'",
66
- "href": "/media/4"
67
- }
65
+ "type": "video/ogg; codecs='theora, vorbis'",
66
+ "href": "/media/4"
67
+ }
68
68
  },
69
69
  "title": "Membership",
70
70
  "description": "In this episode Rob hooks up testing in an effort to deal with ASP.NET Membership. The team has decided..",
@@ -72,4 +72,4 @@
72
72
  }
73
73
  ]
74
74
  }
75
- }
75
+ }
@@ -12,29 +12,29 @@ module Hyperclient
12
12
  end
13
13
 
14
14
  it 'does not set _links as an attribute' do
15
- attributes.wont_respond_to :_links
15
+ _(attributes).wont_respond_to :_links
16
16
  end
17
17
 
18
18
  it 'does not set _embedded as an attribute' do
19
- attributes.wont_respond_to :_embedded
19
+ _(attributes).wont_respond_to :_embedded
20
20
  end
21
21
 
22
22
  it 'sets normal attributes' do
23
- attributes.must_respond_to :permitted
24
- attributes.permitted.must_equal true
23
+ _(attributes).must_respond_to :permitted
24
+ _(attributes.permitted).must_equal true
25
25
 
26
- attributes.must_respond_to :title
27
- attributes.title.must_equal 'Real World ASP.NET MVC3'
26
+ _(attributes).must_respond_to :title
27
+ _(attributes.title).must_equal 'Real World ASP.NET MVC3'
28
28
  end
29
29
 
30
30
  # Underscores should be allowed per http://tools.ietf.org/html/draft-kelly-json-hal#appendix-B.4
31
31
  it 'sets _hidden_attribute as an attribute' do
32
- attributes.must_respond_to :_hidden_attribute
33
- attributes._hidden_attribute.must_equal 'useful value'
32
+ _(attributes).must_respond_to :_hidden_attribute
33
+ _(attributes._hidden_attribute).must_equal 'useful value'
34
34
  end
35
35
 
36
36
  it 'is a collection' do
37
- Attributes.ancestors.must_include Collection
37
+ _(Attributes.ancestors).must_include Collection
38
38
  end
39
39
  end
40
40
  end
@@ -12,19 +12,19 @@ module Hyperclient
12
12
  end
13
13
 
14
14
  it 'exposes the collection as methods' do
15
- collection.title.must_equal 'Real World ASP.NET MVC3'
16
- collection.description.must_match(/production/)
17
- collection.permitted.must_equal true
15
+ _(collection.title).must_equal 'Real World ASP.NET MVC3'
16
+ _(collection.description).must_match(/production/)
17
+ _(collection.permitted).must_equal true
18
18
  end
19
19
 
20
20
  it 'exposes collection as a hash' do
21
- collection['title'].must_equal 'Real World ASP.NET MVC3'
22
- collection['description'].must_match(/production/)
23
- collection['permitted'].must_equal true
21
+ _(collection['title']).must_equal 'Real World ASP.NET MVC3'
22
+ _(collection['description']).must_match(/production/)
23
+ _(collection['permitted']).must_equal true
24
24
  end
25
25
 
26
26
  it 'correctly responds to methods' do
27
- collection.must_respond_to :title
27
+ _(collection).must_respond_to :title
28
28
  end
29
29
 
30
30
  it 'acts as enumerable' do
@@ -32,41 +32,41 @@ module Hyperclient
32
32
  name
33
33
  end
34
34
 
35
- names.must_equal %w(_links title description permitted _hidden_attribute _embedded)
35
+ _(names).must_equal %w[_links title description permitted _hidden_attribute _embedded]
36
36
  end
37
37
 
38
38
  describe '#to_hash' do
39
39
  it 'returns the wrapped collection as a hash' do
40
- collection.to_hash.must_be_kind_of Hash
40
+ _(collection.to_hash).must_be_kind_of Hash
41
41
  end
42
42
  end
43
43
 
44
44
  describe 'include?' do
45
45
  it 'returns true for keys that exist' do
46
- collection.include?('_links').must_equal true
46
+ _(collection.include?('_links')).must_equal true
47
47
  end
48
48
 
49
49
  it 'returns false for missing keys' do
50
- collection.include?('missing key').must_equal false
50
+ _(collection.include?('missing key')).must_equal false
51
51
  end
52
52
  end
53
53
 
54
54
  describe '#fetch' do
55
55
  it 'returns the value for keys that exist' do
56
- collection.fetch('title').must_equal 'Real World ASP.NET MVC3'
56
+ _(collection.fetch('title')).must_equal 'Real World ASP.NET MVC3'
57
57
  end
58
58
 
59
59
  it 'raises an error for missing keys' do
60
- proc { collection.fetch('missing key') }.must_raise KeyError
60
+ _(proc { collection.fetch('missing key') }).must_raise KeyError
61
61
  end
62
62
 
63
63
  describe 'with a default value' do
64
64
  it 'returns the value for keys that exist' do
65
- collection.fetch('title', 'default').must_equal 'Real World ASP.NET MVC3'
65
+ _(collection.fetch('title', 'default')).must_equal 'Real World ASP.NET MVC3'
66
66
  end
67
67
 
68
68
  it 'returns the default value for missing keys' do
69
- collection.fetch('missing key', 'default').must_equal 'default'
69
+ _(collection.fetch('missing key', 'default')).must_equal 'default'
70
70
  end
71
71
  end
72
72
  end
@@ -11,13 +11,13 @@ module Hyperclient
11
11
  it 'returns true if the curie is templated' do
12
12
  curie = Curie.new({ 'name' => 'image', 'templated' => true }, entry_point)
13
13
 
14
- curie.templated?.must_equal true
14
+ _(curie.templated?).must_equal true
15
15
  end
16
16
 
17
17
  it 'returns false if the curie is not templated' do
18
18
  curie = Curie.new({ 'name' => 'image' }, entry_point)
19
19
 
20
- curie.templated?.must_equal false
20
+ _(curie.templated?).must_equal false
21
21
  end
22
22
  end
23
23
 
@@ -26,12 +26,12 @@ module Hyperclient
26
26
  end
27
27
  describe '_name' do
28
28
  it 'returns curie name' do
29
- curie.name.must_equal 'image'
29
+ _(curie.name).must_equal 'image'
30
30
  end
31
31
  end
32
32
  describe 'expand' do
33
33
  it 'expands link' do
34
- curie.expand('thumbnail').must_equal '/images/thumbnail'
34
+ _(curie.expand('thumbnail')).must_equal '/images/thumbnail'
35
35
  end
36
36
  end
37
37
  end
@@ -10,59 +10,52 @@ module Hyperclient
10
10
 
11
11
  describe 'connection' do
12
12
  it 'creates a Faraday connection with the entry point url' do
13
- entry_point.connection.url_prefix.to_s.must_equal 'http://my.api.org/'
13
+ _(entry_point.connection.url_prefix.to_s).must_equal 'http://my.api.org/'
14
14
  end
15
15
 
16
16
  it 'creates a Faraday connection with the default headers' do
17
- entry_point.headers['Content-Type'].must_equal 'application/hal+json'
18
- entry_point.headers['Accept'].must_equal 'application/hal+json,application/json'
17
+ _(entry_point.headers['Content-Type']).must_equal 'application/hal+json'
18
+ _(entry_point.headers['Accept']).must_equal 'application/hal+json,application/json'
19
19
  end
20
20
 
21
21
  it 'can update headers after a connection has been constructed' do
22
- entry_point.connection.must_be_kind_of Faraday::Connection
22
+ _(entry_point.connection).must_be_kind_of Faraday::Connection
23
23
  entry_point.headers.update('Content-Type' => 'application/foobar')
24
- entry_point.headers['Content-Type'].must_equal 'application/foobar'
24
+ _(entry_point.headers['Content-Type']).must_equal 'application/foobar'
25
25
  end
26
26
 
27
27
  it 'can insert additional middleware after a connection has been constructed' do
28
- entry_point.connection.must_be_kind_of Faraday::Connection
28
+ _(entry_point.connection).must_be_kind_of Faraday::Connection
29
29
  entry_point.connection.use :instrumentation
30
30
  handlers = entry_point.connection.builder.handlers
31
- handlers.must_include FaradayMiddleware::Instrumentation
31
+ _(handlers).must_include FaradayMiddleware::Instrumentation
32
32
  end
33
33
 
34
34
  it 'creates a Faraday connection with the default block' do
35
35
  handlers = entry_point.connection.builder.handlers
36
36
 
37
- handlers.must_include Faraday::Response::RaiseError
38
- handlers.must_include FaradayMiddleware::FollowRedirects
39
- handlers.must_include FaradayMiddleware::EncodeHalJson
40
- handlers.must_include FaradayMiddleware::ParseHalJson
41
- handlers.must_include Faraday::Adapter::NetHttp
37
+ _(handlers).must_include Faraday::Response::RaiseError
38
+ _(handlers).must_include FaradayMiddleware::FollowRedirects
39
+ _(handlers).must_include FaradayMiddleware::EncodeHalJson
40
+ _(handlers).must_include FaradayMiddleware::ParseHalJson
42
41
 
43
- entry_point.connection.options.params_encoder.must_equal Faraday::FlatParamsEncoder
42
+ _(entry_point.connection.options.params_encoder).must_equal Faraday::FlatParamsEncoder
44
43
  end
45
44
 
46
45
  it 'raises a ConnectionAlreadyInitializedError if attempting to modify headers' do
47
- entry_point.connection.must_be_kind_of Faraday::Connection
48
- lambda { entry_point.headers = {} }.must_raise ConnectionAlreadyInitializedError
46
+ _(entry_point.connection).must_be_kind_of Faraday::Connection
47
+ _(-> { entry_point.headers = {} }).must_raise ConnectionAlreadyInitializedError
49
48
  end
50
49
 
51
50
  it 'raises a ConnectionAlreadyInitializedError if attempting to modify the faraday block' do
52
- entry_point.connection.must_be_kind_of Faraday::Connection
53
- lambda { entry_point.connection {} }.must_raise ConnectionAlreadyInitializedError
51
+ _(entry_point.connection).must_be_kind_of Faraday::Connection
52
+ _(-> { entry_point.connection {} }).must_raise ConnectionAlreadyInitializedError
54
53
  end
55
54
  end
56
55
 
57
56
  describe 'initialize' do
58
57
  it 'sets a Link with the entry point url' do
59
- entry_point._url.must_equal 'http://my.api.org'
60
- end
61
- end
62
-
63
- describe 'options' do
64
- it 'enables the async option by default' do
65
- entry_point.options[:async].must_equal true
58
+ _(entry_point._url).must_equal 'http://my.api.org'
66
59
  end
67
60
  end
68
61
  end
@@ -76,17 +69,17 @@ module Hyperclient
76
69
 
77
70
  describe 'connection' do
78
71
  it 'creates a Faraday connection with the entry point url' do
79
- entry_point.connection.url_prefix.to_s.must_equal 'http://my.api.org/'
72
+ _(entry_point.connection.url_prefix.to_s).must_equal 'http://my.api.org/'
80
73
  end
81
74
 
82
75
  it 'creates a Faraday connection with the default headers' do
83
- entry_point.headers['Content-Type'].must_equal 'application/hal+json'
84
- entry_point.headers['Accept'].must_equal 'application/hal+json,application/json'
76
+ _(entry_point.headers['Content-Type']).must_equal 'application/hal+json'
77
+ _(entry_point.headers['Accept']).must_equal 'application/hal+json,application/json'
85
78
  end
86
79
 
87
80
  it 'creates a Faraday connection with options' do
88
- entry_point.connection.proxy.must_be_kind_of Faraday::ProxyOptions
89
- entry_point.connection.proxy.uri.to_s.must_equal 'http://my.proxy:8080'
81
+ _(entry_point.connection.proxy).must_be_kind_of Faraday::ProxyOptions
82
+ _(entry_point.connection.proxy.uri.to_s).must_equal 'http://my.proxy:8080'
90
83
  end
91
84
  end
92
85
  end
@@ -100,17 +93,17 @@ module Hyperclient
100
93
 
101
94
  describe 'connection' do
102
95
  it 'creates a Faraday connection with the entry point url' do
103
- entry_point.connection.url_prefix.to_s.must_equal 'http://my.api.org/'
96
+ _(entry_point.connection.url_prefix.to_s).must_equal 'http://my.api.org/'
104
97
  end
105
98
 
106
99
  it 'creates a Faraday connection with the default headers' do
107
- entry_point.headers['Content-Type'].must_equal 'application/hal+json'
108
- entry_point.headers['Accept'].must_equal 'application/hal+json,application/json'
100
+ _(entry_point.headers['Content-Type']).must_equal 'application/hal+json'
101
+ _(entry_point.headers['Accept']).must_equal 'application/hal+json,application/json'
109
102
  end
110
103
 
111
104
  it 'creates a Faraday connection with options' do
112
- entry_point.connection.proxy.must_be_kind_of Faraday::ProxyOptions
113
- entry_point.connection.proxy.uri.to_s.must_equal 'http://my.proxy:8080'
105
+ _(entry_point.connection.proxy).must_be_kind_of Faraday::ProxyOptions
106
+ _(entry_point.connection.proxy.uri.to_s).must_equal 'http://my.proxy:8080'
114
107
  end
115
108
  end
116
109
  end
@@ -133,21 +126,20 @@ module Hyperclient
133
126
 
134
127
  describe 'connection' do
135
128
  it 'creates a Faraday connection with the entry point url' do
136
- entry_point.connection.url_prefix.to_s.must_equal 'http://my.api.org/'
129
+ _(entry_point.connection.url_prefix.to_s).must_equal 'http://my.api.org/'
137
130
  end
138
131
 
139
132
  it 'creates a Faraday connection with non-default headers' do
140
- entry_point.headers['Content-Type'].must_equal 'application/foobar'
141
- entry_point.headers['Accept'].must_equal 'application/foobar'
133
+ _(entry_point.headers['Content-Type']).must_equal 'application/foobar'
134
+ _(entry_point.headers['Accept']).must_equal 'application/foobar'
142
135
  end
143
136
 
144
137
  it 'creates a Faraday connection with the default block' do
145
138
  handlers = entry_point.connection.builder.handlers
146
- handlers.wont_include Faraday::Response::RaiseError
147
- handlers.wont_include FaradayMiddleware::FollowRedirects
148
- handlers.must_include FaradayMiddleware::EncodeJson
149
- handlers.must_include FaradayMiddleware::ParseJson
150
- handlers.must_include Faraday::Adapter::NetHttp
139
+ _(handlers).wont_include Faraday::Response::RaiseError
140
+ _(handlers).wont_include FaradayMiddleware::FollowRedirects
141
+ _(handlers).must_include FaradayMiddleware::EncodeJson
142
+ _(handlers).must_include FaradayMiddleware::ParseJson
151
143
  end
152
144
  end
153
145
  end
@@ -164,26 +156,25 @@ module Hyperclient
164
156
 
165
157
  describe 'connection' do
166
158
  it 'creates a Faraday connection with the default and additional headers' do
167
- entry_point.headers['Content-Type'].must_equal 'application/hal+json'
168
- entry_point.headers['Accept'].must_equal 'application/hal+json,application/json'
169
- entry_point.headers['Access-Token'].must_equal 'token'
159
+ _(entry_point.headers['Content-Type']).must_equal 'application/hal+json'
160
+ _(entry_point.headers['Accept']).must_equal 'application/hal+json,application/json'
161
+ _(entry_point.headers['Access-Token']).must_equal 'token'
170
162
  end
171
163
 
172
164
  it 'creates a Faraday connection with the entry point url' do
173
- entry_point.connection.url_prefix.to_s.must_equal 'http://my.api.org/'
165
+ _(entry_point.connection.url_prefix.to_s).must_equal 'http://my.api.org/'
174
166
  end
175
167
 
176
168
  it 'creates a Faraday connection with the default block plus any additional handlers' do
177
169
  handlers = entry_point.connection.builder.handlers
178
170
 
179
- handlers.must_include Faraday::Request::OAuth
180
- handlers.must_include Faraday::Response::RaiseError
181
- handlers.must_include FaradayMiddleware::FollowRedirects
182
- handlers.must_include FaradayMiddleware::EncodeHalJson
183
- handlers.must_include FaradayMiddleware::ParseHalJson
184
- handlers.must_include Faraday::Adapter::NetHttp
171
+ _(handlers).must_include Faraday::Request::OAuth
172
+ _(handlers).must_include Faraday::Response::RaiseError
173
+ _(handlers).must_include FaradayMiddleware::FollowRedirects
174
+ _(handlers).must_include FaradayMiddleware::EncodeHalJson
175
+ _(handlers).must_include FaradayMiddleware::ParseHalJson
185
176
 
186
- entry_point.connection.options.params_encoder.must_equal Faraday::FlatParamsEncoder
177
+ _(entry_point.connection.options.params_encoder).must_equal Faraday::FlatParamsEncoder
187
178
  end
188
179
  end
189
180
  end