Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
49 changes: 27 additions & 22 deletions lib/ndr_lookup/fhir/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ 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
class InvalidURIError < ApiError; end
class UnauthorizedError < ApiError; end

class << self
attr_writer :additional_headers
Expand All @@ -24,17 +27,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 JSON::ParserError
raise InvalidResponse, 'Invalid JSON response from server'
rescue StandardError => e
raise ApiError, "Unexpected error: #{e.message}"
with_error_handling("#{resource_type} with ID '#{id}' not found") do
Comment thread
Rikki-Dev marked this conversation as resolved.
response = connection.get("#{endpoint}/#{resource_type}/#{id}", headers)
JSON.parse(response.body)
end
end

def endpoint
Expand All @@ -51,24 +47,33 @@ 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
# 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
raise ResourceNotFound, not_found_message || 'Resource 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, "Search failed: #{e.message}"
raise ApiError, "Unexpected error: #{e.message}"
end

private

def construct_url(endpoint, resource_type, params = {})
url = "#{endpoint}/#{resource_type}"

Expand Down
2 changes: 1 addition & 1 deletion lib/ndr_lookup/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module NdrLookup
VERSION = '0.2.0'.freeze
VERSION = '0.3.0'.freeze
end