From 9d2f771dfadb1c82a2392ff2b54293c2b8339a58 Mon Sep 17 00:00:00 2001 From: Rikki Date: Wed, 25 Feb 2026 17:08:50 +0000 Subject: [PATCH 1/4] Add specific error classes for URI and authentication failures Adds InvalidURIError and UnauthorizedError to allow consumers to distinguish between data issues (malformed IDs) and systemic issues (auth failures). Previously these were wrapped as generic ApiError, preventing granular error handling. --- lib/ndr_lookup/fhir/base.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/ndr_lookup/fhir/base.rb b/lib/ndr_lookup/fhir/base.rb index 2c8f0a8..7b4b0c1 100644 --- a/lib/ndr_lookup/fhir/base.rb +++ b/lib/ndr_lookup/fhir/base.rb @@ -9,6 +9,8 @@ class Base < ActiveResource::Base class ApiError < StandardError; end class ResourceNotFound < ApiError; end class InvalidResponse < ApiError; end + class InvalidURIError < ApiError; end + class UnauthorizedError < ApiError; end class << self attr_writer :additional_headers @@ -31,8 +33,12 @@ def find(resource_type, id) JSON.parse(response.body) rescue ActiveResource::ResourceNotFound raise ResourceNotFound, "#{resource_type} with ID '#{id}' not found" + rescue ActiveResource::UnauthorizedAccess + raise UnauthorizedError, 'Authentication failed' rescue JSON::ParserError raise InvalidResponse, 'Invalid JSON response from server' + rescue URI::InvalidURIError => e + raise InvalidURIError, "Invalid ID format: #{e.message}" rescue StandardError => e raise ApiError, "Unexpected error: #{e.message}" end @@ -61,8 +67,12 @@ def search(resource_type, params = {}) raise_unless_response_success(response, payload) payload + rescue ActiveResource::UnauthorizedAccess + raise UnauthorizedError, 'Authentication failed' rescue JSON::ParserError raise InvalidResponse, 'Invalid JSON response from server' + rescue URI::InvalidURIError => e + raise InvalidURIError, "Invalid ID format: #{e.message}" rescue StandardError => e raise ApiError, "Search failed: #{e.message}" end From 8c54d81e960c52a36ddd9903a8e1b0493406c581 Mon Sep 17 00:00:00 2001 From: Rikki Date: Wed, 25 Feb 2026 17:12:13 +0000 Subject: [PATCH 2/4] Extract common error handling into with_error_handling method DRYs up the duplicate rescue blocks in find and search methods. --- lib/ndr_lookup/fhir/base.rb | 44 +++++++++++++++---------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/lib/ndr_lookup/fhir/base.rb b/lib/ndr_lookup/fhir/base.rb index 7b4b0c1..f5f6a9a 100644 --- a/lib/ndr_lookup/fhir/base.rb +++ b/lib/ndr_lookup/fhir/base.rb @@ -26,21 +26,10 @@ def sync # Finds a specific FHIR resource by type and ID # @return [Hash] Parsed FHIR resource def find(resource_type, id) - response = connection.get( - "#{endpoint}/#{resource_type}/#{id}", - headers - ) - JSON.parse(response.body) - rescue ActiveResource::ResourceNotFound - raise ResourceNotFound, "#{resource_type} with ID '#{id}' not found" - rescue ActiveResource::UnauthorizedAccess - raise UnauthorizedError, 'Authentication failed' - rescue JSON::ParserError - raise InvalidResponse, 'Invalid JSON response from server' - rescue URI::InvalidURIError => e - raise InvalidURIError, "Invalid ID format: #{e.message}" - rescue StandardError => e - raise ApiError, "Unexpected error: #{e.message}" + with_error_handling("#{resource_type} with ID '#{id}' not found") do + response = connection.get("#{endpoint}/#{resource_type}/#{id}", headers) + JSON.parse(response.body) + end end def endpoint @@ -57,16 +46,21 @@ def endpoint # @example Search for relationships # Client.search('OrganizationAffiliation', organization: 'RHAGX') def search(resource_type, params = {}) - url = construct_url(endpoint, resource_type, params) - - # Make the request - response = connection.get(url, headers) + with_error_handling do + url = construct_url(endpoint, resource_type, params) + response = connection.get(url, headers) + payload = JSON.parse(response.body) + raise_unless_response_success(response, payload) + payload + end + end - # Process response - payload = JSON.parse(response.body) - raise_unless_response_success(response, payload) + private - payload + def with_error_handling(not_found_message = nil) + yield + rescue ActiveResource::ResourceNotFound + raise ResourceNotFound, not_found_message || 'Resource not found' rescue ActiveResource::UnauthorizedAccess raise UnauthorizedError, 'Authentication failed' rescue JSON::ParserError @@ -74,11 +68,9 @@ def search(resource_type, params = {}) rescue URI::InvalidURIError => e raise InvalidURIError, "Invalid ID format: #{e.message}" rescue StandardError => e - raise ApiError, "Search failed: #{e.message}" + raise ApiError, "Unexpected error: #{e.message}" end - private - def construct_url(endpoint, resource_type, params = {}) url = "#{endpoint}/#{resource_type}" From 47cdb20324fdde090da44394e8cfec6cd7a3e8aa Mon Sep 17 00:00:00 2001 From: Rikki Date: Thu, 26 Feb 2026 10:11:44 +0000 Subject: [PATCH 3/4] Add comments explaining error class and handler purpose --- lib/ndr_lookup/fhir/base.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/ndr_lookup/fhir/base.rb b/lib/ndr_lookup/fhir/base.rb index f5f6a9a..f51d2ba 100644 --- a/lib/ndr_lookup/fhir/base.rb +++ b/lib/ndr_lookup/fhir/base.rb @@ -6,6 +6,7 @@ module NdrLookup module Fhir # Client for interacting with the NHS Digital FHIR API class Base < ActiveResource::Base + # Specific error classes for different API failure modes class ApiError < StandardError; end class ResourceNotFound < ApiError; end class InvalidResponse < ApiError; end @@ -57,6 +58,8 @@ def search(resource_type, params = {}) private + # Wraps API calls with consistent error handling, converting external exceptions + # (ActiveResource, JSON, URI) into our own error classes. def with_error_handling(not_found_message = nil) yield rescue ActiveResource::ResourceNotFound From c7900905305ab11cf3ed1c2e391949f09bb4c87d Mon Sep 17 00:00:00 2001 From: Rikki Date: Thu, 26 Feb 2026 10:28:34 +0000 Subject: [PATCH 4/4] Updated version and changelog. --- CHANGELOG.md | 6 ++++++ lib/ndr_lookup/version.rb | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 969242e..e463c6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ ## [Unreleased] + +## 0.3.0 / 2026-02-26 +### Added +* Added specific error classes (InvalidURIError, UnauthorizedError) for granular error handling +* Extracted common error handling into `with_error_handling` method + ### Fixed * Support Ruby 3.4. Drop support for Rails 7.0, Ruby 3.1 diff --git a/lib/ndr_lookup/version.rb b/lib/ndr_lookup/version.rb index 362749b..8493785 100644 --- a/lib/ndr_lookup/version.rb +++ b/lib/ndr_lookup/version.rb @@ -1,3 +1,3 @@ module NdrLookup - VERSION = '0.2.0'.freeze + VERSION = '0.3.0'.freeze end