excon-hypermedia 0.3.0 → 0.4.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.
@@ -21,14 +21,10 @@ module Excon
21
21
  def handle(method_name, *params)
22
22
  return false unless enabled?
23
23
 
24
- if method_name == :rel
25
- handle_link(params.shift, params)
26
- elsif resource.type?(method_name) == :link
27
- handle_link(method_name, params)
28
- elsif resource.respond_to?(method_name, false)
29
- resource.send(method_name, *params)
30
- else
31
- false
24
+ case method_name
25
+ when :resource then resource
26
+ when :rel then rel(params.shift, params)
27
+ else false
32
28
  end
33
29
  end
34
30
 
@@ -37,15 +33,26 @@ module Excon
37
33
  attr_reader :response
38
34
 
39
35
  def resource
40
- @resource ||= Resource.new(response.body)
36
+ @resource ||= ResourceObject.new(body_to_hash)
37
+ end
38
+
39
+ def body_to_hash
40
+ content_type.include?('application/hal+json') ? JSON.parse(response.body) : {}
41
+ end
42
+
43
+ def content_type
44
+ response.headers['Content-Type'].to_s
41
45
  end
42
46
 
43
47
  def enabled?
44
48
  response.data[:hypermedia] == true
45
49
  end
46
50
 
47
- def handle_link(name, params)
48
- Excon.new(resource.link(name).href, params.first.to_h.merge(hypermedia: true))
51
+ def rel(name, params)
52
+ link = resource._links.send(name)
53
+ options = params.first.to_h.merge(hypermedia: true)
54
+
55
+ link.respond_to?(:to_ary) ? link.map { |l| l.rel(options) } : link.rel(options)
49
56
  end
50
57
  end
51
58
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Excon
4
4
  module HyperMedia
5
- VERSION = '0.3.0'
5
+ VERSION = '0.4.0'
6
6
  end
7
7
  end
@@ -2,8 +2,10 @@
2
2
 
3
3
  require 'excon'
4
4
  require 'excon/addressable'
5
- require 'excon/hypermedia/link'
5
+
6
+ require 'excon/hypermedia/helpers/collection'
7
+ require 'excon/hypermedia/link_object'
6
8
  require 'excon/hypermedia/middleware'
7
- require 'excon/hypermedia/resource'
9
+ require 'excon/hypermedia/resource_object'
8
10
  require 'excon/hypermedia/response'
9
11
  require 'excon/hypermedia/version'
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+ # rubocop:disable Metrics/AbcSize
3
+ # rubocop:disable Metrics/LineLength
4
+
5
+ require_relative '../test_helper'
6
+
7
+ module Excon
8
+ # IntegrationTest
9
+ #
10
+ # Verifies the Excon connection consuming HyperMedia APIs.
11
+ #
12
+ class IntegrationTest < HyperMediaTest
13
+ def api
14
+ Excon.get('https://www.example.org/api.json')
15
+ end
16
+
17
+ def test_request
18
+ response = api.rel('product', expand: { uid: 'bicycle' }).get
19
+
20
+ assert response.body.include?('https://www.example.org/product/bicycle')
21
+ end
22
+
23
+ def test_request_using_link_rel
24
+ response = api.resource._links.product.rel(expand: { uid: 'bicycle' }).get
25
+
26
+ assert response.body.include?('https://www.example.org/product/bicycle')
27
+ end
28
+
29
+ def test_nested_request
30
+ bicycle = api.rel('product', expand: { uid: 'bicycle' }).get
31
+ response = bicycle.rel('handlebar').get
32
+
33
+ assert_equal data(:handlebar)['material'], response.resource.material
34
+ end
35
+
36
+ def test_collection_request
37
+ bicycle = api.rel('product', expand: { uid: 'bicycle' }).get
38
+ wheels = bicycle.rel('wheels')
39
+ responses = wheels.map(&:get)
40
+
41
+ assert Array, wheels.class
42
+ assert_equal data(:front_wheel)['position'], responses.first.resource.position
43
+ end
44
+
45
+ def test_expand_in_get
46
+ response = api.rel('product').get(expand: { uid: 'bicycle' })
47
+
48
+ assert response.body.include?('https://www.example.org/product/bicycle')
49
+ end
50
+
51
+ def test_invalid_relation
52
+ assert_raises(NoMethodError) { api.rel('invalid') }
53
+ end
54
+
55
+ def test_link
56
+ response = api.rel('product', expand: { uid: 'bicycle' }).get
57
+
58
+ assert_equal data(:bicycle)['_links']['handlebar']['href'], response.resource._links.handlebar.href
59
+ end
60
+
61
+ def test_link_collection
62
+ response = api.rel('product', expand: { uid: 'bicycle' }).get
63
+
64
+ assert_equal Array, response.resource._links.wheels.class
65
+ assert_equal data(:bicycle)['_links']['wheels'][0]['href'], response.resource._links.wheels.first.href
66
+ end
67
+
68
+ def test_nested_attributes
69
+ response = api.rel('product', expand: { uid: 'bicycle' }).get
70
+
71
+ assert_equal 7, response.resource.derailleurs.back
72
+ end
73
+
74
+ def test_invalid_attribute
75
+ response = api.rel('product', expand: { uid: 'bicycle' }).get
76
+
77
+ assert_equal 'Mountain Bike', response.resource['bike-type']
78
+ assert_equal false, response.resource.bmx
79
+ end
80
+
81
+ def test_embedded_resource
82
+ response = api.rel('product', expand: { uid: 'bicycle' }).get
83
+
84
+ assert_equal Excon::HyperMedia::ResourceObject, response.resource._embedded.pump.class
85
+ assert_equal '2kg', response.resource._embedded.pump.weight
86
+ end
87
+
88
+ def test_embedded_resource_collection
89
+ response = api.rel('product', expand: { uid: 'bicycle' }).get
90
+
91
+ assert_equal Array, response.resource._embedded.wheels.class
92
+ assert_equal data(:front_wheel)['position'], response.resource._embedded.wheels.first.position
93
+ end
94
+
95
+ def teardown
96
+ Excon.stubs.clear
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+ # rubocop:disable Metrics/MethodLength
3
+
4
+ require_relative '../test_helper'
5
+
6
+ module Excon
7
+ # LinkTest
8
+ #
9
+ # Validate the workings of `Excon::HyperResource::LinkObject`.
10
+ #
11
+ class LinkTest < Minitest::Test
12
+ def self
13
+ '{ "href": "https://www.example.org/hello" }'
14
+ end
15
+
16
+ def templated
17
+ '{ "href": "https://www.example.org/hello/{receiver}", "templated": "true" }'
18
+ end
19
+
20
+ def full
21
+ <<~EOF
22
+ {
23
+ "href": "https://www.example.org/goodbye/{receiver}",
24
+ "templated": "true",
25
+ "type": "json",
26
+ "deprecation": true,
27
+ "name": "goodbye",
28
+ "profile": "https://profile.example.org",
29
+ "title": "Goodbye!",
30
+ "hreflang": "en-gb"
31
+ }
32
+ EOF
33
+ end
34
+
35
+ def data(name)
36
+ JSON.parse(send(name))
37
+ end
38
+
39
+ def link(name)
40
+ Excon::HyperMedia::LinkObject.new(data(name))
41
+ end
42
+
43
+ def test_link
44
+ assert_equal data(:self), link(:self).to_h
45
+ end
46
+
47
+ def test_missing_property
48
+ assert_raises(NoMethodError) { data(:self).name }
49
+ end
50
+
51
+ def test_href
52
+ assert_equal data(:self)['href'], link(:self).href
53
+ end
54
+
55
+ def test_templated
56
+ assert link(:templated).templated
57
+ end
58
+
59
+ def test_templated_returns_false_if_undefined
60
+ refute link(:self).templated
61
+ end
62
+
63
+ def test_type
64
+ assert_equal data(:full)['type'], link(:full).type
65
+ end
66
+
67
+ def test_deprecation
68
+ assert_equal data(:full)['deprecation'], link(:full).deprecation
69
+ end
70
+
71
+ def test_name
72
+ assert_equal data(:full)['name'], link(:full).name
73
+ end
74
+
75
+ def test_profile
76
+ assert_equal data(:full)['profile'], link(:full).profile
77
+ end
78
+
79
+ def test_title
80
+ assert_equal data(:full)['title'], link(:full).title
81
+ end
82
+
83
+ def test_hreflang
84
+ assert_equal data(:full)['hreflang'], link(:full).hreflang
85
+ end
86
+
87
+ def test_uri
88
+ assert_equal data(:self)['href'], link(:self).uri.to_s
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ # rubocop:disable Metrics/MethodLength
3
+
4
+ require_relative '../test_helper'
5
+
6
+ module Excon
7
+ # LinksTest
8
+ #
9
+ # Validate the workings of `Excon::HyperResource::Resource::Links`.
10
+ #
11
+ class LinksTest < Minitest::Test
12
+ def body
13
+ <<~EOF
14
+ {
15
+ "_links": {
16
+ "self": {
17
+ "href": "https://www.example.org/product/bicycle"
18
+ },
19
+ "parts": {
20
+ "href": "https://www.example.org/product/bicycle/parts"
21
+ }
22
+ }
23
+ }
24
+ EOF
25
+ end
26
+
27
+ def data
28
+ JSON.parse(body)
29
+ end
30
+
31
+ def links
32
+ @links ||= Excon::HyperMedia::ResourceObject::Links.new(data['_links'])
33
+ end
34
+
35
+ def test_links
36
+ assert_equal Excon::HyperMedia::ResourceObject::Links, links.class
37
+ end
38
+
39
+ def test_link_properties
40
+ assert_equal %w(self parts), links.to_h.keys
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+ # rubocop:disable Metrics/MethodLength
3
+
4
+ require_relative '../test_helper'
5
+
6
+ module Excon
7
+ # PropertiesTest
8
+ #
9
+ # Validate the workings of `Excon::HyperResource::Resource::Properties`.
10
+ #
11
+ class PropertiesTest < Minitest::Test
12
+ def body
13
+ <<~EOF
14
+ {
15
+ "size": "49CM",
16
+ "bike-type": "Mountain Bike",
17
+ "derailleurs": {
18
+ "front": 3,
19
+ "back": 7
20
+ },
21
+ "reflectors": true,
22
+ "BMX": false
23
+ }
24
+ EOF
25
+ end
26
+
27
+ def data
28
+ JSON.parse(body)
29
+ end
30
+
31
+ def properties
32
+ @properties ||= Excon::HyperMedia::ResourceObject::Properties.new(data)
33
+ end
34
+
35
+ def test_properties
36
+ assert_equal data, properties.to_h
37
+ end
38
+
39
+ def test_attribute
40
+ assert_equal properties.size, '49CM'
41
+ end
42
+
43
+ def test_boolean_attribute
44
+ assert_equal properties.reflectors, true
45
+ end
46
+
47
+ def test_uppercase_attribute_names
48
+ assert_equal properties.bmx, false
49
+ assert_equal properties['BMX'], false
50
+ end
51
+
52
+ def test_invalid_attribute_names
53
+ refute properties.respond_to?('bike-type')
54
+ assert_equal properties['bike-type'], 'Mountain Bike'
55
+ end
56
+
57
+ def test_nested_attribute
58
+ assert_equal properties.derailleurs.front, 3
59
+ assert_equal properties.derailleurs.back, 7
60
+ assert_equal properties['derailleurs'].back, 7
61
+ end
62
+
63
+ def test_nested_attribute_hash
64
+ assert_equal properties.derailleurs.to_h, data['derailleurs']
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+ # rubocop:disable Metrics/MethodLength
3
+
4
+ require_relative '../test_helper'
5
+
6
+ module Excon
7
+ # ResourceObjectTest
8
+ #
9
+ # Validate the workings of `Excon::HyperResource::ResourceObject`.
10
+ #
11
+ class ResourceObjectTest < Minitest::Test
12
+ def body
13
+ <<~EOF
14
+ {
15
+ "_links": {
16
+ "hello": {
17
+ "href": "http://www.example.com/hello/{location}"
18
+ }
19
+ },
20
+ "uid": "universe",
21
+ "hello": "world"
22
+ }
23
+ EOF
24
+ end
25
+
26
+ def data
27
+ @data ||= JSON.parse(body)
28
+ end
29
+
30
+ def resource
31
+ @resource ||= Excon::HyperMedia::ResourceObject.new(data)
32
+ end
33
+
34
+ def test_resource
35
+ assert_equal data, resource.instance_variable_get(:@data)
36
+ end
37
+
38
+ def test_links
39
+ assert_equal data['_links']['hello']['href'], resource._links.hello.href
40
+ end
41
+
42
+ def test_properties
43
+ assert_equal 'universe', resource.uid
44
+ assert_equal 'world', resource.hello
45
+ assert_equal 'world', resource['hello']
46
+ assert_equal nil, resource['invalid']
47
+ end
48
+ end
49
+ end
data/test/test_helper.rb CHANGED
@@ -1,4 +1,145 @@
1
1
  # frozen_string_literal: true
