Skip to content

hackico-ai/ruby-hati-jsonapi-error

Repository files navigation

Hati JSON:API Error

Gem Version Ruby License: MIT Test Coverage

Production-ready JSON:API-compliant error responses for professional Web APIs

Transform inconsistent error handling into standardized, traceable responses. Built for Ruby applications requiring enterprise-grade error management.

Table of Contents

Why Standardized Error Handling Matters

The Problem: Inconsistent Error Responses

Different controllers returning different error formats creates maintenance nightmares:

# Three different error formats in one application
class UsersController
  def show
    render json: { error: "User not found" }, status: 404
  end
end

class OrdersController
  def create
    render json: { message: "Validation failed", details: errors }, status: 422
  end
end

class PaymentsController
  def process
    render json: { errors: errors, error_code: "INVALID", status: "failure" }, status: 400
  end
end

This forces frontend developers to handle multiple error formats:

// Unmaintainable error handling
if (data.error) {
  showError(data.error); // Users format
} else if (data.message && data.details) {
  showError(`${data.message}: ${data.details.join(", ")}`); // Orders format
} else if (data.errors && data.error_code) {
  showError(`${data.error_code}: ${data.errors.join(", ")}`); // Payments format
}

The Impact

  • API Documentation: Each endpoint needs custom error documentation
  • Error Tracking: Different structures break centralized logging
  • Client SDKs: Cannot provide consistent error handling
  • Testing: Each format requires separate test cases
  • Team Coordination: New developers must learn multiple patterns

The Solution: JSON:API Standard

One format across all endpoints:

raise HatiJsonapiError::UnprocessableEntity.new(
  detail: "Email address is required",
  source: { pointer: "/data/attributes/email" }
)

Always produces standardized output:

{
  "errors": [
    {
      "status": 422,
      "code": "unprocessable_entity",
      "title": "Validation Failed",
      "detail": "Email address is required",
      "source": { "pointer": "/data/attributes/email" }
    }
  ]
}

✨ Features

  • JSON:API Compliant - Follows the official JSON:API error specification
  • Auto-Generated Error Classes - Dynamic HTTP status code error classes (400-511)
  • Rich Error Context - Support for id, code, title, detail, status, meta, links, source
  • Error Registry - Map custom exceptions to standardized responses
  • Controller Integration - Helper methods for Rails, Sinatra, and other frameworks
  • 100% Test Coverage - Comprehensive RSpec test suite
  • Zero Dependencies - Lightweight and fast
  • Production Ready - Thread-safe and memory efficient

Installation

# Gemfile
gem 'hati-jsonapi-error'
bundle install

Quick Start

1. Configuration

# config/initializers/hati_jsonapi_error.rb
HatiJsonapiError::Config.configure do |config|
  config.load_errors!

  config.map_errors = {
    ActiveRecord::RecordNotFound => :not_found,
    ActiveRecord::RecordInvalid   => :unprocessable_entity,
    ArgumentError                 => :bad_request
  }

  config.use_unexpected = HatiJsonapiError::InternalServerError
end

2. Basic Usage

# Simple error raising
raise HatiJsonapiError::NotFound.new
raise HatiJsonapiError::BadRequest.new
raise HatiJsonapiError::Unauthorized.new

Usage Examples

Basic Error Handling

Access errors multiple ways:

# By class name
raise HatiJsonapiError::NotFound.new

# By status code
api_err = HatiJsonapiError::Helpers::ApiErr
raise api_err[404]

# By error code
raise api_err[:not_found]

Rich Error Context

Add debugging information:

HatiJsonapiError::NotFound.new(
  id: 'user_lookup_failed',
  detail: 'User with email john@example.com was not found',
  source: { pointer: '/data/attributes/email' },
  meta: {
    searched_email: 'john@example.com',
    suggestion: 'Verify the email address is correct'
  }
)

Multiple Validation Errors

Collect and return multiple errors:

errors = []
errors << HatiJsonapiError::UnprocessableEntity.new(
  detail: "Email format is invalid",
  source: { pointer: '/data/attributes/email' }
)
errors << HatiJsonapiError::UnprocessableEntity.new(
  detail: "Password too short",
  source: { pointer: '/data/attributes/password' }
)

resolver = HatiJsonapiError::Resolver.new(errors)
render json: resolver.to_json, status: resolver.status

Controller Integration

