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.
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
- == Structure
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
- The `GlobalRegister` class is a singleton that manages one or more
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
- # Create a new client with API endpoint
173
- client = Lutaml::Hal::Client.new(api_url: 'https://api.example.com')
174
- register = Lutaml::Hal::ModelRegister.new(name: :my_model_register, client: client)
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
- # Register the ModelRegister with the global register
177
- global_register = Lutaml::Hal::GlobalRegister.instance.register(:my_model_register, register)
72
+ hal_link :self, key: 'self', realize_class: 'Product'
73
+ hal_link :category, key: 'category', realize_class: 'Category'
178
74
 
179
- # Obtain the global register
180
- global_register.get(:my_model_register)
181
-
182
- # Delete a register mapping
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
- The `add_endpoint` method takes the following parameters:
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
- Where,
94
+ # Fetch and use resources
95
+ product = register.fetch(:product_resource, id: '123')
96
+ puts product.name
97
+ puts product.price
781
98
 
782
- `resource_endpoint_id`:: The ID of the endpoint registered in the
783
- `ModelRegister`.
784
- `parameters`:: A hash of parameters to be passed to the API. The parameters
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
- # With a GlobalRegister
955
- product_2 = product_index.links.products.last.realize
104
+ == Documentation
956
105
 
957
- # => client.get('/products/2')
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
- product_2
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
- # Without a GlobalRegister
982
- product_2_related_1 = product_2.links.related.first.realize(register)
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
- # With a GlobalRegister
985
- product_2_related_1 = product_2.links.related.first.realize
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
- ==== General
116
+ * **link:docs/runtime-usage-guide.adoc[Runtime usage guide]** - Patterns for
117
+ fetching resources, navigating links, and handling pagination
993
118
 
994
- The `Lutaml::Hal::Page` class is used to handle pagination in HAL APIs.
119
+ * **link:docs/hal-links-reference.adoc[HAL links reference]** - Advanced
120
+ configuration and customization of HAL links
995
121
 
996
- As described in <<defining_hal_page_models>>, subclassing the `Page` class
997
- provides pagination capabilities, including the management of links to navigate
998
- through pages of resources.
122
+ * **link:docs/pagination-guide.adoc[Pagination guide]** - Working with
123
+ paginated APIs and large datasets
999
124
 
1000
- ==== Pagination navigation methods
125
+ * **link:docs/complex-path-patterns.adoc[Complex path patterns]** - Advanced
126
+ URL patterns and path matching examples
1001
127
 
1002
- The `Page` class provides several convenience methods for navigating through
1003
- paginated results:
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
- `#prev_page`:: Returns the previous page link if available, `nil` otherwise.
132
+ === Architecture overview
1008
133
 
1009
- `#first_page`:: Returns the first page link if available, `nil` otherwise.
134
+ The library is organized into these main components:
1010
135
 
1011
- `#last_page`:: Returns the last page link if available, `nil` otherwise.
1012
-
1013
- These methods return `Link` objects that can be realized using the `realize` method:
136
+ `Lutaml::Hal::Client`::
137
+ HTTP client for making requests to HAL APIs. Supports GET requests with
138
+ automatic response handling.
1014
139
 
1015
- [source,ruby]
1016
- ----
1017
- # Navigate to next page
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
- # Navigate to previous page
1023
- if current_page.prev_page
1024
- prev_page = current_page.prev_page.realize
1025
- end
144
+ `Lutaml::Hal::GlobalRegister`::
145
+ Optional singleton for managing multiple ModelRegisters and enabling automatic
146
+ link resolution.
1026
147
 
1027
- # Jump to first or last page
1028
- first_page = current_page.first_page.realize if current_page.first_page
1029
- last_page = current_page.last_page.realize if current_page.last_page
1030
- ----
148
+ `Lutaml::Hal::Resource`::
149
+ Base class for defining HAL resource models with attributes, links, and
150
+ serialization mappings.
1031
151
 
1032
- ==== Pagination helper methods
152
+ `Lutaml::Hal::Link`::
153
+ Represents HAL links with automatic realization capabilities for fetching
154
+ target resources.
1033
155
 
1034
- The `Page` class also provides helper methods to check the availability of
1035
- navigation links:
156
+ `Lutaml::Hal::Page`::
157
+ Specialized resource class for handling pagination with navigation methods and
158
+ helper functions.
1036
159
 
1037
- `#has_next?`:: Returns `true` if there is a next page available, `false`
1038
- otherwise.
160
+ == Usage workflow
1039
161
 
1040
- `#has_prev?`:: Returns `true` if there is a previous page available, `false`
1041
- otherwise.
162
+ The `lutaml-hal` workflow follows a two-phase approach:
1042
163
 
1043
- `#has_first?`:: Returns `true` if there is a first page link available, `false`
1044
- otherwise.
164
+ === 1. Data definition phase
1045
165
 
1046
- `#has_last?`:: Returns `true` if there is a last page link available, `false`
1047
- otherwise.
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
- `#total_pages`:: Returns the total number of pages (alias for the `pages`
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
- ==== Exhaustive pagination
179
+ == Path matching specification
1054
180
 
1055
- For scenarios where you need to process all pages of results, you can combine
1056
- the pagination methods:
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
- current_page = register.fetch(:resource_index)
186
+ # Simple patterns
187
+ '/products/{id}'
188
+ '/users/{user_id}/orders/{order_id}'
1061
189
 
1062
- while current_page
1063
- # Process current page
1064
- puts "Processing page #{current_page.page} of #{current_page.total_pages}"
1065
-
1066
- # Move to next page
1067
- current_page = current_page.next
1068
- end
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
- ==== Usage
216
+ == Error handling
1073
217
 
1074
- .Usage example of the Page class
1075
- [example]
1076
- ====
1077
- Declaration:
218
+ The library provides structured error handling:
1078
219
 
1079
220
  [source,ruby]
1080
221
  ----
1081
- class ResourceIndex < Lutaml::Hal::Page
1082
- # No attribute definition necessary
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
- # Navigate back to first page
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