lutaml-hal 0.1.6 → 0.1.8
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 +4 -4
- data/README.adoc +115 -1047
- data/lib/lutaml/hal/client.rb +24 -0
- data/lib/lutaml/hal/endpoint_configuration.rb +50 -0
- data/lib/lutaml/hal/endpoint_parameter.rb +188 -0
- data/lib/lutaml/hal/global_register.rb +4 -0
- data/lib/lutaml/hal/link.rb +56 -1
- data/lib/lutaml/hal/link_class_factory.rb +100 -0
- data/lib/lutaml/hal/link_set_class_factory.rb +92 -0
- data/lib/lutaml/hal/model_register.rb +188 -62
- data/lib/lutaml/hal/page.rb +3 -0
- data/lib/lutaml/hal/resource.rb +61 -55
- data/lib/lutaml/hal/type_resolver.rb +59 -0
- data/lib/lutaml/hal/version.rb +1 -1
- data/lib/lutaml/hal.rb +1 -0
- metadata +7 -2
data/README.adoc
CHANGED
@@ -6,7 +6,6 @@ image:https://img.shields.io/github/license/lutaml/lutaml-hal.svg[License]
|
|
6
6
|
image:https://img.shields.io/github/actions/workflow/status/lutaml/lutaml-hal/test.yml?branch=main[Build Status]
|
7
7
|
image:https://img.shields.io/gem/v/lutaml-hal.svg[RubyGems Version]
|
8
8
|
|
9
|
-
|
10
9
|
== Purpose
|
11
10
|
|
12
11
|
The `lutaml-hal` gem provides a framework for interacting with HAL-compliant
|
@@ -31,7 +30,7 @@ APIs.
|
|
31
30
|
* Tools for pagination and resource resolution
|
32
31
|
* Integration with the `lutaml-model` serialization framework
|
33
32
|
* Error handling and response validation for API interactions
|
34
|
-
|
33
|
+
* Comprehensive embed support for reducing HTTP requests and improving performance
|
35
34
|
|
36
35
|
== Installation
|
37
36
|
|
@@ -56,1114 +55,183 @@ Or install it yourself as:
|
|
56
55
|
$ gem install lutaml-hal
|
57
56
|
----
|
58
57
|
|
59
|
-
==
|
60
|
-
|
61
|
-
The classes in this library are organized into the following modules:
|
62
|
-
|
63
|
-
`Lutaml::Hal::Client`::
|
64
|
-
A client for making HTTP requests to HAL APIs. It includes methods for setting
|
65
|
-
the API endpoint, making GET requests, and handling responses.
|
66
|
-
+
|
67
|
-
NOTE: Only GET requests are supported at the moment.
|
68
|
-
|
69
|
-
`Lutaml::Hal::ModelRegister`::
|
70
|
-
A registry for managing HAL resource models and their endpoints. It allows you
|
71
|
-
to register models, define their relationships, and fetch resources from the
|
72
|
-
API.
|
73
|
-
|
74
|
-
`Lutaml::Hal::GlobalRegister`::
|
75
|
-
A global registry (Singleton) for managing ModelRegisters and facilitating model
|
76
|
-
resolution across different resources. Its usage is optional.
|
77
|
-
|
78
|
-
`Lutaml::Hal::Resource`::
|
79
|
-
A base class for defining HAL resource models. It includes methods for
|
80
|
-
defining attributes, links, and key-value mappings for resources.
|
81
|
-
|
82
|
-
`Lutaml::Hal::Link`::
|
83
|
-
A class for defining HAL links. It includes methods for specifying the
|
84
|
-
relationship between resources and their links, as well as methods for
|
85
|
-
resolving links to their target resources.
|
86
|
-
|
87
|
-
`Lutaml::Hal::Page`::
|
88
|
-
A class for handling pagination in HAL APIs. It includes methods for
|
89
|
-
defining pagination attributes, such as `page`, `pages`, `limit`, and
|
90
|
-
`total`, as well as methods for accessing linked resources within a page.
|
91
|
-
|
92
|
-
|
93
|
-
== Usage overview
|
94
|
-
|
95
|
-
In order to interact with a HAL API using `lutaml-hal`, there are two
|
96
|
-
stages of usage: data definition and runtime.
|
97
|
-
|
98
|
-
At the data definition phase:
|
99
|
-
|
100
|
-
. Define the API endpoint using the `Client` class.
|
101
|
-
. Create a `ModelRegister` to manage the resource models and their
|
102
|
-
respective endpoints.
|
103
|
-
. (optional) Create a `GlobalRegister` to manage one or more `ModelRegister`
|
104
|
-
instances. It is necessary for automatic Link resolution.
|
105
|
-
. Define the resource models using the `Resource` class.
|
106
|
-
. Register the models with the `ModelRegister` and define their
|
107
|
-
relationships using the `add_endpoint` method.
|
108
|
-
|
109
|
-
Once data definition is present, the following operations can be performed at
|
110
|
-
runtime:
|
111
|
-
|
112
|
-
. Fetch resources from the API using the `ModelRegister` and `Link#realize` methods.
|
113
|
-
|
114
|
-
.. Once the resources are fetched, you can access their attributes and links
|
115
|
-
and navigate through the resource graph.
|
116
|
-
|
117
|
-
. Pagination, such as on "index" type pages, can be handled by subclassing the `Page` class.
|
118
|
-
+
|
119
|
-
NOTE: The `Page` class itself is also implemented as a `Resource`, so you can
|
120
|
-
use the same methods to access the page's attributes and links.
|
121
|
-
|
122
|
-
|
123
|
-
== Usage: Data definition
|
124
|
-
|
125
|
-
=== General
|
126
|
-
|
127
|
-
HAL resources need to be defined as models to allow data access and serialization.
|
128
|
-
|
129
|
-
The following steps are required:
|
130
|
-
|
131
|
-
. Define HAL resource models.
|
132
|
-
. Define the base API URL using the `Client` class.
|
133
|
-
. Create a `ModelRegister` to manage the resource models.
|
134
|
-
. Define the resource models' respective endpoints on the base API URL.
|
135
|
-
|
136
|
-
|
137
|
-
=== Creating a HAL model register
|
138
|
-
|
139
|
-
The `ModelRegister` class is used to manage the resource models and their
|
140
|
-
respective endpoints on the base API URL.
|
141
|
-
|
142
|
-
It relies on the `Client` class to perform HTTP requests to the API. The base
|
143
|
-
API URL is defined at the `Client` object.
|
144
|
-
|
145
|
-
NOTE: The base API URL is used for all requests made by the `Client` class,
|
146
|
-
including the requests made by the `ModelRegister` class.
|
147
|
-
|
148
|
-
[source,ruby]
|
149
|
-
----
|
150
|
-
require 'lutaml-hal'
|
151
|
-
|
152
|
-
# Create a new client with API endpoint
|
153
|
-
client = Lutaml::Hal::Client.new(api_url: 'https://api.example.com')
|
154
|
-
register = Lutaml::Hal::ModelRegister.new(name: :my_model_register, client: client)
|
155
|
-
# Or set client later, `register.client = client`
|
156
|
-
----
|
157
|
-
|
158
|
-
The `name:` parameter is used to identify the `ModelRegister` instance.
|
159
|
-
|
160
|
-
=== Creating a HAL global register
|
58
|
+
== Quick start
|
161
59
|
|
162
|
-
|
163
|
-
`ModelRegister` instances.
|
164
|
-
|
165
|
-
It is optional, but is required for automatic realization of models from Link
|
166
|
-
objects. See <<fetching_resource_via_link_realization>> for more details.
|
60
|
+
Here's a minimal example to get you started:
|
167
61
|
|
168
62
|
[source,ruby]
|
169
63
|
----
|
170
64
|
require 'lutaml-hal'
|
171
65
|
|
172
|
-
#
|
173
|
-
|
174
|
-
|
66
|
+
# Define a HAL resource
|
67
|
+
class Product < Lutaml::Hal::Resource
|
68
|
+
attribute :id, :string
|
69
|
+
attribute :name, :string
|
70
|
+
attribute :price, :float
|
175
71
|
|
176
|
-
|
177
|
-
|
72
|
+
hal_link :self, key: 'self', realize_class: 'Product'
|
73
|
+
hal_link :category, key: 'category', realize_class: 'Category'
|
178
74
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
global_register.delete(:my_model_register)
|
184
|
-
----
|
185
|
-
|
186
|
-
|
187
|
-
=== Defining HAL resource models
|
188
|
-
|
189
|
-
==== General
|
190
|
-
|
191
|
-
A HAL resource is defined by creating a subclass of the `Resource` class and
|
192
|
-
defining its attributes, links, and key-value mappings.
|
193
|
-
|
194
|
-
The `Resource` class is the base class for defining HAL resource models.
|
195
|
-
It inherits from `Lutaml::Model::Serializable`, which provides data
|
196
|
-
modelling and serialization capabilities.
|
197
|
-
|
198
|
-
The declaration of attributes, links, and key-value mappings for a HAL resource
|
199
|
-
is performed using the `attribute`, `hal_link`, and `key_value` methods.
|
200
|
-
|
201
|
-
There are 3 levels of data modeling in a HAL resource, all of which are necessary
|
202
|
-
for the full usage of a HAL resource:
|
203
|
-
|
204
|
-
* Resource attributes
|
205
|
-
* Serialization mappings
|
206
|
-
* HAL Links
|
207
|
-
|
208
|
-
|
209
|
-
.Integrated example of a resource model
|
210
|
-
[example]
|
211
|
-
====
|
212
|
-
[source,ruby]
|
213
|
-
----
|
214
|
-
module MyApi
|
215
|
-
class Product < Lutaml::Hal::Resource
|
216
|
-
attribute :id, :string
|
217
|
-
attribute :name, :string
|
218
|
-
attribute :price, :float
|
219
|
-
|
220
|
-
hal_link :self, key: 'self', realize_class: 'Product'
|
221
|
-
hal_link :category, key: 'category', realize_class: 'Category'
|
222
|
-
|
223
|
-
key_value do
|
224
|
-
map 'id', to: :id
|
225
|
-
map 'name', to: :name
|
226
|
-
map 'price', to: :price
|
227
|
-
end
|
75
|
+
key_value do
|
76
|
+
map 'id', to: :id
|
77
|
+
map 'name', to: :name
|
78
|
+
map 'price', to: :price
|
228
79
|
end
|
229
80
|
end
|
230
|
-
----
|
231
|
-
====
|
232
|
-
|
233
|
-
|
234
|
-
==== Resource attributes
|
235
|
-
|
236
|
-
A resource attribute is a direct property of the HAL resource.
|
237
|
-
|
238
|
-
These attributes typically hold values of simple data types, and are directly
|
239
|
-
serialized into JSON.
|
240
|
-
|
241
|
-
These attributes are declared using the `attribute` method from `lutaml-model`.
|
242
|
-
|
243
|
-
[example]
|
244
|
-
====
|
245
|
-
A HAL resource of class `Product` can have attributes `id`, `name`, and `price`.
|
246
|
-
====
|
247
|
-
|
248
|
-
Please refer to syntax as described in the
|
249
|
-
https://github.com/lutaml/lutaml-model[`lutaml-model`] documentation.
|
250
|
-
|
251
|
-
.Example of a resource model with attributes
|
252
|
-
[example]
|
253
|
-
====
|
254
|
-
[source,ruby]
|
255
|
-
----
|
256
|
-
module MyApi
|
257
|
-
class Product < Lutaml::Hal::Resource
|
258
|
-
attribute :id, :string
|
259
|
-
attribute :name, :string
|
260
|
-
attribute :price, :float
|
261
|
-
# ...
|
262
|
-
end
|
263
|
-
end
|
264
|
-
----
|
265
|
-
====
|
266
|
-
|
267
|
-
==== Serialization mapping of resource attributes
|
268
|
-
|
269
|
-
A serialization mapping defines rules to serialize a HAL resource to and from a
|
270
|
-
serialization format. In HAL, the serialization format is JSON, but other formats
|
271
|
-
can also be supported.
|
272
|
-
|
273
|
-
The mapping between the HAL model attributes and their corresponding JSON
|
274
|
-
serialization is performed using the `key_value do` or `json do` blocks from
|
275
|
-
`lutaml-model`. The mapping of the contents of `_links` is automatically
|
276
|
-
performed using `hal_link`.
|
277
|
-
|
278
|
-
[example]
|
279
|
-
====
|
280
|
-
A HAL resource of class `Product` with attributes `id`, `name`, and `price` will
|
281
|
-
need to declare a `key_value` block to map the attributes to their corresponding
|
282
|
-
JSON keys, namely, `"id"`, `"name"`, and `"price"`.
|
283
|
-
====
|
284
|
-
|
285
|
-
Please refer to syntax as described in the
|
286
|
-
https://github.com/lutaml/lutaml-model[`lutaml-model`] documentation.
|
287
|
-
|
288
|
-
.Example of a resource model with serialization mapping
|
289
|
-
[example]
|
290
|
-
====
|
291
|
-
[source,ruby]
|
292
|
-
----
|
293
|
-
module MyApi
|
294
|
-
class Product < Lutaml::Hal::Resource
|
295
|
-
attribute :id, :string
|
296
|
-
attribute :name, :string
|
297
|
-
attribute :price, :float
|
298
|
-
|
299
|
-
key_value do
|
300
|
-
map 'id', to: :id
|
301
|
-
map 'name', to: :name
|
302
|
-
map 'price', to: :price
|
303
|
-
end
|
304
|
-
end
|
305
|
-
end
|
306
|
-
----
|
307
|
-
====
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
==== HAL Links
|
312
|
-
|
313
|
-
A HAL resource has links to other resources, typically serialized in
|
314
|
-
the `_links` section of the JSON response.
|
315
|
-
|
316
|
-
[example]
|
317
|
-
====
|
318
|
-
A HAL resource of class `Product` can have links `self` (which is a
|
319
|
-
self-referential identifier link) and `category`.
|
320
|
-
====
|
321
|
-
|
322
|
-
HAL links need to be defined in the resource model to allow the resolution of
|
323
|
-
the links to their target resources.
|
324
|
-
|
325
|
-
These links are declared using the `hal_link` method provided by `lutaml-hal`.
|
326
|
-
|
327
|
-
Syntax:
|
328
|
-
|
329
|
-
[source,ruby]
|
330
|
-
----
|
331
|
-
hal_link :link_name,
|
332
|
-
key: 'link_key',
|
333
|
-
realize_class: 'TargetResourceClass',
|
334
|
-
link_class: 'LinkClass',
|
335
|
-
link_set_class: 'LinkSetClass'
|
336
|
-
----
|
337
|
-
|
338
|
-
`:link_name`:: The name of the link, which will be used to access the link in
|
339
|
-
the resource object.
|
340
|
-
|
341
|
-
`key: 'link_key'`:: The key of the link in the JSON response. This is the name
|
342
|
-
of the link as it appears in the `_links` section of the HAL resource.
|
343
|
-
|
344
|
-
`realize_class: 'TargetResourceClass'`:: The class of the target resource that
|
345
|
-
the link points to. This is used to resolve the link to the associated resource.
|
346
|
-
|
347
|
-
`link_class: 'LinkClass'`:: (optional) The class of the link that defines
|
348
|
-
specific behavior or attributes for the link object itself. This is dynamically
|
349
|
-
created and is inherited from `Lutaml::Hal::Link` if not provided.
|
350
|
-
|
351
|
-
`link_set_class: 'LinkSetClass'`:: (optional) The class of the link set object
|
352
|
-
that contains the links. This is dynamically created and is inherited from
|
353
|
-
`Lutaml::Model::Serializable` if not provided.
|
354
|
-
|
355
|
-
|
356
|
-
The `_links` section is modeled as a dynamically created link set class, named
|
357
|
-
after the resource's class name (with an appended `LinkSet` string), which in turn
|
358
|
-
contains the defined links to other resources. The link set class is inherited
|
359
|
-
from `Lutaml::Model::Serializable`.
|
360
|
-
|
361
|
-
[example]
|
362
|
-
====
|
363
|
-
A HAL resource of class `Product` may have a link set of class `ProductLinkSet`
|
364
|
-
which contains the `self` and `category` links as its attributes.
|
365
|
-
====
|
366
|
-
|
367
|
-
|
368
|
-
Each link object of the link set is provided as a `Link` object that is
|
369
|
-
dynamically created for the type of resolved resource. The name of the link
|
370
|
-
class is the same as the resource class name with an appended `Link` string.
|
371
|
-
This Link class is inherited from `Lutaml::Hal::Link`.
|
372
|
-
|
373
|
-
[example]
|
374
|
-
====
|
375
|
-
A HAL resource of class `Product` with a link set that contains the `self`
|
376
|
-
(points to a `Product`) and `category` (points to a `Category`) links will
|
377
|
-
have:
|
378
|
-
|
379
|
-
* a link set of class `ProductLinks` which contains:
|
380
|
-
** a `self` attribute that is an instance of `ProductLink`
|
381
|
-
** a `category` attribute that is an instance of `CategoryLink`
|
382
|
-
====
|
383
|
-
|
384
|
-
|
385
|
-
.Integrated example of a HAL resource model using auto-generated LinkSet and Link classes
|
386
|
-
[example]
|
387
|
-
====
|
388
|
-
For an instance of `Product`:
|
389
|
-
|
390
|
-
[source,ruby]
|
391
|
-
----
|
392
|
-
module MyApi
|
393
|
-
class Product < Lutaml::Hal::Resource
|
394
|
-
attribute :id, :string
|
395
|
-
attribute :name, :string
|
396
|
-
attribute :price, :float
|
397
|
-
|
398
|
-
hal_link :self, key: 'self', realize_class: 'Product'
|
399
|
-
hal_link :category, key: 'category', realize_class: 'Category'
|
400
|
-
|
401
|
-
key_value do
|
402
|
-
map 'id', to: :id
|
403
|
-
map 'name', to: :name
|
404
|
-
map 'price', to: :price
|
405
|
-
end
|
406
|
-
end
|
407
|
-
end
|
408
|
-
----
|
409
|
-
|
410
|
-
The library will provide:
|
411
|
-
|
412
|
-
* the link set (serialized in HAL as JSON `_links`) in the class `ProductLinks`.
|
413
|
-
|
414
|
-
* the link set contains the `self` and the `category` links of class `Lutaml::Hal::Link`.
|
415
|
-
|
416
|
-
As a result:
|
417
|
-
|
418
|
-
* calling `product.links.self` will return an instance of `ProductLink`.
|
419
|
-
|
420
|
-
* calling `product.links.self.realize(register)` will dynamically fetch and
|
421
|
-
return an instance of `Product`.
|
422
|
-
====
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
==== Custom link set class
|
427
|
-
|
428
|
-
When a custom link set class (via `link_set_class:`) is provided, links are no
|
429
|
-
longer automatically added to the link set via `hal_link`. Please ensure that
|
430
|
-
all links are defined as model `attributes` and their `key_value` mappings
|
431
|
-
provided.
|
432
|
-
|
433
|
-
This is useful for the scenario where the link set needs to be
|
434
|
-
customized to provide additional attributes or behavior.
|
435
|
-
|
436
|
-
A LinkSetClass for a resource must implement the following interface:
|
437
|
-
|
438
|
-
[source,ruby]
|
439
|
-
----
|
440
|
-
module MyApi
|
441
|
-
# This represents the link set of a Resource
|
442
|
-
class ResourceLinkSet < Lutaml::Model::Serializable
|
443
|
-
attribute :attribute_name_1, :link_class_1, collection: {true|false}
|
444
|
-
attribute :attribute_name_2, :link_class_2, collection: {true|false}
|
445
|
-
# ...
|
446
|
-
|
447
|
-
key_value do
|
448
|
-
map 'link_key_1', to: :attribute_name_1
|
449
|
-
map 'link_key_2', to: :attribute_name_2
|
450
|
-
# ...
|
451
|
-
end
|
452
|
-
end
|
453
|
-
|
454
|
-
# This represents the basic setup of a Resource with a custom LinkSet class
|
455
|
-
class Resource < Lutaml::Hal::Resource
|
456
|
-
attribute :links, ResourceLinkSet
|
457
|
-
# Define resource attributes
|
458
|
-
|
459
|
-
key_value do
|
460
|
-
# This is the mapping of the `_links` key to the attribute `links`.
|
461
|
-
map '_links', to: :links
|
462
|
-
# Mappings for resource attributes need to be explicitly provided
|
463
|
-
end
|
464
|
-
end
|
465
|
-
end
|
466
|
-
----
|
467
|
-
|
468
|
-
Alternatively, it is possible to re-open the dynamically created link set class
|
469
|
-
and add additional attributes to it.
|
470
|
-
|
471
|
-
.Override the default link set class for Product
|
472
|
-
[source,ruby]
|
473
|
-
----
|
474
|
-
module MyApi
|
475
|
-
class Product < Lutaml::Hal::Resource
|
476
|
-
attribute :id, :string
|
477
|
-
end
|
478
|
-
# The class `MyApi::ProductLinkSet` is created automatically by the library.
|
479
|
-
|
480
|
-
# Re-open the default link set class and add additional attributes
|
481
|
-
class ProductLinkSet < Lutaml::Hal::LinkSet
|
482
|
-
# Add additional attributes to the link set
|
483
|
-
attribute :custom_link_set_attribute, Something, collection: false
|
484
|
-
|
485
|
-
key_value do
|
486
|
-
map 'my_custom_link', to: :custom_link_set_attribute
|
487
|
-
end
|
488
|
-
end
|
489
|
-
end
|
490
|
-
----
|
491
|
-
|
492
|
-
==== Custom link class
|
493
|
-
|
494
|
-
When a custom link class (via `link_class:`) is provided, the custom link class
|
495
|
-
is automatically added into the link set.
|
496
|
-
|
497
|
-
This makes it possible to:
|
498
|
-
|
499
|
-
* supplement the link with additional attributes, or
|
500
|
-
* override the `realize(register)` method to provide custom behavior for the link.
|
501
|
-
|
502
|
-
A Link class pointing to a resource must implement the following interface:
|
503
|
-
|
504
|
-
[source,ruby]
|
505
|
-
----
|
506
|
-
module MyApi
|
507
|
-
# This represents a link set pointing to a Resource
|
508
|
-
class TargetResourceLink < Lutaml::Model::Serializable
|
509
|
-
# This is the link class for the resource class Resource
|
510
|
-
# 'default:' needs to be set to the name of the target resource class
|
511
|
-
attribute :type, :string, default: 'Resource'
|
512
|
-
|
513
|
-
# No specification of key_value block needed since attribute presence
|
514
|
-
# provides a default mapping.
|
515
|
-
end
|
516
|
-
end
|
517
|
-
----
|
518
|
-
|
519
|
-
Alternatively, it is possible to re-open the dynamically created link class and add
|
520
|
-
additional attributes to it.
|
521
|
-
|
522
|
-
.Override the default link class for Product
|
523
|
-
[source,ruby]
|
524
|
-
----
|
525
|
-
module MyApi
|
526
|
-
class Product < Lutaml::Hal::Resource
|
527
|
-
attribute :id, :string
|
528
|
-
hal_link :category, key: 'category', realize_class: 'Category'
|
529
|
-
end
|
530
|
-
# The class `MyApi::CategoryLink` is created automatically by the library.
|
531
|
-
|
532
|
-
# Re-open the default link class and add additional attributes
|
533
|
-
class CategoryLink < Lutaml::Hal::Link
|
534
|
-
# Add additional attributes to the link
|
535
|
-
attribute :language_code, :string, collection: false
|
536
|
-
|
537
|
-
key_value do
|
538
|
-
map 'language_code', to: :language_code
|
539
|
-
end
|
540
|
-
end
|
541
|
-
end
|
542
|
-
----
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
=== Registering resource models and endpoints
|
547
|
-
|
548
|
-
The `ModelRegister` allows you to register resource models and their endpoints.
|
549
|
-
|
550
|
-
You can define endpoints for collections (index) and individual resources
|
551
|
-
(resource) using the `add_endpoint` method.
|
552
81
|
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
`id`:: A unique identifier for the endpoint.
|
557
|
-
|
558
|
-
`type`:: The type of endpoint, which can be `index` or `resource`.
|
559
|
-
|
560
|
-
`url`:: The URL of the endpoint, which can include path parameters.
|
561
|
-
+
|
562
|
-
In the `url`, you can use interpolation parameters, which will be replaced with
|
563
|
-
the actual values when fetching the resource. The interpolation parameters are
|
564
|
-
defined in the `url` string using curly braces `{}`.
|
565
|
-
|
566
|
-
`model`:: The class of the resource that will be fetched from the API.
|
567
|
-
The class must inherit from `Lutaml::Hal::Resource`.
|
568
|
-
|
569
|
-
`query_params`:: (optional) A hash defining query parameters that should be
|
570
|
-
appended to the URL when fetching the resource. Supports parameter templates
|
571
|
-
using curly braces `{}` for dynamic values.
|
572
|
-
+
|
573
|
-
This is essential for APIs that require query parameters for pagination,
|
574
|
-
filtering, or other functionality where the same base URL needs different query
|
575
|
-
parameters to access different resources or views.
|
576
|
-
|
577
|
-
The `add_endpoint` method will automatically handle the URL resolution and fetch
|
578
|
-
the resource from the API.
|
579
|
-
|
580
|
-
When the `ModelRegister` fetches a resource using the `realize` method, it will
|
581
|
-
match the resource URL against registered paths in order to find the
|
582
|
-
appropriate model class to use for deserialization and resolution.
|
583
|
-
|
584
|
-
Syntax:
|
585
|
-
|
586
|
-
[source,ruby]
|
587
|
-
----
|
588
|
-
register.add_endpoint( <1>
|
589
|
-
id: :model_index, <2>
|
590
|
-
type: :index, <3>
|
591
|
-
url: '/url_supporting_interpolation/{param}', <4>
|
592
|
-
model: ModelClass <5>
|
593
|
-
)
|
594
|
-
----
|
595
|
-
<1> The `add_endpoint` method is used to register an endpoint for a model.
|
596
|
-
<2> The `id` is a unique identifier for the endpoint, which is required to
|
597
|
-
fetch the resource later.
|
598
|
-
<3> The `type` specifies the type of endpoint, which can be `index` or
|
599
|
-
`resource`. The `index` type is used for collections, while the
|
600
|
-
`resource` type is used for individual resources.
|
601
|
-
<4> The `url` is the URL of the endpoint, which can include path
|
602
|
-
parameters. The URL can also include interpolation parameters, which
|
603
|
-
will be replaced with the actual values when fetching the resource.
|
604
|
-
<5> The `model` is the class of the resource that will be fetched from
|
605
|
-
the API. The class must inherit from `Lutaml::Hal::Resource`.
|
606
|
-
|
607
|
-
|
608
|
-
.Example of registering and using query parameters
|
609
|
-
[example]
|
610
|
-
====
|
611
|
-
[source,ruby]
|
612
|
-
----
|
613
|
-
# Register an endpoint that supports pagination via query parameters
|
614
|
-
register.add_endpoint(
|
615
|
-
id: :product_index,
|
616
|
-
type: :index,
|
617
|
-
url: '/products',
|
618
|
-
model: ProductIndex,
|
619
|
-
query_params: {
|
620
|
-
'page' => '{page}',
|
621
|
-
'items' => '{items}'
|
622
|
-
}
|
623
|
-
)
|
624
|
-
|
625
|
-
# Fetch the first page with 10 items per page
|
626
|
-
page_1 = register.fetch(:product_index, page: 1, items: 10)
|
627
|
-
# => client.get('/products?page=1&items=10')
|
628
|
-
|
629
|
-
# Fetch the second page with 5 items per page
|
630
|
-
page_2 = register.fetch(:product_index, page: 2, items: 5)
|
631
|
-
# => client.get('/products?page=2&items=5')
|
632
|
-
----
|
633
|
-
====
|
634
|
-
|
635
|
-
|
636
|
-
.Example of registering the Product class to both index and resource endpoints
|
637
|
-
[example]
|
638
|
-
====
|
639
|
-
[source,ruby]
|
640
|
-
----
|
641
|
-
register.add_endpoint(
|
642
|
-
id: :product_index,
|
643
|
-
type: :index,
|
644
|
-
url: '/products',
|
645
|
-
model: Product
|
646
|
-
)
|
82
|
+
# Set up API client and register
|
83
|
+
client = Lutaml::Hal::Client.new(api_url: 'https://api.example.com')
|
84
|
+
register = Lutaml::Hal::ModelRegister.new(name: :my_api, client: client)
|
647
85
|
|
86
|
+
# Register endpoints
|
648
87
|
register.add_endpoint(
|
649
88
|
id: :product_resource,
|
650
89
|
type: :resource,
|
651
90
|
url: '/products/{id}',
|
652
91
|
model: Product
|
653
92
|
)
|
654
|
-
----
|
655
|
-
====
|
656
|
-
|
657
|
-
|
658
|
-
.Example of using query_params for pagination
|
659
|
-
[example]
|
660
|
-
====
|
661
|
-
[source,ruby]
|
662
|
-
----
|
663
|
-
# Register an endpoint that supports pagination via query parameters
|
664
|
-
register.add_endpoint(
|
665
|
-
id: :product_index_paginated,
|
666
|
-
type: :index,
|
667
|
-
url: '/products',
|
668
|
-
model: ProductIndex,
|
669
|
-
query_params: {
|
670
|
-
'page' => '{page}',
|
671
|
-
'items' => '{items}'
|
672
|
-
}
|
673
|
-
)
|
674
|
-
|
675
|
-
# Fetch the first page with 10 items per page
|
676
|
-
page_1 = register.fetch(:product_index_paginated, page: 1, items: 10)
|
677
|
-
# => client.get('/products?page=1&items=10')
|
678
|
-
|
679
|
-
# Fetch the second page with 5 items per page
|
680
|
-
page_2 = register.fetch(:product_index_paginated, page: 2, items: 5)
|
681
|
-
# => client.get('/products?page=2&items=5')
|
682
|
-
----
|
683
|
-
====
|
684
|
-
|
685
|
-
|
686
|
-
[[defining_hal_page_models]]
|
687
|
-
=== Defining HAL page models
|
688
|
-
|
689
|
-
HAL index APIs often support pagination, which allows clients to retrieve a
|
690
|
-
limited number of resources at a time.
|
691
|
-
|
692
|
-
The `Lutaml::Hal::Page` class is used to handle pagination in HAL APIs. It is a
|
693
|
-
subclass of `Resource`, and provides additional attributes and methods for
|
694
|
-
handling pagination information
|
695
|
-
|
696
|
-
The `Page` class by default supports the following attributes:
|
697
|
-
|
698
|
-
`page`:: The current page number.
|
699
|
-
`pages`:: The total number of pages.
|
700
|
-
`limit`:: The number of resources per page.
|
701
|
-
`total`:: The total number of resources.
|
702
|
-
|
703
|
-
The way to use the `Page` class is through inheritance from it, where the
|
704
|
-
class will automatically create the necessary links for typical page objects.
|
705
|
-
|
706
|
-
The typical links of a page object are:
|
707
|
-
|
708
|
-
`self`:: A link to the current page.
|
709
|
-
`prev`:: A link to the previous page.
|
710
|
-
`next`:: A link to the next page.
|
711
|
-
`first`:: A link to the first page.
|
712
|
-
`last`:: A link to the last page.
|
713
|
-
|
714
|
-
The "realize class" of these links are the same as the inherited page
|
715
|
-
object, ensuring consistency in the pagination model.
|
716
|
-
|
717
|
-
Syntax:
|
718
|
-
|
719
|
-
[source,ruby]
|
720
|
-
----
|
721
|
-
class ProductIndex < Lutaml::Hal::Page
|
722
|
-
# No attributes necessary
|
723
|
-
end
|
724
|
-
|
725
|
-
register.add_endpoint(
|
726
|
-
id: :product_index,
|
727
|
-
type: :index,
|
728
|
-
url: '/products',
|
729
|
-
model: ProductIndex
|
730
|
-
)
|
731
|
-
|
732
|
-
page_1 = register.fetch(:product_index) # Updated to use the correct endpoint id
|
733
|
-
page_2_link = page_1.links.next
|
734
|
-
# => <#ProductIndexLink href: "/products/2", title: "Next Page">
|
735
|
-
----
|
736
|
-
|
737
|
-
Where,
|
738
|
-
|
739
|
-
`ProductIndex`:: The class of the page that will be fetched from the API. The class
|
740
|
-
must inherit from `Lutaml::Hal::Page`.
|
741
|
-
`register`:: The instance of `ModelRegister`.
|
742
|
-
`id`:: The ID of the pagination endpoint to be registered in the `ModelRegister`.
|
743
|
-
`url`:: The URL of the pagination endpoint.
|
744
|
-
`model`:: The class of the page that will be fetched from the API.
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
== Usage: Runtime
|
750
|
-
|
751
|
-
=== General
|
752
|
-
|
753
|
-
NOTE: The `lutaml-hal` library currently only supports synchronous data fetching.
|
754
|
-
Asynchronous data fetching will be supported in the future.
|
755
|
-
|
756
|
-
NOTE: The `lutaml-hal` library currently only supports data fetching requests
|
757
|
-
(GET) today. Additional features may be provided in the future.
|
758
|
-
|
759
|
-
Once the data definition is complete, you can use the `ModelRegister` to
|
760
|
-
fetch and interact with resources from the API.
|
761
|
-
|
762
|
-
=== Fetching a resource
|
763
|
-
|
764
|
-
The `ModelRegister` allows you to fetch resources from the API using the `fetch`
|
765
|
-
method.
|
766
|
-
|
767
|
-
NOTE: The endpoint of the resource must be already defined through the
|
768
|
-
`add_endpoint` method.
|
769
|
-
|
770
|
-
The `fetch` method will automatically handle the URL resolution and fetch the
|
771
|
-
resource from the API.
|
772
|
-
|
773
|
-
Syntax:
|
774
|
-
|
775
|
-
[source,ruby]
|
776
|
-
----
|
777
|
-
register.fetch(:resource_endpoint_id, {parameters})
|
778
|
-
----
|
779
93
|
|
780
|
-
|
94
|
+
# Fetch and use resources
|
95
|
+
product = register.fetch(:product_resource, id: '123')
|
96
|
+
puts product.name
|
97
|
+
puts product.price
|
781
98
|
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
are used to replace the interpolation parameters in the URL.
|
786
|
-
`register`:: The instance of `ModelRegister`.
|
787
|
-
|
788
|
-
|
789
|
-
.Fetch a resource directly from the API
|
790
|
-
[example]
|
791
|
-
====
|
792
|
-
[source,ruby]
|
793
|
-
----
|
794
|
-
product_1 = register.fetch(:product_resource, id: 1)
|
795
|
-
# => client.get('/products/1')
|
796
|
-
|
797
|
-
# => {
|
798
|
-
# "id": 1,
|
799
|
-
# "name": "Product 1",
|
800
|
-
# "price": 10.0,
|
801
|
-
# "_links": {
|
802
|
-
# "self": { "href": "/products/1" },
|
803
|
-
# "category": { "href": "/categories/1", "title": "Category 1" },
|
804
|
-
# "related": [
|
805
|
-
# { "href": "/products/3", "title": "Product 3" },
|
806
|
-
# { "href": "/products/5", "title": "Product 5" }
|
807
|
-
# ]
|
808
|
-
# }
|
809
|
-
# }
|
810
|
-
|
811
|
-
product_1
|
812
|
-
# => #<Product id: 1, name: "Product 1", price: 10.0, links:
|
813
|
-
# #<ProductLinks self: <ProductLink href: "/products/1">,
|
814
|
-
# category: <ProductLink href: "/categories/1", title: "Category 1">,
|
815
|
-
# related: [
|
816
|
-
# <ProductLink href: "/products/3", title: "Product 3">,
|
817
|
-
# <ProductLink href: "/products/5", title: "Product 5">
|
818
|
-
# ]}>
|
819
|
-
----
|
820
|
-
====
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
=== Fetching a resource index
|
825
|
-
|
826
|
-
In HAL, collections are provided via the `_links` or the `_embedded` sections of
|
827
|
-
the response.
|
828
|
-
|
829
|
-
NOTE: The `_embedded` section is not yet supported by the `Lutaml::Hal` library.
|
830
|
-
|
831
|
-
The `ModelRegister` allows you to define endpoints for collections and fetch
|
832
|
-
them using the `fetch` method.
|
833
|
-
|
834
|
-
The `fetch` method will automatically handle the URL resolution and fetch the
|
835
|
-
resource index from the API.
|
836
|
-
|
837
|
-
Syntax:
|
838
|
-
|
839
|
-
[source,ruby]
|
840
|
-
----
|
841
|
-
register.fetch(:index_endpoint_id)
|
842
|
-
----
|
843
|
-
|
844
|
-
Where,
|
845
|
-
|
846
|
-
`index_endpoint_id`:: The ID of the endpoint registered in the `ModelRegister`.
|
847
|
-
`register`:: The instance of `ModelRegister`.
|
848
|
-
|
849
|
-
|
850
|
-
.Fetch a collection of resources from the API
|
851
|
-
[example]
|
852
|
-
====
|
853
|
-
[source,ruby]
|
854
|
-
----
|
855
|
-
product_index = register.fetch(:product_index)
|
856
|
-
# => client.get('/products')
|
857
|
-
|
858
|
-
# => {
|
859
|
-
# "page": 1,
|
860
|
-
# "pages": 10,
|
861
|
-
# "limit": 10,
|
862
|
-
# "total": 45,
|
863
|
-
# "_links": {
|
864
|
-
# "self": { "href": "/products/1" },
|
865
|
-
# "next": { "href": "/products/2" },
|
866
|
-
# "last": { "href": "/products/5" },
|
867
|
-
# "products": [
|
868
|
-
# { "href": "/products/1", "title": "Product 1" },
|
869
|
-
# { "href": "/products/2", "title": "Product 2" }
|
870
|
-
# ]
|
871
|
-
# }
|
872
|
-
|
873
|
-
product_index
|
874
|
-
# => #<ProductPage page: 1, pages: 10, limit: 10, total: 45,
|
875
|
-
# links: #<ProductLinks self: <ProductLink href: "/products/1">,
|
876
|
-
# next: <ProductLink href: "/products/2">,
|
877
|
-
# last: <ProductLink href: "/products/5">,
|
878
|
-
# products: <ProductLinks
|
879
|
-
# <ProductLink href: "/products/1", title: "Product 1">,
|
880
|
-
# <ProductLink href: "/products/2", title: "Product 2">
|
881
|
-
# ]>>
|
882
|
-
----
|
883
|
-
====
|
884
|
-
|
885
|
-
|
886
|
-
[[fetching_resource_via_link_realization]]
|
887
|
-
=== Fetching a resource via link realization
|
888
|
-
|
889
|
-
Given a resource index that contains links to resources, the individual resource
|
890
|
-
links can be "realized" as actual model instances through the
|
891
|
-
`Link#realize(register:)` method which dynamically retrieves the resource.
|
892
|
-
|
893
|
-
Given a `Link` object, the `realize` method fetches the resource from the API
|
894
|
-
using the provided `register`.
|
895
|
-
|
896
|
-
There are two ways a resource gets realized from a `Link` object:
|
897
|
-
|
898
|
-
* If a `Lutaml::Hal::GlobalRegister` is used, and the `Link` object originated
|
899
|
-
from a fetch using a `ModelRegister` then the `realize` method has sufficient
|
900
|
-
information to automatically fetch the resource from the API using the same
|
901
|
-
`register`.
|
902
|
-
+
|
903
|
-
NOTE: This relies on the `Hal::REGISTER_ID_ATTR_NAME` attribute to be set
|
904
|
-
in the `ModelRegister` class. This attribute is used to identify the
|
905
|
-
resource endpoint ID in the URL.
|
906
|
-
|
907
|
-
* If a `GlobalRegister` is not used, even if the Link object originated
|
908
|
-
from a fetch using a `ModelRegister`, the `realize` method does not have sufficient
|
909
|
-
information to fetch the resource from the API using the same
|
910
|
-
`register`. In this case an explicit `register` must be provided to the
|
911
|
-
`realize(register: ...)` method.
|
912
|
-
|
913
|
-
Syntax for standalone usage:
|
914
|
-
|
915
|
-
[source,ruby]
|
916
|
-
----
|
917
|
-
Lutaml::Model::Link.new(
|
918
|
-
href: 'resource_endpoint_href',
|
919
|
-
# ... other attributes
|
920
|
-
).realize(register)
|
921
|
-
----
|
922
|
-
|
923
|
-
Where,
|
924
|
-
|
925
|
-
`resource_endpoint_href`:: The href of the resource endpoint. This is the URL of the
|
926
|
-
resource as it appears in the `_links` section of the HAL resource.
|
927
|
-
`register`:: The instance of `ModelRegister`.
|
928
|
-
|
929
|
-
The `realize` method will automatically handle the URL resolution and fetch
|
930
|
-
the resource from the API, and return an instance of the resource class
|
931
|
-
defined in the `ModelRegister` (through the endpoint definition of `realize_class`).
|
932
|
-
|
933
|
-
NOTE: It is possible to use the `realize` method on a link object using another
|
934
|
-
`ModelRegister` instance. This is useful when you want to resolve a link
|
935
|
-
using a different API endpoint or a different set of resource models.
|
936
|
-
|
937
|
-
Syntax when using a `GlobalRegister`:
|
938
|
-
|
939
|
-
[source,ruby]
|
940
|
-
----
|
941
|
-
resource_index = model_register.fetch(:resource_index)
|
942
|
-
resource_index.links.products.first.realize
|
943
|
-
# => client.get('/resources/1')
|
944
|
-
----
|
945
|
-
|
946
|
-
.Dynamically realizing a resource from the collection using links
|
947
|
-
[example]
|
948
|
-
====
|
949
|
-
[source,ruby]
|
99
|
+
# Navigate to related resources
|
100
|
+
category = product.links.category.realize(register)
|
101
|
+
puts category.name
|
950
102
|
----
|
951
|
-
# Without a GlobalRegister
|
952
|
-
product_2 = product_index.links.products.last.realize(register)
|
953
103
|
|
954
|
-
|
955
|
-
product_2 = product_index.links.products.last.realize
|
104
|
+
== Documentation
|
956
105
|
|
957
|
-
|
958
|
-
# => {
|
959
|
-
# "id": 2,
|
960
|
-
# "name": "Product 2",
|
961
|
-
# "price": 20.0,
|
962
|
-
# "_links": {
|
963
|
-
# "self": { "href": "/products/2" },
|
964
|
-
# "category": { "href": "/categories/2", "title": "Category 2" },
|
965
|
-
# "related": [
|
966
|
-
# { "href": "/products/4", "title": "Product 4" },
|
967
|
-
# { "href": "/products/6", "title": "Product 6" }
|
968
|
-
# ]
|
969
|
-
# }
|
970
|
-
# }
|
106
|
+
=== Comprehensive guides
|
971
107
|
|
972
|
-
|
973
|
-
# => #<Product id: 2, name: "Product 2", price: 20.0, links:
|
974
|
-
# #<ProductLinks self: <ProductLink href: "/products/2">,
|
975
|
-
# category: <ProductLink href: "/categories/2", title: "Category 2">,
|
976
|
-
# related: [
|
977
|
-
# <ProductLink href: "/products/4", title: "Product 4">,
|
978
|
-
# <ProductLink href: "/products/6", title: "Product 6">
|
979
|
-
# ]}>
|
108
|
+
For detailed documentation, see these comprehensive guides:
|
980
109
|
|
981
|
-
|
982
|
-
|
110
|
+
* **link:docs/getting-started-guide.adoc[Getting started guide]** - Step-by-step
|
111
|
+
tutorial for your first HAL API client (15 minutes)
|
983
112
|
|
984
|
-
|
985
|
-
|
986
|
-
----
|
987
|
-
====
|
988
|
-
|
989
|
-
|
990
|
-
=== Handling HAL pages / pagination
|
113
|
+
* **link:docs/data-definition-guide.adoc[Data definition guide]** - Complete
|
114
|
+
reference for defining HAL resources, model registers, and API endpoints
|
991
115
|
|
992
|
-
|
116
|
+
* **link:docs/runtime-usage-guide.adoc[Runtime usage guide]** - Patterns for
|
117
|
+
fetching resources, navigating links, and handling pagination
|
993
118
|
|
994
|
-
|
119
|
+
* **link:docs/hal-links-reference.adoc[HAL links reference]** - Advanced
|
120
|
+
configuration and customization of HAL links
|
995
121
|
|
996
|
-
|
997
|
-
|
998
|
-
through pages of resources.
|
122
|
+
* **link:docs/pagination-guide.adoc[Pagination guide]** - Working with
|
123
|
+
paginated APIs and large datasets
|
999
124
|
|
1000
|
-
|
125
|
+
* **link:docs/complex-path-patterns.adoc[Complex path patterns]** - Advanced
|
126
|
+
URL patterns and path matching examples
|
1001
127
|
|
1002
|
-
|
1003
|
-
|
128
|
+
* **link:docs/embedded-resources.adoc[Embedded resources]** - Implementing and
|
129
|
+
using embedded resources in HAL APIs
|
1004
130
|
|
1005
|
-
`#next_page`:: Returns the next page link if available, `nil` otherwise.
|
1006
131
|
|
1007
|
-
|
132
|
+
=== Architecture overview
|
1008
133
|
|
1009
|
-
|
134
|
+
The library is organized into these main components:
|
1010
135
|
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
136
|
+
`Lutaml::Hal::Client`::
|
137
|
+
HTTP client for making requests to HAL APIs. Supports GET requests with
|
138
|
+
automatic response handling.
|
1014
139
|
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
if current_page.next_page
|
1019
|
-
next_page = current_page.next_page.realize
|
1020
|
-
end
|
140
|
+
`Lutaml::Hal::ModelRegister`::
|
141
|
+
Registry for managing HAL resource models and their API endpoints. Handles URL
|
142
|
+
resolution and resource fetching.
|
1021
143
|
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
end
|
144
|
+
`Lutaml::Hal::GlobalRegister`::
|
145
|
+
Optional singleton for managing multiple ModelRegisters and enabling automatic
|
146
|
+
link resolution.
|
1026
147
|
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
----
|
148
|
+
`Lutaml::Hal::Resource`::
|
149
|
+
Base class for defining HAL resource models with attributes, links, and
|
150
|
+
serialization mappings.
|
1031
151
|
|
1032
|
-
|
152
|
+
`Lutaml::Hal::Link`::
|
153
|
+
Represents HAL links with automatic realization capabilities for fetching
|
154
|
+
target resources.
|
1033
155
|
|
1034
|
-
|
1035
|
-
navigation
|
156
|
+
`Lutaml::Hal::Page`::
|
157
|
+
Specialized resource class for handling pagination with navigation methods and
|
158
|
+
helper functions.
|
1036
159
|
|
1037
|
-
|
1038
|
-
otherwise.
|
160
|
+
== Usage workflow
|
1039
161
|
|
1040
|
-
|
1041
|
-
otherwise.
|
162
|
+
The `lutaml-hal` workflow follows a two-phase approach:
|
1042
163
|
|
1043
|
-
|
1044
|
-
otherwise.
|
164
|
+
=== 1. Data definition phase
|
1045
165
|
|
1046
|
-
|
1047
|
-
|
166
|
+
. **Define resource models**: Create classes inheriting from
|
167
|
+
`Lutaml::Hal::Resource`
|
168
|
+
. **Set up client**: Create a `Client` instance pointing to your API
|
169
|
+
. **Create register**: Set up a `ModelRegister` to manage your models
|
170
|
+
. **Register endpoints**: Map your models to specific API URLs
|
1048
171
|
|
1049
|
-
|
1050
|
-
attribute).
|
172
|
+
=== 2. Runtime phase
|
1051
173
|
|
174
|
+
. **Fetch resources**: Use `register.fetch()` to get data from the API
|
175
|
+
. **Access attributes**: Work with resource data as normal Ruby objects
|
176
|
+
. **Navigate links**: Use HAL links to move between related resources
|
177
|
+
. **Realize links**: Convert links to actual resource instances
|
1052
178
|
|
1053
|
-
|
179
|
+
== Path matching specification
|
1054
180
|
|
1055
|
-
|
1056
|
-
|
181
|
+
The library supports sophisticated URL pattern matching for endpoint
|
182
|
+
registration. Patterns use curly braces `{}` for parameter interpolation:
|
1057
183
|
|
1058
184
|
[source,ruby]
|
1059
185
|
----
|
1060
|
-
|
186
|
+
# Simple patterns
|
187
|
+
'/products/{id}'
|
188
|
+
'/users/{user_id}/orders/{order_id}'
|
1061
189
|
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
190
|
+
# With query parameters
|
191
|
+
register.add_endpoint(
|
192
|
+
id: :search_products,
|
193
|
+
type: :index,
|
194
|
+
url: '/products',
|
195
|
+
model: ProductIndex,
|
196
|
+
parameters: [
|
197
|
+
Lutaml::Hal::EndpointParameter.query('category',
|
198
|
+
schema: { type: :string },
|
199
|
+
description: 'Product category filter'
|
200
|
+
),
|
201
|
+
Lutaml::Hal::EndpointParameter.query('page',
|
202
|
+
schema: { type: :integer },
|
203
|
+
description: 'Page number'
|
204
|
+
),
|
205
|
+
Lutaml::Hal::EndpointParameter.query('limit',
|
206
|
+
schema: { type: :integer },
|
207
|
+
description: 'Results per page'
|
208
|
+
)
|
209
|
+
]
|
210
|
+
)
|
1069
211
|
----
|
1070
212
|
|
213
|
+
For complex path pattern examples, see
|
214
|
+
link:docs/complex-path-patterns.adoc[Complex Path Patterns].
|
1071
215
|
|
1072
|
-
|
216
|
+
== Error handling
|
1073
217
|
|
1074
|
-
|
1075
|
-
[example]
|
1076
|
-
====
|
1077
|
-
Declaration:
|
218
|
+
The library provides structured error handling:
|
1078
219
|
|
1079
220
|
[source,ruby]
|
1080
221
|
----
|
1081
|
-
|
1082
|
-
|
222
|
+
begin
|
223
|
+
product = register.fetch(:product_resource, id: '123')
|
224
|
+
rescue Lutaml::Hal::Errors::NotFoundError => e
|
225
|
+
puts "Product not found: #{e.message}"
|
226
|
+
rescue Lutaml::Hal::Errors::ApiError => e
|
227
|
+
puts "API Error: #{e.message}"
|
1083
228
|
end
|
1084
|
-
|
1085
|
-
register.add_endpoint(
|
1086
|
-
id: :resource_index,
|
1087
|
-
type: :index,
|
1088
|
-
url: '/resources',
|
1089
|
-
model: ResourceIndex
|
1090
|
-
)
|
1091
|
-
----
|
1092
|
-
|
1093
|
-
Usage:
|
1094
|
-
|
1095
|
-
[source,ruby]
|
1096
229
|
----
|
1097
|
-
page_1 = register.fetch(:resource_index)
|
1098
|
-
# => client.get('/resources')
|
1099
|
-
# => {
|
1100
|
-
# "page": 1,
|
1101
|
-
# "pages": 10,
|
1102
|
-
# "limit": 10,
|
1103
|
-
# "total": 100,
|
1104
|
-
# "_links": {
|
1105
|
-
# "self": {
|
1106
|
-
# "href": "https://api.example.com/resources?page=1&items=10"
|
1107
|
-
# },
|
1108
|
-
# "first": {
|
1109
|
-
# "href": "https://api.example.com/resources?page=1&items=10"
|
1110
|
-
# },
|
1111
|
-
# "last": {
|
1112
|
-
# "href": "https://api.example.com/resources?page=10&items=10"
|
1113
|
-
# },
|
1114
|
-
# "next": {
|
1115
|
-
# "href": "https://api.example.com/resources?page=2&items=10"
|
1116
|
-
# }
|
1117
|
-
# }
|
1118
|
-
# }
|
1119
|
-
|
1120
|
-
page_1
|
1121
|
-
# => #<ResourceIndex page: 1, pages: 10, limit: 10, total: 100,
|
1122
|
-
# links: #<ResourceIndexLinks
|
1123
|
-
# self: #<ResourceIndexLink href: "/resources?page=1&items=10">,
|
1124
|
-
# next: #<ResourceIndexLink href: "/resources?page=2&items=10">,
|
1125
|
-
# last: #<ResourceIndexLink href: "/resources?page=10&items=10">>>
|
1126
|
-
|
1127
|
-
# Check if navigation is available
|
1128
|
-
page_1.has_next? # => true
|
1129
|
-
page_1.has_prev? # => false
|
1130
|
-
page_1.total_pages # => 10
|
1131
|
-
|
1132
|
-
# Navigate using convenience methods
|
1133
|
-
page_2 = page_1.next
|
1134
|
-
# => client.get('/resources?page=2&items=10')
|
1135
|
-
# => #<ResourceIndex page: 2, pages: 10, limit: 10, total: 100, ...>
|
1136
|
-
|
1137
|
-
page_2.has_prev? # => true
|
1138
|
-
page_2.has_next? # => true
|
1139
230
|
|
1140
|
-
|
1141
|
-
first_page = page_2.first
|
1142
|
-
# => client.get('/resources?page=1&items=10')
|
1143
|
-
|
1144
|
-
# Jump to last page
|
1145
|
-
last_page = page_2.last
|
1146
|
-
# => client.get('/resources?page=10&items=10')
|
1147
|
-
|
1148
|
-
# Alternative: using link realization (original method)
|
1149
|
-
# Without a GlobalRegister
|
1150
|
-
page_2 = page_1.links.next.realize(register)
|
1151
|
-
|
1152
|
-
# With a GlobalRegister
|
1153
|
-
page_2 = page_1.links.next.realize
|
1154
|
-
|
1155
|
-
# => client.get('/resources?page=2&items=10')
|
1156
|
-
# => #<ResourceIndex page: 2, pages: 10, limit: 10, total: 100,
|
1157
|
-
# links: #<ResourceIndexLinks
|
1158
|
-
# self: #<ResourceIndexLink href: "/resources?page=2&items=10">,
|
1159
|
-
# prev: #<ResourceIndexLink href: "/resources?page=1&items=10">,
|
1160
|
-
# next: #<ResourceIndexLink href: "/resources?page=3&items=10">,
|
1161
|
-
# first: #<ResourceIndexLink href: "/resources?page=1&items=10">,
|
1162
|
-
# last: #<ResourceIndexLink href: "/resources?page=10&items=10">>>,
|
1163
|
-
# prev: #<ResourceIndexLink href: "/resources?page=1&items=10">>>
|
1164
|
-
----
|
1165
|
-
====
|
231
|
+
== Contributing
|
1166
232
|
|
233
|
+
Bug reports and pull requests are welcome on GitHub at
|
234
|
+
https://github.com/lutaml/lutaml-hal.
|
1167
235
|
|
1168
236
|
== License and Copyright
|
1169
237
|
|