excon-hypermedia 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +194 -30
- data/lib/excon/hypermedia/ext/response.rb +2 -0
- data/lib/excon/hypermedia/helpers/collection.rb +62 -0
- data/lib/excon/hypermedia/link_object.rb +153 -0
- data/lib/excon/hypermedia/middleware.rb +2 -0
- data/lib/excon/hypermedia/resource_object/embedded.rb +23 -0
- data/lib/excon/hypermedia/resource_object/links.rb +19 -0
- data/lib/excon/hypermedia/resource_object/properties.rb +17 -0
- data/lib/excon/hypermedia/resource_object.rb +69 -0
- data/lib/excon/hypermedia/response.rb +18 -11
- data/lib/excon/hypermedia/version.rb +1 -1
- data/lib/excon/hypermedia.rb +4 -2
- data/test/excon/integration_test.rb +99 -0
- data/test/excon/link_object_test.rb +91 -0
- data/test/excon/links_test.rb +43 -0
- data/test/excon/properties_test.rb +67 -0
- data/test/excon/resource_object_test.rb +49 -0
- data/test/test_helper.rb +141 -0
- metadata +13 -7
- data/lib/excon/hypermedia/link.rb +0 -40
- data/lib/excon/hypermedia/resource.rb +0 -49
- data/test/excon/hypermedia_test.rb +0 -100
- data/test/excon/link_test.rb +0 -56
- data/test/excon/resource_test.rb +0 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38a442095c082e7251b1a72d8e88084833357a3d
|
4
|
+
data.tar.gz: f0ff7d16042fa3d35bfca84b216b6e145f55db2d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6ce27164e467d58bd426538b59cb917761c246d36dcb744d43e807169415ca225280cbd9965d41a631c907b2abeed49794090e3800a78e51a05b2f0811f781f1
|
7
|
+
data.tar.gz: 4670c0806fa665a012b0238598cc724296e5252b79735f116954aba80649b116773560d07aeebad932cbfd0f39550bd1a21248376d2ab25ccc6f49f1615fdf9e
|
data/README.md
CHANGED
@@ -2,6 +2,16 @@
|
|
2
2
|
|
3
3
|
Teaches [Excon][] how to talk to [HyperMedia APIs][hypermedia].
|
4
4
|
|
5
|
+
* [Installation](#installation)
|
6
|
+
* [Quick Start](#quick-start)
|
7
|
+
* [Usage](#usage)
|
8
|
+
* [resources](#resources)
|
9
|
+
* [links](#links)
|
10
|
+
* [relations](#relations)
|
11
|
+
* [properties](#properties)
|
12
|
+
* [embedded](#embedded)
|
13
|
+
* [License](#license)
|
14
|
+
|
5
15
|
## Installation
|
6
16
|
|
7
17
|
Add this line to your application's Gemfile:
|
@@ -22,6 +32,22 @@ Or install it yourself as:
|
|
22
32
|
gem install excon-hypermedia
|
23
33
|
```
|
24
34
|
|
35
|
+
## Quick Start
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
Excon.defaults[:middlewares].push(Excon::HyperMedia::Middleware)
|
39
|
+
|
40
|
+
api = Excon.get('https://www.example.org/api.json')
|
41
|
+
api.class # => Excon::Response
|
42
|
+
|
43
|
+
product = api.rel('product', expand: { uid: 'bicycle' })
|
44
|
+
product.class # => Excon::Connection
|
45
|
+
|
46
|
+
response = product.get
|
47
|
+
response.class # => Excon::Response
|
48
|
+
response.resource.name # => 'bicycle'
|
49
|
+
```
|
50
|
+
|
25
51
|
## Usage
|
26
52
|
|
27
53
|
To let Excon know the API supports HyperMedia, simply enable the correct
|
@@ -34,65 +60,202 @@ api = Excon.get('https://www.example.org/api.json')
|
|
34
60
|
api.class # => Excon::Response
|
35
61
|
```
|
36
62
|
|
37
|
-
|
38
|
-
|
63
|
+
> NOTE: we'll use the following JSON response body in the below examples:
|
64
|
+
>
|
65
|
+
> **https://www.example.org/api.json**
|
66
|
+
>
|
67
|
+
> ```json
|
68
|
+
> {
|
69
|
+
> "_links": {
|
70
|
+
> "self": {
|
71
|
+
> "href": "https://www.example.org/api.json"
|
72
|
+
> },
|
73
|
+
> "product": {
|
74
|
+
> "href": "https://www.example.org/product/{uid}",
|
75
|
+
> "templated": true
|
76
|
+
> }
|
77
|
+
> }
|
78
|
+
> }
|
79
|
+
> ```
|
80
|
+
>
|
81
|
+
> **https://www.example.org/product/bicycle**
|
82
|
+
>
|
83
|
+
> ```json
|
84
|
+
> {
|
85
|
+
> "_links": {
|
86
|
+
> "self": {
|
87
|
+
> "href": "https://www.example.org/product/bicycle"
|
88
|
+
> }
|
89
|
+
> },
|
90
|
+
> "bike-type": "Mountain Bike",
|
91
|
+
> "BMX": false,
|
92
|
+
> "derailleurs": {
|
93
|
+
> "back": 7,
|
94
|
+
> "front": 3
|
95
|
+
> },
|
96
|
+
> "name": "bicycle",
|
97
|
+
> "reflectors": true,
|
98
|
+
> "_embedded": {
|
99
|
+
> "pump": {
|
100
|
+
> "_links": {
|
101
|
+
> "self": "https://www.example.org/product/pump"
|
102
|
+
> },
|
103
|
+
> "weight": "2kg",
|
104
|
+
> "type": "Floor Pump",
|
105
|
+
> "valve-type": "Presta"
|
106
|
+
> }
|
107
|
+
> }
|
108
|
+
> }
|
109
|
+
|
110
|
+
With this middleware injected in the stack, Excon's model is now expanded with
|
111
|
+
several key concepts:
|
112
|
+
|
113
|
+
### resources
|
114
|
+
|
115
|
+
A **resource** is the representation of the object returned by the API. Almost
|
116
|
+
all other concepts and methods originate from this object.
|
117
|
+
|
118
|
+
Use the newly available `Excon::Response#resource` method to access the resource
|
119
|
+
object:
|
39
120
|
|
40
121
|
```ruby
|
41
|
-
|
42
|
-
|
122
|
+
api.resource.class # => Excon::HyperMedia::ResourceObject
|
123
|
+
```
|
43
124
|
|
44
|
-
|
45
|
-
|
46
|
-
|
125
|
+
A resource has several methods exposed:
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
api.resource.public_methods(false) # => [:_links, :_properties, :_embedded]
|
129
|
+
```
|
130
|
+
|
131
|
+
Each of these methods represents one of the following HyperMedia concepts.
|
132
|
+
|
133
|
+
### links
|
134
|
+
|
135
|
+
A resource has links, that point to related resources (and itself), these can be
|
136
|
+
accessed as well:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
api.resource._links.class # => Excon::HyperMedia::ResourceObject::Links
|
140
|
+
```
|
141
|
+
|
142
|
+
You can get a list of valid links using `keys`:
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
api.resource._links.keys # => ['self', 'product']
|
146
|
+
```
|
147
|
+
|
148
|
+
Each links is represented by a `LinkObject` instance:
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
api.resource._links.product.class # => Excon::HyperMedia::LinkObject
|
152
|
+
api.resource._links.product.href # => 'https://www.example.org/product/{uid}'
|
153
|
+
api.resource._links.product.templated # => true
|
154
|
+
```
|
155
|
+
|
156
|
+
### relations
|
157
|
+
|
158
|
+
Links are the primary way to traverse between relations. This is what makes a
|
159
|
+
HyperMedia-based API "self-discoverable".
|
160
|
+
|
161
|
+
To go from one resource, to the next, you use the `rel` (short for relation)
|
162
|
+
method. This method is available on any `LinkObject` instance.
|
163
|
+
|
164
|
+
Using `rel`, returns an `Excon::Connection` object, the same as if you where to
|
165
|
+
call `Excon.new`:
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
relation = api.resource._links.self.rel
|
169
|
+
relation.class # => Excon::Connection
|
170
|
+
```
|
171
|
+
|
172
|
+
Since the returned object is of type `Excon::Connection`, all
|
173
|
+
[Excon-provided options][options] are available as well:
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
relation.get(idempotent: true, retry_limit: 6)
|
177
|
+
```
|
178
|
+
|
179
|
+
`Excon::Response` also has a convenient delegation to `LinkObject#rel`:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
relation = api.rel('self').get
|
183
|
+
```
|
184
|
+
|
185
|
+
Once you call `get` (or `post`, or any other valid Excon request method), you
|
186
|
+
are back where you started, with a new `Excon::Response` object, imbued with
|
187
|
+
HyperMedia powers:
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
relation.resource._links.keys # => ['self', 'product']
|
191
|
+
```
|
192
|
+
|
193
|
+
In this case, we ended up back with the same type of object as before. To go
|
194
|
+
anywhere meaningful, we want to use the `product` rel:
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
product = api.rel('product', expand: { uid: 'bicycle' }).get
|
47
198
|
```
|
48
199
|
|
49
200
|
As seen above, you can expand URI Template variables using the `expand` option,
|
50
201
|
provided by the [`excon-addressable` library][excon-addressable].
|
51
202
|
|
52
|
-
|
53
|
-
|
54
|
-
are
|
203
|
+
### properties
|
204
|
+
|
205
|
+
Properties are what make a resource unique, they tell us more about the state of
|
206
|
+
the resource, they are the key/value pairs that define the resource.
|
207
|
+
|
208
|
+
In HAL/JSON terms, this is everything returned by the response body, excluding
|
209
|
+
the `_links` and `_embedded` sections:
|
55
210
|
|
56
211
|
```ruby
|
57
|
-
product.
|
212
|
+
product.resource.name # => "bicycle"
|
213
|
+
product.resource.reflectors # => true
|
58
214
|
```
|
59
215
|
|
60
|
-
|
61
|
-
|
62
|
-
You can access all links in a resource using the `links` method:
|
216
|
+
Nested properties are supported as well:
|
63
217
|
|
64
218
|
```ruby
|
65
|
-
|
66
|
-
|
67
|
-
|
219
|
+
product.resource.derailleurs.class # => Excon::HyperMedia::ResourceObject::Properties
|
220
|
+
product.resource.derailleurs.front # => 3
|
221
|
+
product.resource.derailleurs.back # => 7
|
68
222
|
```
|
69
223
|
|
70
|
-
|
224
|
+
Property names that aren't valid method names can always be accessed using the
|
225
|
+
hash notation:
|
71
226
|
|
72
227
|
```ruby
|
73
|
-
|
228
|
+
product.resource['bike-type'] # => 'Mountain Bike'
|
229
|
+
product.resource['BMX'] # => false
|
230
|
+
product.resource.bmx # => false
|
74
231
|
```
|
75
232
|
|
76
|
-
|
77
|
-
|
233
|
+
Properties should be implicitly accessed on a resource, but are internally
|
234
|
+
accessed via the `_properties` method:
|
78
235
|
|
79
236
|
```ruby
|
80
|
-
|
237
|
+
product.resource._properties.class # => Excon::HyperMedia::ResourceObject::Properties
|
238
|
+
```
|
81
239
|
|
82
|
-
|
83
|
-
api.customer-orders(expand: { sort: 'desc' }) # => NoMethodError: undefined method `customer'
|
240
|
+
The `Properties` object inherits its logics from `Enumerable`:
|
84
241
|
|
85
|
-
|
86
|
-
|
242
|
+
```ruby
|
243
|
+
product.resource._properties.to_h.class # => Hash
|
244
|
+
product.resource._properties.first # => ['name', 'bicycle']
|
87
245
|
```
|
88
246
|
|
89
|
-
###
|
247
|
+
### embedded
|
248
|
+
|
249
|
+
Embedded resources are resources that are available through link relations, but
|
250
|
+
embedded in the current resource for easier access.
|
251
|
+
|
252
|
+
For more information on this concept, see the [formal specification][_embedded].
|
90
253
|
|
91
|
-
|
254
|
+
Embedded resources work the same as the top-level resource:
|
92
255
|
|
93
256
|
```ruby
|
94
|
-
product.
|
95
|
-
product.
|
257
|
+
product._embedded.pump.class # => Excon::HyperMedia::ResourceObject
|
258
|
+
product._embedded.pump.weight # => '2kg'
|
96
259
|
```
|
97
260
|
|
98
261
|
## License
|
@@ -103,3 +266,4 @@ The gem is available as open source under the terms of the [MIT License](http://
|
|
103
266
|
[hypermedia]: https://en.wikipedia.org/wiki/HATEOAS
|
104
267
|
[excon-addressable]: https://github.com/JeanMertz/excon-addressable
|
105
268
|
[options]: https://github.com/excon/excon#options
|
269
|
+
[_embedded]: https://tools.ietf.org/html/draft-kelly-json-hal-08#section-4.1.2
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Excon
|
6
|
+
module HyperMedia
|
7
|
+
# Collection
|
8
|
+
#
|
9
|
+
# Given a `Hash`, provides dot-notation properties and other helper methods.
|
10
|
+
#
|
11
|
+
module Collection
|
12
|
+
include Enumerable
|
13
|
+
|
14
|
+
def initialize(collection = {})
|
15
|
+
@collection ||= collection
|
16
|
+
to_properties
|
17
|
+
end
|
18
|
+
|
19
|
+
def each(&block)
|
20
|
+
collection.each(&block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def keys
|
24
|
+
@collection.keys
|
25
|
+
end
|
26
|
+
|
27
|
+
def key?(key)
|
28
|
+
collection.key?(key.to_s)
|
29
|
+
end
|
30
|
+
|
31
|
+
def [](key)
|
32
|
+
to_property(key)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def to_properties
|
38
|
+
collection.each do |key, value|
|
39
|
+
key = key.downcase
|
40
|
+
next unless /[@$"]/ !~ key.to_sym.inspect
|
41
|
+
|
42
|
+
singleton_class.class_eval { attr_reader key }
|
43
|
+
instance_variable_set("@#{key}", property(value))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def property(value)
|
48
|
+
value.respond_to?(:keys) ? self.class.new(value) : value
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_property(key)
|
52
|
+
key?(key) ? property(collection[key]) : nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_property!(key)
|
56
|
+
key?(key) ? to_property(key) : method_missing(key)
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_reader :collection
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Excon
|
4
|
+
module HyperMedia
|
5
|
+
# Link
|
6
|
+
#
|
7
|
+
# Encapsulates a link pointing to a resource.
|
8
|
+
#
|
9
|
+
# @see https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5
|
10
|
+
#
|
11
|
+
class LinkObject
|
12
|
+
include Collection
|
13
|
+
|
14
|
+
# href
|
15
|
+
#
|
16
|
+
# The "href" property is REQUIRED.
|
17
|
+
#
|
18
|
+
# Its value is either a URI [RFC3986] or a URI Template [RFC6570].
|
19
|
+
#
|
20
|
+
# If the value is a URI Template then the Link Object SHOULD have a
|
21
|
+
# "templated" attribute whose value is true.
|
22
|
+
#
|
23
|
+
# @see https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.1
|
24
|
+
#
|
25
|
+
def href
|
26
|
+
to_property!(:href)
|
27
|
+
end
|
28
|
+
|
29
|
+
# The "templated" property is OPTIONAL.
|
30
|
+
#
|
31
|
+
# Its value is boolean and SHOULD be true when the Link Object's "href"
|
32
|
+
# property is a URI Template.
|
33
|
+
#
|
34
|
+
# Its value SHOULD be considered false if it is undefined or any other
|
35
|
+
# value than true.
|
36
|
+
#
|
37
|
+
# @see: https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.2
|
38
|
+
#
|
39
|
+
def templated
|
40
|
+
to_property(__method__) || false
|
41
|
+
end
|
42
|
+
|
43
|
+
# type
|
44
|
+
#
|
45
|
+
# The "type" property is OPTIONAL.
|
46
|
+
#
|
47
|
+
# Its value is a string used as a hint to indicate the media type
|
48
|
+
# expected when dereferencing the target resource.
|
49
|
+
#
|
50
|
+
# @see: https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.3
|
51
|
+
#
|
52
|
+
def type
|
53
|
+
to_property!(__method__)
|
54
|
+
end
|
55
|
+
|
56
|
+
# deprecation
|
57
|
+
#
|
58
|
+
# The "deprecation" property is OPTIONAL.
|
59
|
+
#
|
60
|
+
# Its presence indicates that the link is to be deprecated (i.e.
|
61
|
+
# removed) at a future date. Its value is a URL that SHOULD provide
|
62
|
+
# further information about the deprecation.
|
63
|
+
#
|
64
|
+
# A client SHOULD provide some notification (for example, by logging a
|
65
|
+
# warning message) whenever it traverses over a link that has this
|
66
|
+
# property. The notification SHOULD include the deprecation property's
|
67
|
+
# value so that a client manitainer can easily find information about
|
68
|
+
# the deprecation.
|
69
|
+
#
|
70
|
+
# @see https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.4
|
71
|
+
#
|
72
|
+
def deprecation
|
73
|
+
to_property!(__method__)
|
74
|
+
end
|
75
|
+
|
76
|
+
# name
|
77
|
+
#
|
78
|
+
# The "name" property is OPTIONAL.
|
79
|
+
#
|
80
|
+
# Its value MAY be used as a secondary key for selecting Link Objects
|
81
|
+
# which share the same relation type.
|
82
|
+
#
|
83
|
+
# @see https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.5
|
84
|
+
#
|
85
|
+
def name
|
86
|
+
to_property!(__method__)
|
87
|
+
end
|
88
|
+
|
89
|
+
# profile
|
90
|
+
#
|
91
|
+
# The "profile" property is OPTIONAL.
|
92
|
+
#
|
93
|
+
# Its value is a string which is a URI that hints about the profile (as
|
94
|
+
# defined by [I-D.wilde-profile-link]) of the target resource.
|
95
|
+
#
|
96
|
+
# @see https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.6
|
97
|
+
#
|
98
|
+
def profile
|
99
|
+
to_property!(__method__)
|
100
|
+
end
|
101
|
+
|
102
|
+
# title
|
103
|
+
#
|
104
|
+
# The "title" property is OPTIONAL.
|
105
|
+
#
|
106
|
+
# Its value is a string and is intended for labelling the link with a
|
107
|
+
# human-readable identifier (as defined by [RFC5988]).
|
108
|
+
#
|
109
|
+
# @see https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.7
|
110
|
+
#
|
111
|
+
def title
|
112
|
+
to_property!(__method__)
|
113
|
+
end
|
114
|
+
|
115
|
+
# hreflang
|
116
|
+
#
|
117
|
+
# The "hreflang" property is OPTIONAL.
|
118
|
+
#
|
119
|
+
# Its value is a string and is intended for indicating the language of
|
120
|
+
# the target resource (as defined by [RFC5988]).
|
121
|
+
#
|
122
|
+
# @see https://tools.ietf.org/html/draft-kelly-json-hal-08#section-5.8
|
123
|
+
#
|
124
|
+
def hreflang
|
125
|
+
to_property!(__method__)
|
126
|
+
end
|
127
|
+
|
128
|
+
# uri
|
129
|
+
#
|
130
|
+
# Returns a URI representation of the provided "href" property.
|
131
|
+
#
|
132
|
+
# @return [URI] URI object of the "href" property
|
133
|
+
#
|
134
|
+
def uri
|
135
|
+
::Addressable::URI.parse(href)
|
136
|
+
end
|
137
|
+
|
138
|
+
# rel
|
139
|
+
#
|
140
|
+
# Returns an `Excon::Connection` instance, based on the current link.
|
141
|
+
#
|
142
|
+
# @return [Excon::Connection] Connection object based on current link
|
143
|
+
#
|
144
|
+
def rel(params = {})
|
145
|
+
Excon.new(href, params)
|
146
|
+
end
|
147
|
+
|
148
|
+
private def property(value)
|
149
|
+
value
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
Excon.defaults[:middlewares].delete(Excon::Addressable::Middleware)
|
3
4
|
Excon.defaults[:middlewares].unshift(Excon::Addressable::Middleware)
|
4
5
|
|
5
6
|
module Excon
|
@@ -16,6 +17,7 @@ module Excon
|
|
16
17
|
def request_call(datum)
|
17
18
|
return super unless (content_type = datum.dig(:response, :headers, 'Content-Type').to_s)
|
18
19
|
|
20
|
+
datum[:response] ||= {}
|
19
21
|
datum[:response][:hypermedia] = if datum[:hypermedia].nil?
|
20
22
|
content_type.include?('hal+json')
|
21
23
|
else
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Excon
|
4
|
+
module HyperMedia
|
5
|
+
class ResourceObject
|
6
|
+
# Links
|
7
|
+
#
|
8
|
+
# Represents a collection of links part of a resource.
|
9
|
+
#
|
10
|
+
class Embedded
|
11
|
+
include Collection
|
12
|
+
|
13
|
+
private def property(value)
|
14
|
+
if value.respond_to?(:to_ary)
|
15
|
+
value.map { |v| ResourceObject.new(v) }
|
16
|
+
else
|
17
|
+
ResourceObject.new(value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Excon
|
4
|
+
module HyperMedia
|
5
|
+
class ResourceObject
|
6
|
+
# Links
|
7
|
+
#
|
8
|
+
# Represents a collection of links part of a resource.
|
9
|
+
#
|
10
|
+
class Links
|
11
|
+
include Collection
|
12
|
+
|
13
|
+
private def property(value)
|
14
|
+
value.respond_to?(:to_ary) ? value.map { |v| LinkObject.new(v) } : LinkObject.new(value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Excon
|
6
|
+
module HyperMedia
|
7
|
+
class ResourceObject
|
8
|
+
# Properties
|
9
|
+
#
|
10
|
+
# Provides consistent access to resource properties.
|
11
|
+
#
|
12
|
+
class Properties
|
13
|
+
include Collection
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'excon/hypermedia/resource_object/embedded'
|
4
|
+
require 'excon/hypermedia/resource_object/links'
|
5
|
+
require 'excon/hypermedia/resource_object/properties'
|
6
|
+
|
7
|
+
module Excon
|
8
|
+
module HyperMedia
|
9
|
+
# ResourceObject
|
10
|
+
#
|
11
|
+
# Represents a resource.
|
12
|
+
#
|
13
|
+
class ResourceObject
|
14
|
+
RESERVED_PROPERTIES = %w(_links _embedded).freeze
|
15
|
+
|
16
|
+
def initialize(data)
|
17
|
+
@data = data
|
18
|
+
|
19
|
+
_properties.each do |key, value|
|
20
|
+
key = key.downcase
|
21
|
+
next unless /[@$"]/ !~ key.to_sym.inspect
|
22
|
+
|
23
|
+
singleton_class.class_eval { attr_reader key }
|
24
|
+
instance_variable_set("@#{key}", value.respond_to?(:keys) ? Properties.new(value) : value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def _properties
|
29
|
+
@_properties ||= Properties.new(@data.reject { |k, _| RESERVED_PROPERTIES.include?(k) })
|
30
|
+
end
|
31
|
+
|
32
|
+
# _links
|
33
|
+
#
|
34
|
+
# The reserved "_links" property is OPTIONAL.
|
35
|
+
#
|
36
|
+
# It is an object whose property names are link relation types (as
|
37
|
+
# defined by [RFC5988]) and values are either a Link Object or an array
|
38
|
+
# of Link Objects. The subject resource of these links is the Resource
|
39
|
+
# Object of which the containing "_links" object is a property.
|
40
|
+
#
|
41
|
+
# @see https://tools.ietf.org/html/draft-kelly-json-hal-08#section-4.1.1
|
42
|
+
#
|
43
|
+
def _links
|
44
|
+
@_links ||= Links.new(@data['_links'])
|
45
|
+
end
|
46
|
+
|
47
|
+
# _embedded
|
48
|
+
#
|
49
|
+
# The reserved "_embedded" property is OPTIONAL
|
50
|
+
#
|
51
|
+
# It is an object whose property names are link relation types (as
|
52
|
+
# defined by [RFC5988]) and values are either a Resource Object or an
|
53
|
+
# array of Resource Objects.
|
54
|
+
#
|
55
|
+
# Embedded Resources MAY be a full, partial, or inconsistent version of
|
56
|
+
# the representation served from the target URI.
|
57
|
+
#
|
58
|
+
# @see https://tools.ietf.org/html/draft-kelly-json-hal-08#section-4.1.2
|
59
|
+
#
|
60
|
+
def _embedded
|
61
|
+
@_embedded ||= Embedded.new(@data['_embedded'])
|
62
|
+
end
|
63
|
+
|
64
|
+
def [](key)
|
65
|
+
_properties[key]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|