hyperclient 0.8.5 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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