Skip to content
Closed
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ Gemfile.lock
Gemfile-test.lock
*.gem
pkg/

.claude/
1 change: 1 addition & 0 deletions packages/forest_admin_agent/lib/forest_admin_agent.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require_relative 'forest_admin_agent/version'
require_relative 'forest_admin_agent/http/Exceptions/business_error'
require 'zeitwerk'

loader = Zeitwerk::Loader.for_gem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def start(rendering_id)

def verify_code_and_generate_token(params)
unless params['state']
raise ForestAdminAgent::Error,
raise ForestAdminAgent::Http::Exceptions::BadRequestError,
ForestAdminAgent::Utils::ErrorMessages::INVALID_STATE_MISSING
end

Expand All @@ -40,14 +40,19 @@ def verify_code_and_generate_token(params)
def get_rendering_id_from_state(state)
state = JSON.parse(state.tr("'", '"').gsub('=>', ':'))
unless state.key? 'renderingId'
raise ForestAdminAgent::Error,
ForestAdminAgent::Utils::ErrorMessages::INVALID_STATE_RENDERING_ID
raise ForestAdminAgent::Http::Exceptions::BadRequestError.new(
ForestAdminAgent::Utils::ErrorMessages::INVALID_STATE_RENDERING_ID,
details: { state: state }
)
end

begin
Integer(state['renderingId'])
rescue ArgumentError
raise ForestAdminAgent::Error, ForestAdminAgent::Utils::ErrorMessages::INVALID_RENDERING_ID
raise ForestAdminAgent::Http::Exceptions::ValidationFailedError.new(
ForestAdminAgent::Utils::ErrorMessages::INVALID_RENDERING_ID,
details: { renderingId: state['renderingId'] }
)
end

state['renderingId'].to_i
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module Builder
class AgentFactory
include Singleton
include ForestAdminAgent::Utils::Schema
include ForestAdminAgent::Http::Exceptions
include ForestAdminDatasourceToolkit::Exceptions

TTL_SCHEMA = 7200
Expand Down Expand Up @@ -73,7 +74,10 @@ def send_schema(force: false)

if Facades::Container.cache(:is_production)
unless schema_path && File.exist?(schema_path)
raise ForestException, "Can't load #{schema_path}. Providing a schema is mandatory in production."
raise InternalServerError.new(
'Schema file not found in production',
details: { schema_path: schema_path }
)
end

schema = JSON.parse(File.read(schema_path), symbolize_names: true)
Expand Down Expand Up @@ -129,8 +133,12 @@ def do_server_want_schema(hash)
response = client.post('/forest/apimaps/hashcheck', { schemaFileHash: hash }.to_json)
body = JSON.parse(response.body)
body['sendSchema']
rescue JSON::ParserError
raise ForestException, "Invalid JSON response from ForestAdmin server #{response.body}"
rescue JSON::ParserError => e
raise InternalServerError.new(
'Invalid JSON response from ForestAdmin server',
details: { body: response.body },
cause: e
)
rescue Faraday::Error => e
client.handle_response_error(e)
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
module ForestAdminAgent
module Http
module Exceptions
# Parent class for all business errors
# This is the base class that all specific error types inherit from
class BusinessError < StandardError
attr_reader :details, :cause

def initialize(message = nil, details: {}, cause: nil)
super(message)
@details = details || {}
@cause = cause
end

# Returns the name of the error class
def name
self.class.name.split('::').last
end
end

# ====================
# Specific error types
# ====================

class BadRequestError < BusinessError
def initialize(message = 'Bad request', details: {})
super
end
end

class ForbiddenError < BusinessError
def initialize(message = 'Forbidden', details: {})
super
end
end

class NotFoundError < BusinessError
def initialize(message, details: {})
super
end
end

class ConflictError < BusinessError
def initialize(message = 'Conflict', details: {})
super
end
end

class UnprocessableError < BusinessError
def initialize(message = 'Unprocessable entity', details: {})
super
end
end

class TooManyRequestsError < BusinessError
attr_reader :retry_after

def initialize(message, retry_after, details: {})
super(message, details: details)
@retry_after = retry_after
end
end

class InternalServerError < BusinessError
def initialize(message = 'Internal server error', details: {}, cause: nil)
super
end
end

class BadGatewayError < BusinessError
def initialize(message = 'Bad gateway error', details: {}, cause: nil)
super
end
end

class ServiceUnavailableError < BusinessError
def initialize(message = 'Service unavailable error', details: {}, cause: nil)
super
end
end

# Specialized BadRequestError subclass
class ValidationFailedError < BadRequestError
def initialize(message = 'Validation failed', details: {})
super
end
end

# Legacy ValidationError - kept for backward compatibility with HttpException-based code
# This extends HttpException rather than BusinessError to maintain compatibility
# New code should use ValidationFailedError instead
class ValidationError < BusinessError
def initialize(message = 'Validation error', details: {})
super
end
end
end
end
end

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
require_relative 'business_error'

module ForestAdminAgent
module Http
module Exceptions
# HttpError wraps a BusinessError and adds HTTP-specific properties
class HttpError < StandardError
attr_reader :status, :user_message, :custom_headers, :meta, :cause, :name

def initialize(error, status, user_message = nil, _meta = nil, custom_headers_proc = nil)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with many parameters (count = 5): initialize [qlty:function-parameters]

super(error.message)

@name = error.class.name.split('::').last
@status = status
@user_message = error.message || user_message
@meta = error.respond_to?(:details) ? error.details : {}
@cause = error

@custom_headers = if custom_headers_proc.respond_to?(:call)
custom_headers_proc.call(error)
else
{}
end
end
end

# Factory class to generate HTTP error classes for specific status codes
class HttpErrorFactory
def self.create_for_business_error(status, default_message, options = {})
custom_headers_proc = options[:custom_headers]

Class.new(HttpError) do
define_method(:initialize) do |error, user_message = nil, meta = nil|
super(error, status, user_message || default_message, meta, custom_headers_proc)
end
end
end
end
end
end
end

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading