GitHub Stars GitHub Forks License Build Status RubyGems Version

Purpose

The lutaml-hal gem provides a framework for interacting with HAL-compliant APIs using the power of LutaML Models.

Hypertext Application Language (HAL) (HAL Internet-Draft) is a simple format for representing resources and their relationships in a hypermedia-driven API.

It allows clients to navigate and interact with resources using links, making it easier to build flexible and extensible applications.

This library provides a set of classes and methods for modeling HAL resources, links, and collections, as well as a client for making HTTP requests to HAL APIs.

Features

  • Classes for modeling HAL resources and links

  • A client for making HTTP requests to HAL APIs

  • Tools for pagination and resource resolution

  • Integration with the lutaml-model serialization framework

  • Error handling and response validation for API interactions

Installation

Add this line to your application’s Gemfile:

gem 'lutaml-hal'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install lutaml-hal

Structure

The classes in this library are organized into the following modules:

Lutaml::Hal::Client

A client for making HTTP requests to HAL APIs. It includes methods for setting the API endpoint, making GET requests, and handling responses.

Note
Only GET requests are supported at the moment.
Lutaml::Hal::ModelRegister

A registry for managing HAL resource models and their endpoints. It allows you to register models, define their relationships, and fetch resources from the API.

Lutaml::Hal::Resource

A base class for defining HAL resource models. It includes methods for defining attributes, links, and key-value mappings for resources.

Lutaml::Hal::Link

A class for defining HAL links. It includes methods for specifying the relationship between resources and their links, as well as methods for resolving links to their target resources.

Lutaml::Hal::Page

A class for handling pagination in HAL APIs. It includes methods for defining pagination attributes, such as page, pages, limit, and total, as well as methods for accessing linked resources within a page.

Usage

General

In order to interact with a HAL API, the following steps are required:

  1. Create a Client that points to the API endpoint.

  2. Create a ModelRegister to manage the resource models and their respective endpoints.

  3. Define the resource models using the Resource class.

  4. Register the models with the ModelRegister.

  5. Fetch resources from the API using the ModelRegister.

    1. Once the resources are fetched, you can access their attributes and links and navigate through the resource graph.

  6. Pagination, such as on "index" type pages, can be handled by subclassing the Page class. The Page class itself is also implemented as a Resource, so you can use the same methods to access the page’s attributes and links.

Creating a HAL model register

require 'lutaml-hal'

# Create a new client with API endpoint
client = Lutaml::Hal::Client.new(api_url: 'https://api.example.com')
register = Lutaml::Hal::ModelRegister.new(client: client)
# Or set client later, `register.client = client`

register.add_endpoint(
  id: :product_index,
  type: :index,
  url: '/products',
  model: Product
)
register.add_endpoint(
  id: :product_resource,
  type: :resource,
  url: '/products/{id}',
  model: Product
)

register.fetch(:product_index)
# => client.get('/products')

# => {
# "page": 1,
# "pages": 10,
# "limit": 10,
# "total": 45,
# "_links": {
#   "self": { "href": "/products/1" },
#   "next": { "href": "/products/2" },
#   "last": { "href": "/products/5" },
#   "products": [
#     { "id": 1, "name": "Product 1", "price": 10.0 },
#     { "id": 2, "name": "Product 2", "price": 15.0 }
#   ]
# }

product_1 = register.fetch(:product_resource, id: 1)
# => client.get('/products/1')

# => {
# "id": 1,
# "name": "Product 1",
# "price": 10.0,
# "_links": {
#   "self": { "href": "/products/1" },
#   "category": { "href": "/categories/1", "title": "Category 1" },
#   "related": [
#      { "href": "/products/3", "title": "Product 3" },
#      { "href": "/products/5", "title": "Product 5" }
#   ]
# }
# }

product_1
# => #<Product id: 1, name: "Product 1", price: 10.0, links:
#      #<ProductLinks self: <ProductLink href: "/products/1">,
#                     category: <ProductLink href: "/categories/1", title: "Category 1">,
#                     related: [
#                         <ProductLink href: "/products/3", title: "Product 3">,
#                         <ProductLink href: "/products/5", title: "Product 5">
#                     ]}>

Defining resource models

module MyApi
  class Product < Lutaml::Hal::Resource
    attribute :id, :string
    attribute :name, :string
    attribute :price, :float

    hal_link :self, key: 'self', realize_class: 'Product'
    hal_link :category, key: 'category', realize_class: 'Category'

    key_value do
      map 'id', to: :id
      map 'name', to: :name
      map 'price', to: :price
    end
  end

  # Register the model with the registry
  Lutaml::Hal::ModelRegister.register(Product, '/products/{id}')
end

Registering endpoints

Fetching Resources

# Assume that the client is already created and registered at
# the ModelRegister
# Get a resource
product = client.get('products/123')
product_resource = MyApi::Product.from_json(product.to_json)

# Follow a link
category = product_resource.category.realize(register)

Working with Collections

class ProductPage < Lutaml::Hal::Page
  # Define the relationship between page and items
end

response = client.get('/products')
products = ProductPage.from_json(response.to_json)

# Access pagination info
puts "Page #{products.page} of #{products.pages}, total: #{products.total}"

# Access linked items
products.links.products.each do |product|
  puts "#{product.name}: $#{product.price}"
end

This project is licensed under the BSD 2-clause License. See the LICENSE.md file for details.

Copyright Ribose.