2
+ # rubocop:disable Metrics/MethodLength
3
+ # rubocop:disable Metrics/LineLength
4
+ # rubocop:disable Metrics/ClassLength
5
+ # rubocop:disable Metrics/AbcSize
6
+
2
7
  $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
3
8
  require 'excon/hypermedia'
4
9
  require 'minitest/autorun'
10
+
11
+ module Excon
12
+ # HyperMediaTest
13
+ #
14
+ class HyperMediaTest < Minitest::Test
15
+ def setup
16
+ Excon.defaults[:mock] = true
17
+ Excon.defaults[:middlewares].push(Excon::HyperMedia::Middleware)
18
+
19
+ response = { headers: { 'Content-Type' => 'application/hal+json' } }
20
+ Excon.stub({ method: :get, path: '/api.json' }, response.merge(body: api_body))
21
+ Excon.stub({ method: :get, path: '/product/bicycle' }, response.merge(body: bicycle_body))
22
+ Excon.stub({ method: :get, path: '/product/bicycle/wheels/front' }, response.merge(body: front_wheel_body))
23
+ Excon.stub({ method: :get, path: '/product/bicycle/wheels/rear' }, response.merge(body: rear_wheel_body))
24
+ Excon.stub({ method: :get, path: '/product/pump' }, response.merge(body: pump_body))
25
+ Excon.stub({ method: :get, path: '/product/handlebar' }, response.merge(body: handlebar_body))
26
+ end
27
+
28
+ def teardown
29
+ Excon.stubs.clear
30
+ Excon.defaults[:middlewares].delete(Excon::HyperMedia::Middleware)
31
+ Excon.defaults[:mock] = true
32
+ end
33
+
34
+ def data(name)
35
+ JSON.parse(send("#{name}_body"))
36
+ end
37
+
38
+ def api_body
39
+ <<~EOF
40
+ {
41
+ "_links": {
42
+ "self": {
43
+ "href": "https://www.example.org/api.json"
44
+ },
45
+ "product": {
46
+ "href": "https://www.example.org/product/{uid}",
47
+ "templated": true
48
+ }
49
+ }
50
+ }
51
+ EOF
52
+ end
53
+
54
+ def bicycle_body
55
+ <<~EOF
56
+ {
57
+ "_links": {
58
+ "self": {
59
+ "href": "https://www.example.org/product/bicycle"
60
+ },
61
+ "handlebar": {
62
+ "href": "https://www.example.org/product/handlebar"
63
+ },
64
+ "object_id": {
65
+ "href": "https://www.example.org/product/bicycle/object_id_as_text"
66
+ },
67
+ "pump": {
68
+ "href": "https://www.example.org/product/pump"
69
+ },
70
+ "wheels": [
71
+ { "href": "https://www.example.org/product/bicycle/wheels/front" },
72
+ { "href": "https://www.example.org/product/bicycle/wheels/rear" }
73
+ ]
74
+ },
75
+ "bike-type": "Mountain Bike",
76
+ "BMX": false,
77
+ "derailleurs": {
78
+ "back": 7,
79
+ "front": 3
80
+ },
81
+ "name": "bicycle",
82
+ "reflectors": true,
83
+ "_embedded": {
84
+ "pump": #{pump_body},
85
+ "wheels": [#{front_wheel_body}, #{rear_wheel_body}]
86
+ }
87
+ }
88
+ EOF
89
+ end
90
+
91
+ def handlebar_body
92
+ <<~EOF
93
+ {
94
+ "_links": {
95
+ "self": "https://www.example.org/product/handlebar"
96
+ },
97
+ "material": "Carbon fiber",
98
+ "reach": "75mm",
99
+ "bend": "compact"
100
+ }
101
+ EOF
102
+ end
103
+
104
+ def pump_body
105
+ <<~EOF
106
+ {
107
+ "_links": {
108
+ "self": "https://www.example.org/product/pump"
109
+ },
110
+ "weight": "2kg",
111
+ "type": "Floor Pump",
112
+ "valve-type": "Presta"
113
+ }
114
+ EOF
115
+ end
116
+
117
+ def rear_wheel_body
118
+ <<~EOF
119
+ {
120
+ "_links": {
121
+ "self": {
122
+ "href": "https://www.example.org/product/bicycle/wheels/rear"
123
+ }
124
+ },
125
+ "position": "rear",
126
+ "lacing_pattern": "Radial"
127
+ }
128
+ EOF
129
+ end
130
+
131
+ def front_wheel_body
132
+ <<~EOF
133
+ {
134
+ "_links": {
135
+ "self": {
136
+ "href": "https://www.example.org/product/bicycle/wheels/front"
137
+ }
138
+ },
139
+ "position": "front",
140
+ "lacing_pattern": "Radial/2-Cross"
141
+ }
142
+ EOF
143
+ end
144
+ end
145
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: excon-hypermedia
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-05-18 00:00:00.000000000 Z
12
+ date: 2016-05-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -141,14 +141,20 @@ files:
141
141
  - excon-hypermedia.gemspec
142
142
  - lib/excon/hypermedia.rb
143
143
  - lib/excon/hypermedia/ext/response.rb
144
- - lib/excon/hypermedia/link.rb
144
+ - lib/excon/hypermedia/helpers/collection.rb
145
+ - lib/excon/hypermedia/link_object.rb
145
146
  - lib/excon/hypermedia/middleware.rb
146
- - lib/excon/hypermedia/resource.rb
147
+ - lib/excon/hypermedia/resource_object.rb
148
+ - lib/excon/hypermedia/resource_object/embedded.rb
149
+ - lib/excon/hypermedia/resource_object/links.rb
150
+ - lib/excon/hypermedia/resource_object/properties.rb
147
151
  - lib/excon/hypermedia/response.rb
148
152
  - lib/excon/hypermedia/version.rb
149
- - test/excon/hypermedia_test.rb
150
- - test/excon/link_test.rb
151
- - test/excon/resource_test.rb
153
+ - test/excon/integration_test.rb
154
+ - test/excon/link_object_test.rb
155
+ - test/excon/links_test.rb
156
+ - test/excon/properties_test.rb
157
+ - test/excon/resource_object_test.rb
152
158
  - test/test_helper.rb
153
159
  homepage: https://github.com/JeanMertz/excon-hypermedia
154
160
  licenses:
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Excon
4
- module HyperMedia
5
- # Link
6
- #
7
- # This HyperMedia::Link object encapsulates a link pointing to a resource.
8
- #
9
- class Link
10
- attr_reader :name
11
-
12
- def initialize(name:, hash:)
13
- @hash = hash
14
- @name = name
15
- end
16
-
17
- def valid?
18
- link_data.keys.any?
19
- end
20
-
21
- def invalid?
22
- !valid?
23
- end
24
-
25
- def uri
26
- ::Addressable::URI.parse(href)
27
- end
28
-
29
- def href
30
- link_data['href']
31
- end
32
-
33
- private
34
-
35
- def link_data
36
- @hash.dig('_links', name.to_s) || {}
37
- end
38
- end
39
- end
40
- end
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
-
5
- module Excon
6
- module HyperMedia
7
- # Resource
8
- #
9
- # This HyperMedia::Resource object encapsulates the returned JSON and
10
- # makes it easy to access the links and attributes.
11
- #
12
- class Resource
13
- attr_reader :data
14
-
15
- def initialize(body)
16
- @body = body
17
- end
18
-
19
- def links
20
- data.fetch('_links', {}).keys.map { |name| link(name) }
21
- end
22
-
23
- def link(link_name)
24
- Link.new(name: link_name, hash: data)
25
- end
26
-
27
- def attributes
28
- attributes = data.reject do |k, _|
29
- k == '_links'
30
- end
31
-
32
- Struct.new(*attributes.keys.map(&:to_sym)).new(*attributes.values)
33
- end
34
-
35
- def type?(name)
36
- return :link if link(name).valid?
37
- return :attribute if attributes.respond_to?(name.to_s)
38
-
39
- :unknown
40
- end
41
-
42
- def data
43
- @data ||= JSON.parse(@body)
44
- rescue JSON::ParserError
45
- {}
46
- end
47
- end
48
- end
49
- end