class ApiController < ApplicationController
  include HatiJsonapiError::Helpers

  rescue_from StandardError, with: :handle_error

  def show
    # ActiveRecord::RecordNotFound automatically mapped to JSON:API NotFound
    user = User.find(params[:id])
    render json: user
  end

  def create
    user = User.new(user_params)

    unless user.save
      validation_error = HatiJsonapiError::UnprocessableEntity.new(
        detail: user.errors.full_messages.join(', '),
        source: { pointer: '/data/attributes' },
        meta: { validation_errors: user.errors.messages }
      )

      return render_error(validation_error)
    end

    render json: user, status: :created
  end
end

Custom Error Classes

Domain-specific errors:

class PaymentRequiredError < HatiJsonapiError::PaymentRequired
  def initialize(amount:, currency: 'USD')
    super(
      detail: "Payment of #{amount} #{currency} required",
      meta: {
        required_amount: amount,
        currency: currency,
        payment_methods: ['credit_card', 'paypal']
      },
      links: {
        payment_page: "https://app.com/billing/upgrade?amount=#{amount}"
      }
    )
  end
end

# Usage
raise PaymentRequiredError.new(amount: 29.99)

Functional Programming Integration

Perfect for functional programming patterns with hati-operation gem:

require 'hati_operation'

class Api::User::CreateOperation < Hati::Operation
  ApiErr = HatiJsonapiError::Helpers::ApiErr

  def call(params)
    user_params = step validate_params(params), err: ApiErr[422]
    user = step create_user(user_params),       err: ApiErr[409]
    profile = step create_profile(user),        err: ApiErr[503]

    Success(profile)
  end

  private

  def validate_params(params)
    return Failure('Invalid parameters') unless params[:name]
    Success(params)
  end
end

Configuration

Error Mapping

HatiJsonapiError::Config.configure do |config|
  config.map_errors = {
    # Rails exceptions
    ActiveRecord::RecordNotFound  => :not_found,
    ActiveRecord::RecordInvalid   => :unprocessable_entity,

    # Custom exceptions
    AuthenticationError           => :unauthorized,
    RateLimitError               => :too_many_requests,

    # Infrastructure exceptions
    Redis::TimeoutError          => :service_unavailable,
    Net::ReadTimeout             => :gateway_timeout
  }

  config.use_unexpected = HatiJsonapiError::InternalServerError
end

Available Error Classes

Quick Reference - Most Common:

Status Class Code
400 BadRequest bad_request
401 Unauthorized unauthorized
403 Forbidden forbidden
404 NotFound not_found
422 UnprocessableEntity unprocessable_entity
429 TooManyRequests too_many_requests
500 InternalServerError internal_server_error
502 BadGateway bad_gateway
503 ServiceUnavailable service_unavailable

Complete list of all 39 HTTP status codes →

Testing

RSpec Integration

# Shared examples for JSON:API compliance
RSpec.shared_examples 'JSON:API error response' do |expected_status, expected_code|
  it 'returns proper JSON:API error format' do
    json = JSON.parse(response.body)

    expect(response).to have_http_status(expected_status)
    expect(json['errors'].first['status']).to eq(expected_status)
    expect(json['errors'].first['code']).to eq(expected_code)
  end
end

# Usage in specs
describe 'GET #show' do
  context 'when user not found' do
    subject { get :show, params: { id: 'nonexistent' } }
    include_examples 'JSON:API error response', 404, 'not_found'
  end
end

Unit Testing

RSpec.describe HatiJsonapiError::NotFound do
  it 'has correct default attributes' do
    error = described_class.new

    expect(error.status).to eq(404)
    expect(error.code).to eq(:not_found)
    expect(error.to_h[:title]).to eq('Not Found')
  end
end

Benefits

For Development Teams:

  • Reduced development time with single error pattern
  • Easier onboarding for new developers
  • Better testing with standardized structure
  • Improved debugging with consistent error tracking

For Frontend/Mobile Teams:

  • One error parser for entire API
  • Rich error context for better user experience
  • Easier SDK development

For Operations:

  • Centralized monitoring and alerting
  • Consistent error analysis
  • Simplified documentation

Contributing

git clone https://github.com/hackico-ai/ruby-hati-jsonapi-error.git
cd ruby-hati-jsonapi-error
bundle install
bundle exec rspec

License

MIT License - see LICENSE file.


Professional error handling for professional APIs

About

Standardized JSON: API-compliant error responses made easy for your Web API

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages