diff --git a/.gitignore b/.gitignore index 36e6fdeeb..8de9ce3a8 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ Gemfile.lock Gemfile-test.lock *.gem pkg/ + +.claude/ \ No newline at end of file diff --git a/packages/forest_admin_agent/lib/forest_admin_agent.rb b/packages/forest_admin_agent/lib/forest_admin_agent.rb index 160119525..be8270393 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent.rb @@ -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 diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/auth/auth_manager.rb b/packages/forest_admin_agent/lib/forest_admin_agent/auth/auth_manager.rb index 6e9259ff8..619850616 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/auth/auth_manager.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/auth/auth_manager.rb @@ -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 @@ -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 diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/builder/agent_factory.rb b/packages/forest_admin_agent/lib/forest_admin_agent/builder/agent_factory.rb index ca40f55e1..53fc01822 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/builder/agent_factory.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/builder/agent_factory.rb @@ -7,6 +7,7 @@ module Builder class AgentFactory include Singleton include ForestAdminAgent::Utils::Schema + include ForestAdminAgent::Http::Exceptions include ForestAdminDatasourceToolkit::Exceptions TTL_SCHEMA = 7200 @@ -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) @@ -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 diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/business_error.rb b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/business_error.rb new file mode 100644 index 000000000..9f6da4098 --- /dev/null +++ b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/business_error.rb @@ -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 diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/conflict_error.rb b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/conflict_error.rb deleted file mode 100644 index 4b3335e88..000000000 --- a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/conflict_error.rb +++ /dev/null @@ -1,13 +0,0 @@ -module ForestAdminAgent - module Http - module Exceptions - class ConflictError < HttpException - attr_reader :name - - def initialize(message, name = 'ConflictError') - super(429, message, name) - end - end - end - end -end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/forbidden_error.rb b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/forbidden_error.rb deleted file mode 100644 index 16cedb473..000000000 --- a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/forbidden_error.rb +++ /dev/null @@ -1,13 +0,0 @@ -module ForestAdminAgent - module Http - module Exceptions - class ForbiddenError < HttpException - attr_reader :name - - def initialize(message, name = 'ForbiddenError') - super(403, message, name) - end - end - end - end -end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/http_error.rb b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/http_error.rb new file mode 100644 index 000000000..9a39826d7 --- /dev/null +++ b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/http_error.rb @@ -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) + 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 diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/not_found_error.rb b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/not_found_error.rb deleted file mode 100644 index da14759bb..000000000 --- a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/not_found_error.rb +++ /dev/null @@ -1,11 +0,0 @@ -module ForestAdminAgent - module Http - module Exceptions - class NotFoundError < HttpException - def initialize(message, name = 'NotFoundError') - super(404, message, name) - end - end - end - end -end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/require_approval.rb b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/require_approval.rb deleted file mode 100644 index c4a0c4ff6..000000000 --- a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/require_approval.rb +++ /dev/null @@ -1,14 +0,0 @@ -module ForestAdminAgent - module Http - module Exceptions - class RequireApproval < HttpException - attr_reader :name, :data - - def initialize(message, name = 'RequireApproval', data = []) - super(403, message, name) - @data = data - end - end - end - end -end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/unprocessable_error.rb b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/unprocessable_error.rb deleted file mode 100644 index 135b6ed37..000000000 --- a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/unprocessable_error.rb +++ /dev/null @@ -1,13 +0,0 @@ -module ForestAdminAgent - module Http - module Exceptions - class UnprocessableError < HttpException - attr_reader :name - - def initialize(message = '', name = 'UnprocessableError') - super(422, message, name) - end - end - end - end -end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/validation_error.rb b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/validation_error.rb deleted file mode 100644 index e9f0d7124..000000000 --- a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/validation_error.rb +++ /dev/null @@ -1,13 +0,0 @@ -module ForestAdminAgent - module Http - module Exceptions - class ValidationError < HttpException - attr_reader :name - - def initialize(message, name = 'ValidationError') - super(400, message, name) - end - end - end - end -end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/http/error_handling.rb b/packages/forest_admin_agent/lib/forest_admin_agent/http/error_handling.rb index 864771bbe..61ed94e5d 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/http/error_handling.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/http/error_handling.rb @@ -1,28 +1,99 @@ +require_relative 'error_translator' +require_relative 'Exceptions/business_error' +require_relative 'Exceptions/http_error' +require_relative 'Exceptions/http_exception' + module ForestAdminAgent module Http module ErrorHandling + # Get the appropriate error message for an error + # @param error [Exception] The error to get the message for + # @return [String] The error message to show to the user def get_error_message(error) + return error.user_message if error.is_a?(ForestAdminAgent::Http::Exceptions::HttpError) + if error.class.respond_to?(:ancestors) && error.class.ancestors.include?(ForestAdminAgent::Http::Exceptions::HttpException) return error.message end - if (customizer = ForestAdminAgent::Facades::Container.cache(:customize_error_message)) + return error.message if error.is_a?(ForestAdminAgent::Http::Exceptions::BusinessError) + + if defined?(ForestAdminAgent::Facades) && + (customizer = ForestAdminAgent::Facades::Container.cache(:customize_error_message)) message = eval(customizer).call(error) return message if message end - return error.message if error.is_a?(ForestAdminDatasourceToolkit::Exceptions::ValidationError) - 'Unexpected error' end + # Get the appropriate HTTP status code for an error + # @param error [Exception] The error to get the status for + # @return [Integer] The HTTP status code def get_error_status(error) - return error.status if error.respond_to?(:status) && error.status + return error.status if error.is_a?(ForestAdminAgent::Http::Exceptions::HttpError) - return 400 if error.is_a?(ForestAdminDatasourceToolkit::Exceptions::ValidationError) + return error.status if error.respond_to?(:status) && error.status 500 end + + # Get custom headers for an error + # @param error [Exception] The error to get headers for + # @return [Hash] Custom headers to include in the response + def get_error_headers(error) + return error.custom_headers if error.is_a?(ForestAdminAgent::Http::Exceptions::HttpError) + + {} + end + + # Get error metadata/details + # @param error [Exception] The error to get metadata for + # @return [Hash] Error metadata + def get_error_meta(error) + return error.meta if error.is_a?(ForestAdminAgent::Http::Exceptions::HttpError) + + return error.details if error.is_a?(ForestAdminAgent::Http::Exceptions::BusinessError) + + {} + end + + # Get the error name + # @param error [Exception] The error to get the name for + # @return [String] The error name + def get_error_name(error) + return error.name if error.is_a?(ForestAdminAgent::Http::Exceptions::HttpError) + + return error.name if error.is_a?(ForestAdminAgent::Http::Exceptions::BusinessError) + + return error.name if error.respond_to?(:name) && error.name + + error.class.name.split('::').last + end + + # Translate any exception to its appropriate HTTP error representation + # and return all error information needed for response + # @param error [Exception] The error to translate + # @return [Hash] Hash containing: + # - :translated_error - The translated error (HttpError, HttpException, or original) + # - :status - HTTP status code (Integer) + # - :message - Error message for the user (String) + # - :name - Error name (String) + # - :meta - Error metadata/details (Hash) + # - :headers - Custom headers to include in response (Hash) + def translate_error(error) + # Delegate translation logic to ErrorTranslator + translated = ForestAdminAgent::Http::ErrorTranslator.translate(error) + + { + translated_error: translated, + status: get_error_status(translated), + message: get_error_message(translated), + name: get_error_name(translated), + meta: get_error_meta(translated), + headers: get_error_headers(translated) + } + end end end end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/http/error_translator.rb b/packages/forest_admin_agent/lib/forest_admin_agent/http/error_translator.rb new file mode 100644 index 000000000..082ecb63c --- /dev/null +++ b/packages/forest_admin_agent/lib/forest_admin_agent/http/error_translator.rb @@ -0,0 +1,75 @@ +require_relative 'Exceptions/business_error' +require_relative 'Exceptions/http_error' + +module ForestAdminAgent + module Http + # Translates exceptions to their appropriate HTTP error representation + # This is the single source of truth for error translation in the agent + class ErrorTranslator + # Create specific HTTP error classes for each status code + ERROR_400 = Exceptions::HttpErrorFactory.create_for_business_error(400, 'Bad Request') + ERROR_403 = Exceptions::HttpErrorFactory.create_for_business_error(403, 'Forbidden') + ERROR_404 = Exceptions::HttpErrorFactory.create_for_business_error(404, 'Not Found', { + custom_headers: lambda { |_error| + { 'x-error-type' => 'object-not-found' } + } + }) + ERROR_409 = Exceptions::HttpErrorFactory.create_for_business_error(409, 'Conflict') + ERROR_422 = Exceptions::HttpErrorFactory.create_for_business_error(422, 'Unprocessable Entity') + ERROR_429 = Exceptions::HttpErrorFactory.create_for_business_error(429, 'Too Many Requests', { + custom_headers: lambda { |error| + { 'Retry-After' => error.retry_after.to_s } + } + }) + ERROR_500 = Exceptions::HttpErrorFactory.create_for_business_error(500, 'Internal Server Error') + ERROR_502 = Exceptions::HttpErrorFactory.create_for_business_error(502, 'Bad Gateway Error') + ERROR_503 = Exceptions::HttpErrorFactory.create_for_business_error(503, 'Service Unavailable Error') + + # Translate any exception to its appropriate HTTP error representation + # @param error [Exception] The error to translate + # @return [HttpError, HttpException, Exception] The translated error or the original error + # + # This method handles: + # 1. HttpError/HttpException → returned as-is (already have status info) + # 2. DatasourceToolkit ValidationError → Agent ValidationError (HttpException with status 400) + # 3. BusinessError → HttpError (with proper status, headers, metadata) + # 4. Unknown errors → returned as-is (will be handled as 500 by error handler) + def self.translate(error) # rubocop:disable Metrics/MethodLength + # Already an HttpError or HttpException - no translation needed + return error if error.is_a?(Exceptions::HttpError) + return error if error.respond_to?(:status) && error.status + + # Translate DatasourceToolkit ValidationError to Agent BadRequestError + if defined?(ForestAdminDatasourceToolkit::Exceptions::ValidationError) && + error.is_a?(ForestAdminDatasourceToolkit::Exceptions::ValidationError) + error = Exceptions::BadRequestError.new(error.message) + end + + # Translate BusinessError to HttpError with appropriate status code + case error + when Exceptions::BadRequestError, Exceptions::ValidationError + ERROR_400.new(error) + when Exceptions::ForbiddenError + ERROR_403.new(error) + when Exceptions::NotFoundError + ERROR_404.new(error) + when Exceptions::ConflictError + ERROR_409.new(error) + when Exceptions::UnprocessableError + ERROR_422.new(error) + when Exceptions::TooManyRequestsError + ERROR_429.new(error) + when Exceptions::InternalServerError + ERROR_500.new(error) + when Exceptions::BadGatewayError + ERROR_502.new(error) + when Exceptions::ServiceUnavailableError + ERROR_503.new(error) + else + # Unknown error type - return as-is, will be handled as 500 + error + end + end + end + end +end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/http/forest_admin_api_requester.rb b/packages/forest_admin_agent/lib/forest_admin_agent/http/forest_admin_api_requester.rb index bc79fba03..8691d4728 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/http/forest_admin_api_requester.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/http/forest_admin_api_requester.rb @@ -3,7 +3,7 @@ module ForestAdminAgent module Http class ForestAdminApiRequester - include ForestAdminDatasourceToolkit::Exceptions + include ForestAdminAgent::Http::Exceptions def initialize @headers = { @@ -28,29 +28,45 @@ def post(url, params = nil) end def handle_response_error(error) - raise error if error.is_a?(ForestException) + # Re-raise if it's already a BusinessError + raise error if error.is_a?(BusinessError) if error.response[:message]&.include?('certificate') - raise ForestException, - 'ForestAdmin server TLS certificate cannot be verified. Please check that your system time is set properly.' + raise InternalServerError.new( + 'ForestAdmin server TLS certificate cannot be verified. Please check that your system time is set properly.', + details: { error: error.message }, + cause: error + ) end if error.response[:status].zero? || error.response[:status] == 502 - raise ForestException, 'Failed to reach ForestAdmin server. Are you online?' + raise BadGatewayError.new( + 'Failed to reach ForestAdmin server. Are you online?', + details: { status: error.response[:status] }, + cause: error + ) end if error.response[:status] == 404 - raise ForestException, - 'ForestAdmin server failed to find the project related to the envSecret you configured. Can you check that you copied it properly in the Forest initialization?' + raise NotFoundError.new( + 'ForestAdmin server failed to find the project related to the envSecret you configured. Can you check that you copied it properly in the Forest initialization?', + details: { status: error.response[:status] } + ) end if error.response[:status] == 503 - raise ForestException, - 'Forest is in maintenance for a few minutes. We are upgrading your experience in the forest. We just need a few more minutes to get it right.' + raise ServiceUnavailableError.new( + 'Forest is in maintenance for a few minutes. We are upgrading your experience in the forest. We just need a few more minutes to get it right.', + details: { status: error.response[:status] }, + cause: error + ) end - raise ForestException, - 'An unexpected error occurred while contacting the ForestAdmin server. Please contact support@forestadmin.com for further investigations.' + raise InternalServerError.new( + 'An unexpected error occurred while contacting the ForestAdmin server. Please contact support@forestadmin.com for further investigations.', + details: { status: error.response[:status], message: error.message }, + cause: error + ) end end end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/routes/charts/charts.rb b/packages/forest_admin_agent/lib/forest_admin_agent/routes/charts/charts.rb index 2bb67988a..55226c77f 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/routes/charts/charts.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/routes/charts/charts.rb @@ -53,7 +53,7 @@ def handle_request(args = {}) def type=(type) chart_types = %w[Value Objective Pie Line Leaderboard] unless chart_types.include?(type) - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, "Invalid Chart type #{type}" + raise ForestAdminAgent::Http::Exceptions::BadRequestError, "Invalid Chart type #{type}" end @type = type.downcase @@ -197,7 +197,7 @@ def make_leaderboard return LeaderboardChart.new(result).serialize end - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, + raise ForestAdminAgent::Http::Exceptions::BadRequestError, 'Failed to generate leaderboard chart: parameters do not match pre-requisites' end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/native_query.rb b/packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/native_query.rb index 6534adf5e..ab2d3b60a 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/native_query.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/native_query.rb @@ -7,6 +7,7 @@ module Resources class NativeQuery < AbstractAuthenticatedRoute include ForestAdminAgent::Builder include ForestAdminAgent::Utils + include ForestAdminAgent::Http::Exceptions include ForestAdminDatasourceToolkit::Exceptions include ForestAdminDatasourceToolkit::Components::Charts include ForestAdminAgent::Routes::QueryHandler @@ -53,14 +54,14 @@ def handle_request(args = {}) def type=(type) chart_types = %w[Value Objective Pie Line Leaderboard] unless chart_types.include?(type) - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, "Invalid Chart type #{type}" + raise ForestAdminAgent::Http::Exceptions::BadRequestError, "Invalid Chart type #{type}" end @type = type.downcase end def raise_error(result, key_names) - raise ForestException, + raise BadRequestError, "The result columns must be named #{key_names} instead of '#{result.keys.join("', '")}'" end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/related/dissociate_related.rb b/packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/related/dissociate_related.rb index 659634abb..5f6737c99 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/related/dissociate_related.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/related/dissociate_related.rb @@ -90,7 +90,7 @@ def get_base_foreign_filter(args) selected_ids = selected_ids.inverse if selection_ids[:are_excluded] if selection_ids[:ids].empty? && !selection_ids[:are_excluded] - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Expected no empty id list' + raise ForestAdminAgent::Http::Exceptions::BadRequestError, 'Expected no empty id list' end Filter.new( diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb b/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb index ff19eb26c..62de26e6f 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/routes/security/authentication.rb @@ -80,14 +80,14 @@ def auth def get_and_check_rendering_id(params) unless params['renderingId'] - raise ForestAdminAgent::Error, + raise BadRequestError, ForestAdminAgent::Utils::ErrorMessages::MISSING_RENDERING_ID end begin Integer(params['renderingId']) rescue ArgumentError - raise ForestAdminAgent::Error, ForestAdminAgent::Utils::ErrorMessages::INVALID_RENDERING_ID + raise ValidationFailedError, ForestAdminAgent::Utils::ErrorMessages::INVALID_RENDERING_ID end params['renderingId'].to_i diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/services/ip_whitelist.rb b/packages/forest_admin_agent/lib/forest_admin_agent/services/ip_whitelist.rb index 96cd9e01d..828aab407 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/services/ip_whitelist.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/services/ip_whitelist.rb @@ -40,7 +40,7 @@ def ip_matches_rule?(ip, rule) when RULE_MATCH_SUBNET ip_match_subnet?(ip, rule['range']) else - raise 'Invalid rule type' + raise ForestAdminAgent::Http::Exceptions::InternalServerError, 'Invalid rule type' end end @@ -93,7 +93,8 @@ def fetch_rules status: response.status, response: response.body }) - raise ForestAdminAgent::Error, ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED + raise ForestAdminAgent::Http::Exceptions::InternalServerError, + ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED end begin @@ -104,7 +105,8 @@ def fetch_rules status: response.status, response: response.body }) - raise ForestAdminAgent::Error, ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED + raise ForestAdminAgent::Http::Exceptions::InternalServerError, + ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED end ip_whitelist_data = body['data']['attributes'] @@ -117,7 +119,8 @@ def fetch_rules status: response&.status, response: response&.body }) - raise ForestAdminAgent::Error, ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED + raise ForestAdminAgent::Http::Exceptions::InternalServerError, + ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED end end end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/services/permissions.rb b/packages/forest_admin_agent/lib/forest_admin_agent/services/permissions.rb index ffcfea9a3..fec8fa977 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/services/permissions.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/services/permissions.rb @@ -263,7 +263,7 @@ def find_action_from_endpoint(collection_name, path, http_method) action = actions.find { |a| a['endpoint'] == endpoint && a['httpMethod'].casecmp(http_method).zero? } - raise ForestException, "The collection #{collection_name} does not have this smart action" if action.nil? + raise BadRequestError, "The collection #{collection_name} does not have this smart action" if action.nil? action end @@ -277,7 +277,10 @@ def decode_crud_permissions(collection) "Available keys: #{collection.is_a?(Hash) ? collection.keys.join(", ") : "N/A (not a hash)"}. " \ 'This indicates an API contract violation or data corruption.' ) - raise ForestException, 'Invalid permission data structure received from Forest Admin API' + raise InternalServerError.new( + 'Invalid permission data structure received from Forest Admin API', + details: { received_keys: collection.is_a?(Hash) ? collection.keys : nil } + ) end collection_data = collection[:collection] @@ -288,7 +291,10 @@ def decode_crud_permissions(collection) "Invalid permissions data: :collection is not a hash (got #{collection_data.class}). " \ 'This indicates an API contract violation or data corruption.' ) - raise ForestException, 'Invalid permission data structure: :collection must be a hash' + raise InternalServerError.new( + 'Invalid permission data structure: :collection must be a hash', + details: { collection_data_class: collection_data.class } + ) end # Use dig to safely extract roles, allowing for missing permissions diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/services/smart_action_checker.rb b/packages/forest_admin_agent/lib/forest_admin_agent/services/smart_action_checker.rb index c9bc31595..a2f6386aa 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/services/smart_action_checker.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/services/smart_action_checker.rb @@ -1,7 +1,26 @@ module ForestAdminAgent module Services + include ForestAdminAgent::Http::Exceptions + + class CustomActionTriggerForbiddenError < ForbiddenError + def initialize(message = 'Custom action trigger forbidden', details: {}) + super + end + end + + class InvalidActionConditionError < ConflictError + def initialize(message = 'Invalid action condition', details: {}) + super + end + end + + class CustomActionRequiresApprovalError < ForbiddenError + def initialize(message = 'Custom action requires approval', details: {}) + super + end + end + class SmartActionChecker - include ForestAdminAgent::Http::Exceptions include ForestAdminAgent::Utils include ForestAdminDatasourceToolkit::Utils include ForestAdminDatasourceToolkit::Components::Query @@ -9,12 +28,6 @@ class SmartActionChecker attr_reader :parameters, :collection, :smart_action, :caller, :role_id, :filter, :attributes - TRIGGER_FORBIDDEN_ERROR = 'CustomActionTriggerForbiddenError'.freeze - - REQUIRE_APPROVAL_ERROR = 'CustomActionRequiresApprovalError'.freeze - - INVALID_ACTION_CONDITION_ERROR = 'InvalidActionConditionError'.freeze - def initialize(parameters, collection, smart_action, caller, role_id, filter) @parameters = parameters @collection = collection @@ -43,7 +56,7 @@ def can_approve? return true end - raise ForbiddenError.new('You don\'t have the permission to trigger this action.', TRIGGER_FORBIDDEN_ERROR) + raise CustomActionTriggerForbiddenError, 'You don\'t have the permission to trigger this action.' end def can_trigger? @@ -53,17 +66,16 @@ def can_trigger? end elsif smart_action[:approvalRequired].include?(role_id) && smart_action[:triggerEnabled].include?(role_id) if condition_by_role_id(smart_action[:approvalRequiredConditions]).nil? || match_conditions(:approvalRequiredConditions) - raise RequireApproval.new( + raise CustomActionRequiresApprovalError.new( 'This action requires to be approved.', - REQUIRE_APPROVAL_ERROR, - smart_action[:userApprovalEnabled] + details: { user_approval_enabled: smart_action[:userApprovalEnabled] } ) elsif condition_by_role_id(smart_action[:triggerConditions]).nil? || match_conditions(:triggerConditions) return true end end - raise ForbiddenError.new('You don\'t have the permission to trigger this action.', TRIGGER_FORBIDDEN_ERROR) + raise CustomActionTriggerForbiddenError, 'You don\'t have the permission to trigger this action.' end def match_conditions(condition_name) @@ -76,7 +88,7 @@ def match_conditions(condition_name) "Action: #{attributes[:smart_action_id]}" ) - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "Collection '#{collection.name}' has no primary keys. " \ 'Actions with conditional permissions require a primary key to identify records.' end @@ -100,21 +112,16 @@ def match_conditions(condition_name) rows = collection.aggregate(caller, conditional_filter, Aggregation.new(operation: 'Count')) (rows.empty? ? 0 : rows[0]['value']) == attributes[:ids].count - rescue ForestAdminDatasourceToolkit::Exceptions::ForestException => e + rescue ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::BusinessError => e # Let primary key validation errors propagate - these are actionable schema issues - # Wrap other ForestExceptions (like invalid operators) in ConflictError + # Wrap other exceptions (like invalid operators) in ConflictError raise if e.message.include?('has no primary keys') - raise ConflictError.new( - 'The conditions to trigger this action cannot be verified. Please contact an administrator.', - INVALID_ACTION_CONDITION_ERROR - ) + raise InvalidActionConditionError, 'The conditions to trigger this action cannot be verified. Please contact an administrator.' rescue ArgumentError, TypeError => e # Catch specific errors from condition parsing/validation - raise ConflictError.new( - "Invalid action condition: #{e.message}. Please contact an administrator.", - INVALID_ACTION_CONDITION_ERROR - ) + raise InvalidActionConditionError, "Invalid action condition: #{e.message}. Please contact an administrator." rescue StandardError => e # Catch unexpected errors and log for debugging ForestAdminAgent::Facades::Container.logger.log( @@ -122,10 +129,7 @@ def match_conditions(condition_name) "Unexpected error in match_conditions: #{e.class} - #{e.message}" ) - raise ConflictError.new( - 'The conditions to trigger this action cannot be verified. Please contact an administrator.', - INVALID_ACTION_CONDITION_ERROR - ) + raise InvalidActionConditionError, 'The conditions to trigger this action cannot be verified. Please contact an administrator.' end def condition_by_role_id(condition) diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/services/sse_cache_invalidation.rb b/packages/forest_admin_agent/lib/forest_admin_agent/services/sse_cache_invalidation.rb index 7cee8165f..d3e6bd3d9 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/services/sse_cache_invalidation.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/services/sse_cache_invalidation.rb @@ -3,6 +3,7 @@ module ForestAdminAgent module Services class SSECacheInvalidation + include ForestAdminAgent::Http::Exceptions include ForestAdminDatasourceToolkit::Exceptions MESSAGE_CACHE_KEYS = { @@ -38,7 +39,7 @@ def self.run ) end end - rescue StandardError + rescue StandardError => e ForestAdminAgent::Facades::Container.logger.log( 'Debug', 'SSE connection to forestadmin server' @@ -49,7 +50,11 @@ def self.run 'SSE connection to forestadmin server closed unexpectedly, retrying.' ) - raise ForestException, 'Failed to reach SSE data from ForestAdmin server.' + raise ServiceUnavailableError.new( + 'Failed to reach SSE data from ForestAdmin server', + details: { error: e.message }, + cause: e + ) end end end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/utils/caller_parser.rb b/packages/forest_admin_agent/lib/forest_admin_agent/utils/caller_parser.rb index 1946265b3..c1a13d144 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/utils/caller_parser.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/utils/caller_parser.rb @@ -5,6 +5,7 @@ module ForestAdminAgent module Utils class CallerParser + include ForestAdminAgent::Http::Exceptions include ForestAdminDatasourceToolkit::Exceptions def initialize(args) @@ -37,8 +38,10 @@ def validate_headers def extract_timezone timezone = @args[:params]['timezone'] - raise ForestException, 'Missing timezone' unless timezone - raise ForestException, "Invalid timezone: #{timezone}" unless Time.find_zone(timezone) + + raise BadRequestError, 'Missing timezone' unless timezone + + raise BadRequestError, "Invalid timezone: #{timezone}" unless Time.find_zone(timezone) timezone end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/utils/condition_tree_parser.rb b/packages/forest_admin_agent/lib/forest_admin_agent/utils/condition_tree_parser.rb index 5bf70fb03..88d08be74 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/utils/condition_tree_parser.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/utils/condition_tree_parser.rb @@ -4,7 +4,7 @@ module ForestAdminAgent module Utils class ConditionTreeParser - include ForestAdminDatasourceToolkit::Exceptions + include ForestAdminAgent::Http::Exceptions include ForestAdminDatasourceToolkit::Utils include ForestAdminDatasourceToolkit::Components::Query::ConditionTree include ForestAdminDatasourceToolkit::Components::Query::ConditionTree::Nodes @@ -26,7 +26,7 @@ def self.from_plain_object(collection, filters) return conditions.size == 1 ? conditions[0] : ConditionTreeBranch.new(aggregator, conditions) end - raise ForestException, 'Failed to instantiate condition tree' + raise BadRequestError, 'Failed to instantiate condition tree' end def self.parse_value(collection, leaf) diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/utils/id.rb b/packages/forest_admin_agent/lib/forest_admin_agent/utils/id.rb index 3237c4864..6ead38ceb 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/utils/id.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/utils/id.rb @@ -11,7 +11,9 @@ def self.pack_ids(schema, records) def self.pack_id(schema, record) pk_names = ForestAdminDatasourceToolkit::Utils::Schema.primary_keys(schema) - raise Exceptions::ForestException, 'This collection has no primary key' if pk_names.empty? + if pk_names.empty? + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, 'This collection has no primary key' + end pk_names.map { |pk| record[pk].to_s }.join('|') end @@ -20,7 +22,8 @@ def self.unpack_id(collection, packed_id, with_key: false) primary_keys = ForestAdminDatasourceToolkit::Utils::Schema.primary_keys(collection) primary_key_values = packed_id.to_s.split('|') if (nb_pks = primary_keys.size) != (nb_values = primary_key_values.size) - raise Exceptions::ForestException, "Expected #{nb_pks} primary keys, found #{nb_values}" + raise ForestAdminAgent::Http::Exceptions::BadRequestError, + "Expected #{nb_pks} primary keys, found #{nb_values}" end result = primary_keys.map.with_index do |pk_name, index| diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/utils/query_string_parser.rb b/packages/forest_admin_agent/lib/forest_admin_agent/utils/query_string_parser.rb index e883d9e93..5e6c1ba9d 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/utils/query_string_parser.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/utils/query_string_parser.rb @@ -1,6 +1,7 @@ module ForestAdminAgent module Utils class QueryStringParser + include ForestAdminAgent::Http::Exceptions include ForestAdminDatasourceToolkit::Exceptions include ForestAdminDatasourceToolkit::Components include ForestAdminDatasourceToolkit::Components::Query @@ -92,7 +93,7 @@ def self.parse_pagination(args) limit_valid = !items_per_pages.to_s.match(/\A[+]?\d+\z/).nil? && items_per_pages.to_i.positive? unless page_valid && limit_valid - raise ForestException, "Invalid pagination [limit: #{items_per_pages}, skip: #{page}]" + raise BadRequestError, "Invalid pagination [limit: #{items_per_pages}, skip: #{page}]" end offset = (page.to_i - 1) * items_per_pages.to_i @@ -107,7 +108,7 @@ def self.parse_export_pagination(limit) def self.parse_search(collection, args) search = args.dig(:params, :data, :attributes, :all_records_subset_query, :search) || args.dig(:params, :search) - raise ForestException, 'Collection is not searchable' if search && !collection.is_searchable? + raise BadRequestError, 'Collection is not searchable' if search && !collection.is_searchable? search end @@ -148,7 +149,7 @@ def self.parse_segment(collection, args) return unless segment - raise ForestException, "Invalid segment: #{segment}" unless collection.schema[:segments].include?(segment) + raise BadRequestError, "Invalid segment: #{segment}" unless collection.schema[:segments].include?(segment) segment end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/utils/query_validator.rb b/packages/forest_admin_agent/lib/forest_admin_agent/utils/query_validator.rb index 4eb4c1c47..7ef3b48fd 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/utils/query_validator.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/utils/query_validator.rb @@ -8,7 +8,7 @@ module QueryValidator def self.valid?(query) query = query.strip - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Query cannot be empty.' if query.empty? + raise Http::Exceptions::BadRequestError, 'Query cannot be empty.' if query.empty? sanitized_query = remove_content_inside_strings(query) check_select_only(sanitized_query) @@ -21,30 +21,31 @@ def self.valid?(query) end class << self - include ForestAdminDatasourceToolkit::Exceptions - private def check_select_only(query) return if query.strip.upcase.start_with?('SELECT') - raise ForestException, 'Only SELECT queries are allowed.' + raise Http::Exceptions::BadRequestError, 'Only SELECT queries are allowed.' end def check_semicolon_placement(query) semicolon_count = query.scan(';').size - raise ForestException, 'Only one query is allowed.' if semicolon_count > 1 + raise Http::Exceptions::BadRequestError, 'Only one query is allowed.' if semicolon_count > 1 return if semicolon_count != 1 || query.strip[-1] == ';' - raise ForestException, 'Semicolon must only appear as the last character in the query.' + raise Http::Exceptions::BadRequestError, 'Semicolon must only appear as the last character in the query.' end def check_forbidden_keywords(query) FORBIDDEN_KEYWORDS.each do |keyword| - if /\b#{Regexp.escape(keyword)}\b/i.match?(query) - raise ForestException, "The query contains forbidden keyword: #{keyword}." - end + next unless /\b#{Regexp.escape(keyword)}\b/i.match?(query) + + raise Http::Exceptions::BadRequestError.new( + "The query contains forbidden keyword: #{keyword}.", + details: { forbidden_keyword: keyword } + ) end end @@ -54,12 +55,17 @@ def check_unbalanced_parentheses(query) return if open_count == close_count - raise ForestException, 'The query contains unbalanced parentheses.' + raise Http::Exceptions::BadRequestError.new( + 'The query contains unbalanced parentheses.', + details: { open_count: open_count, close_count: close_count } + ) end def check_sql_injection_patterns(query) INJECTION_PATTERNS.each do |pattern| - raise ForestException, 'The query contains a potential SQL injection pattern.' if pattern.match?(query) + next unless pattern.match?(query) + + raise Http::Exceptions::BadRequestError, 'The query contains a potential SQL injection pattern.' end end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action_field_widget.rb b/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action_field_widget.rb index a54904558..cfb2a5d84 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action_field_widget.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action_field_widget.rb @@ -2,7 +2,7 @@ module ForestAdminAgent module Utils module Schema class GeneratorActionFieldWidget - include ForestAdminDatasourceToolkit::Exceptions + include ForestAdminAgent::Http::Exceptions def self.build_widget_options(field) return if !ActionFields.widget?(field) || %w[Collection Enum EnumList].include?(field.type) @@ -43,7 +43,10 @@ def self.build_widget_options(field) return build_address_autocomplete_widget_edit(field) if ActionFields.address_autocomplete_field?(field) - raise ForestException, "Unsupported widget type: #{field&.widget}" + raise InternalServerError.new( + "Unsupported widget type: #{field&.widget}", + details: { widget: field&.widget, field_type: field&.type } + ) end class << self diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/builder/agent_factory_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/builder/agent_factory_spec.rb index 3bd61c5de..8dfc7a9f5 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/builder/agent_factory_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/builder/agent_factory_spec.rb @@ -103,7 +103,7 @@ module Builder allow(Facades::Container).to receive(:cache).with(:is_production).and_return(true) allow(File).to receive(:exist?).with('/path/to/schema.json').and_return(false) - expect { instance.send_schema }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException) + expect { instance.send_schema }.to raise_error(ForestAdminAgent::Http::Exceptions::InternalServerError) end it 'loads schema from file in production mode' do diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/http/error_handling_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/http/error_handling_spec.rb index f4297fa2f..f29491187 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/http/error_handling_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/http/error_handling_spec.rb @@ -98,8 +98,9 @@ module Http allow(ForestAdminAgent::Facades::Container).to receive(:cache).with(:customize_error_message).and_return(nil) end - it 'returns the validation error message' do - expect(handler.get_error_message(validation_error)).to eq('The query violates a unicity constraint') + it 'returns the validation error message via translate_error' do + result = handler.translate_error(validation_error) + expect(result[:message]).to eq('The query violates a unicity constraint') end end end @@ -120,8 +121,9 @@ module Http ForestAdminDatasourceToolkit::Exceptions::ValidationError.new('Invalid data') end - it 'returns 400' do - expect(handler.get_error_status(validation_error)).to eq(400) + it 'returns 400 via translate_error' do + result = handler.translate_error(validation_error) + expect(result[:status]).to eq(400) end end diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/http/forest_admin_api_requester_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/http/forest_admin_api_requester_spec.rb index bad4e58ef..3a1f4b7e1 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/http/forest_admin_api_requester_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/http/forest_admin_api_requester_spec.rb @@ -32,17 +32,17 @@ module Http end context 'when the handle response method is called' do - it 'raises an exception when the error is a ForestException' do + it 'raises an exception when the error is a BusinessError' do expect do - forest_admin_api_requester.handle_response_error(ForestAdminDatasourceToolkit::Exceptions::ForestException.new('test')) - end.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException) + forest_admin_api_requester.handle_response_error(ForestAdminAgent::Http::Exceptions::BusinessError.new('test')) + end.to raise_error(ForestAdminAgent::Http::Exceptions::BusinessError) end it 'raises an exception when the error message contains certificate' do expect do forest_admin_api_requester.handle_response_error(Faraday::ConnectionFailed.new('test', { message: 'certificate' })) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::InternalServerError, 'ForestAdmin server TLS certificate cannot be verified. Please check that your system time is set properly.' ) end @@ -51,7 +51,7 @@ module Http expect do forest_admin_api_requester.handle_response_error(Faraday::ConnectionFailed.new('test', { status: 0 })) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::BadGatewayError, 'Failed to reach ForestAdmin server. Are you online?' ) end @@ -60,7 +60,7 @@ module Http expect do forest_admin_api_requester.handle_response_error(Faraday::ConnectionFailed.new('test', { status: 502 })) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::BadGatewayError, 'Failed to reach ForestAdmin server. Are you online?' ) end @@ -69,7 +69,7 @@ module Http expect do forest_admin_api_requester.handle_response_error(Faraday::ConnectionFailed.new('test', { status: 404 })) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::NotFoundError, 'ForestAdmin server failed to find the project related to the envSecret you configured. Can you check that you copied it properly in the Forest initialization?' ) end @@ -78,7 +78,7 @@ module Http expect do forest_admin_api_requester.handle_response_error(Faraday::ConnectionFailed.new('test', { status: 503 })) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::ServiceUnavailableError, 'Forest is in maintenance for a few minutes. We are upgrading your experience in the forest. We just need a few more minutes to get it right.' ) end @@ -87,7 +87,7 @@ module Http expect do forest_admin_api_requester.handle_response_error(Faraday::ConnectionFailed.new('test', { status: 500 })) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::InternalServerError, 'An unexpected error occurred while contacting the ForestAdmin server. Please contact support@forestadmin.com for further investigations.' ) end diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/charts/charts_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/charts/charts_spec.rb index 330525e7f..6661588d4 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/charts/charts_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/charts/charts_spec.rb @@ -135,7 +135,7 @@ module Charts expect do chart.handle_request(args) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Invalid Chart type unknown_type' + ForestAdminAgent::Http::Exceptions::BadRequestError, 'Invalid Chart type unknown_type' ) end @@ -488,7 +488,7 @@ module Charts ) end - it 'throw a ForestException when the request is not filled correctly' do + it 'throw a BadRequestError when the request is not filled correctly' do args[:params] = args[:params].merge({ relationshipFieldName: 'unknown_relation', aggregator: 'Count', @@ -500,7 +500,7 @@ module Charts expect do chart.handle_request(args) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::BadRequestError, 'Failed to generate leaderboard chart: parameters do not match pre-requisites' ) end diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/list_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/list_spec.rb index cb4661d62..f57c4d250 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/list_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/list_spec.rb @@ -140,7 +140,7 @@ module Resources it 'throws an error when the filter operator is not allowed' do args[:params][:filters] = JSON.generate({ field: 'id', operator: 'shorter_than', value: 7 }) - expect { list.handle_request(args) }.to raise_error(ForestException, "The given operator 'shorter_than' is not supported by the column: 'id'. The column is not filterable") + expect { list.handle_request(args) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ValidationError, "The given operator 'shorter_than' is not supported by the column: 'id'. The column is not filterable") end end end diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/native_query_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/native_query_spec.rb index fedd8570a..dfa5a1600 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/native_query_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/native_query_spec.rb @@ -79,7 +79,7 @@ module Resources expect do native_query.handle_request(args) end.to raise_error( - ForestException, 'Invalid Chart type unknown_type' + ForestAdminAgent::Http::Exceptions::BadRequestError, 'Invalid Chart type unknown_type' ) end @@ -143,7 +143,7 @@ module Resources allow(@root_datasource).to receive(:execute_native_query).and_return([{ foo: 10 }]) expect { native_query.handle_request(args) }.to raise_error( - ForestException, + ForestAdminAgent::Http::Exceptions::BadRequestError, "The result columns must be named 'value' instead of 'foo'" ) end @@ -187,7 +187,7 @@ module Resources allow(@root_datasource).to receive(:execute_native_query).and_return([{ foo: 10 }]) expect { native_query.handle_request(args) }.to raise_error( - ForestException, + ForestAdminAgent::Http::Exceptions::BadRequestError, "The result columns must be named 'value' instead of 'foo'" ) end @@ -231,7 +231,7 @@ module Resources allow(@root_datasource).to receive(:execute_native_query).and_return([{ foo: 10 }]) expect { native_query.handle_request(args) }.to raise_error( - ForestException, + ForestAdminAgent::Http::Exceptions::BadRequestError, "The result columns must be named 'key', 'value' instead of 'foo'" ) end @@ -288,7 +288,7 @@ module Resources ) expect { native_query.handle_request(args) }.to raise_error( - ForestException, + ForestAdminAgent::Http::Exceptions::BadRequestError, "The result columns must be named 'key', 'value' instead of 'value', 'foo'" ) end @@ -351,7 +351,7 @@ module Resources ) expect { native_query.handle_request(args) }.to raise_error( - ForestException, + ForestAdminAgent::Http::Exceptions::BadRequestError, "The result columns must be named 'key', 'value' instead of 'value', 'foo'" ) end diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/dissociate_related_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/dissociate_related_spec.rb index d294cb05c..736fd60a7 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/dissociate_related_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/dissociate_related_spec.rb @@ -233,7 +233,7 @@ module Related expect do dissociate.handle_request(args) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::BadRequestError, 'Expected no empty id list' ) end diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb index e97b5fa05..df3fbee6a 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/security/authentication_spec.rb @@ -56,7 +56,7 @@ module Security args = { params: {} } expect do authentication.handle_authentication args - end.to raise_error(Error, + end.to raise_error(ForestAdminAgent::Http::Exceptions::BadRequestError, ForestAdminAgent::Utils::ErrorMessages::MISSING_RENDERING_ID) end @@ -64,7 +64,7 @@ module Security args = { params: { 'renderingId' => 'abc' } } expect do authentication.handle_authentication args - end.to raise_error(Error, + end.to raise_error(ForestAdminAgent::Http::Exceptions::ValidationFailedError, ForestAdminAgent::Utils::ErrorMessages::INVALID_RENDERING_ID) end diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/services/ip_whitelist_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/services/ip_whitelist_spec.rb index 7c27bc181..3cd75b6ee 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/services/ip_whitelist_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/services/ip_whitelist_spec.rb @@ -55,7 +55,7 @@ module Services it 'raises an error without parsing JSON' do expect do ip_whitelist.enabled? - end.to raise_error(Error, ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED) + end.to raise_error(ForestAdminAgent::Http::Exceptions::InternalServerError, ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED) end end @@ -71,7 +71,7 @@ module Services it 'raises an error without parsing JSON' do expect do ip_whitelist.enabled? - end.to raise_error(Error, ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED) + end.to raise_error(ForestAdminAgent::Http::Exceptions::InternalServerError, ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED) end end @@ -87,7 +87,7 @@ module Services it 'raises an error without parsing JSON' do expect do ip_whitelist.enabled? - end.to raise_error(Error, ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED) + end.to raise_error(ForestAdminAgent::Http::Exceptions::InternalServerError, ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED) end end @@ -105,7 +105,7 @@ module Services it 'raises an error' do expect do ip_whitelist.enabled? - end.to raise_error(Error, ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED) + end.to raise_error(ForestAdminAgent::Http::Exceptions::InternalServerError, ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED) end end @@ -123,7 +123,7 @@ module Services it 'raises an error' do expect do ip_whitelist.enabled? - end.to raise_error(Error, ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED) + end.to raise_error(ForestAdminAgent::Http::Exceptions::InternalServerError, ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED) end end diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/services/permissions_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/services/permissions_spec.rb index a7622908c..b13eb94dd 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/services/permissions_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/services/permissions_spec.rb @@ -1035,7 +1035,7 @@ module Services expect do @permissions.can_smart_action?(args, @datasource.collections['Book'], Filter.new) - end.to raise_error(ForestException, 'The collection Book does not have this smart action') + end.to raise_error(ForestAdminAgent::Http::Exceptions::BadRequestError, 'The collection Book does not have this smart action') end it "throws when the forest schema doesn't have any actions" do @@ -1068,7 +1068,7 @@ module Services expect do @permissions.can_smart_action?(args, @datasource.collections['Book'], Filter.new) - end.to raise_error(ForestException, 'The collection Book does not have this smart action') + end.to raise_error(ForestAdminAgent::Http::Exceptions::BadRequestError, 'The collection Book does not have this smart action') end end end diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/services/smart_action_checker_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/services/smart_action_checker_spec.rb index cc3bfbce2..fb766752c 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/services/smart_action_checker_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/services/smart_action_checker_spec.rb @@ -153,7 +153,7 @@ module Services QueryStringParser.parse_caller(args), 1, Filter.new) expect do smart_action_checker.can_execute? - end.to raise_error(RequireApproval, 'This action requires to be approved.') + end.to raise_error(CustomActionRequiresApprovalError, 'This action requires to be approved.') end it 'throws when the user try to trigger the action with approvalRequired and match approvalRequiredConditions' do @@ -183,7 +183,7 @@ module Services QueryStringParser.parse_caller(args), 1, Filter.new) expect do smart_action_checker.can_execute? - end.to raise_error(RequireApproval, 'This action requires to be approved.') + end.to raise_error(CustomActionRequiresApprovalError, 'This action requires to be approved.') end it 'returns true when the user try to trigger the action with approvalRequired and triggerConditions and correct role into approvalRequired' do @@ -610,7 +610,7 @@ module Services expect do smart_action_checker.can_execute? end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::UnprocessableError, "Collection 'Book' has no primary keys. Actions with conditional permissions require a primary key to identify records." ) end @@ -643,7 +643,7 @@ module Services expect do smart_action_checker.can_execute? end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::UnprocessableError, "Collection 'Book' has no primary keys. Actions with conditional permissions require a primary key to identify records." ) end @@ -677,7 +677,7 @@ module Services expect do smart_action_checker.can_execute? end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::UnprocessableError, "Collection 'Book' has no primary keys. Actions with conditional permissions require a primary key to identify records." ) end @@ -717,7 +717,7 @@ module Services expect do smart_action_checker.can_execute? end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::UnprocessableError, "Collection 'Book' has no primary keys. Actions with conditional permissions require a primary key to identify records." ) end @@ -753,7 +753,7 @@ module Services expect do smart_action_checker.can_execute? end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::UnprocessableError, "Collection 'Book' has no primary keys. Actions with conditional permissions require a primary key to identify records." ) end diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/condition_tree_parser_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/condition_tree_parser_spec.rb index a381b6499..a8db921c6 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/condition_tree_parser_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/condition_tree_parser_spec.rb @@ -32,7 +32,7 @@ module Utils expect do condition_tree_parser.from_plain_object(collection_category, {}) - end.to raise_error(ForestException, 'Failed to instantiate condition tree') + end.to raise_error(ForestAdminAgent::Http::Exceptions::BadRequestError, 'Failed to instantiate condition tree') end it 'works with aggregator' do diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/id_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/id_spec.rb index a0a5cec7f..50b72bf7d 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/id_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/id_spec.rb @@ -52,7 +52,7 @@ module Utils expect do described_class.unpack_id(collection, '1|foo') end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::BadRequestError, 'Expected 1 primary keys, found 2' ) end @@ -70,7 +70,7 @@ module Utils expect do described_class.unpack_id(collection, '1') end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::BadRequestError, 'Expected 2 primary keys, found 1' ) end @@ -157,7 +157,7 @@ module Utils expect do described_class.pack_id(collection_foo, { 'id' => 1, 'foo' => 'bar' }) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::UnprocessableError, 'This collection has no primary key' ) end diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/query_string_parser_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/query_string_parser_spec.rb index 6bd598715..d90dd448c 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/query_string_parser_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/query_string_parser_spec.rb @@ -35,7 +35,7 @@ module Utils expect do described_class.parse_caller(args) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + Http::Exceptions::BadRequestError, 'Missing timezone' ) end @@ -52,7 +52,7 @@ module Utils expect do described_class.parse_caller(args) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + Http::Exceptions::BadRequestError, 'Invalid timezone: foo/timezone' ) end @@ -341,7 +341,7 @@ module Utils expect do described_class.parse_pagination(args) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + Http::Exceptions::BadRequestError, 'Invalid pagination [limit: -5, skip: NaN]' ) end @@ -358,7 +358,7 @@ module Utils expect do described_class.parse_pagination(args) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + Http::Exceptions::BadRequestError, 'Invalid pagination [limit: abc, skip: 1]' ) end @@ -373,7 +373,7 @@ module Utils expect do described_class.parse_pagination(args) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + Http::Exceptions::BadRequestError, 'Invalid pagination [limit: -50, skip: 1]' ) end @@ -388,7 +388,7 @@ module Utils expect do described_class.parse_pagination(args) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + Http::Exceptions::BadRequestError, 'Invalid pagination [limit: 0, skip: 1]' ) end @@ -403,7 +403,7 @@ module Utils expect do described_class.parse_pagination(args) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + Http::Exceptions::BadRequestError, 'Invalid pagination [limit: 1.5, skip: 1]' ) end @@ -420,7 +420,7 @@ module Utils expect do described_class.parse_pagination(args) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + Http::Exceptions::BadRequestError, 'Invalid pagination [limit: 50, skip: invalid]' ) end @@ -435,7 +435,7 @@ module Utils expect do described_class.parse_pagination(args) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + Http::Exceptions::BadRequestError, 'Invalid pagination [limit: 50, skip: -1]' ) end @@ -450,7 +450,7 @@ module Utils expect do described_class.parse_pagination(args) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + Http::Exceptions::BadRequestError, 'Invalid pagination [limit: 50, skip: 1.5]' ) end @@ -465,7 +465,7 @@ module Utils expect do described_class.parse_pagination(args) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + Http::Exceptions::BadRequestError, "Invalid pagination [limit: 50, skip: '; DROP TABLE users--]" ) end @@ -533,7 +533,7 @@ module Utils expect do described_class.parse_condition_tree(collection_category, args) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminDatasourceToolkit::Exceptions::ValidationError, "The given operator 'not_equal' is not supported by the column: 'id'. The column is not filterable" ) end @@ -585,7 +585,7 @@ module Utils expect do described_class.parse_search(collection_category, args) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + Http::Exceptions::BadRequestError, 'Collection is not searchable' ) end @@ -678,7 +678,7 @@ module Utils expect do described_class.parse_sort(collection_user, args) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminDatasourceToolkit::Exceptions::ValidationError, "Column not found: 'User.fieldThatDoNotExist'" ) end @@ -704,7 +704,7 @@ module Utils expect do described_class.parse_sort(collection_user, args) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminDatasourceToolkit::Exceptions::ValidationError, 'Column not found: \'User.fieldThatDoesNotExist\'' ) end diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/query_validator_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/query_validator_spec.rb index b6e4d8bd0..d384166bb 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/query_validator_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/query_validator_spec.rb @@ -49,43 +49,43 @@ module Utils describe 'queries that raise exceptions' do it 'raises an error for an empty query' do query = ' ' - expect { described_class.valid?(query) }.to raise_error(ForestException, 'Query cannot be empty.') + expect { described_class.valid?(query) }.to raise_error(Http::Exceptions::BadRequestError, 'Query cannot be empty.') end it 'raises an error for non-SELECT queries' do query = 'DELETE FROM users;' - expect { described_class.valid?(query) }.to raise_error(ForestException, 'Only SELECT queries are allowed.') + expect { described_class.valid?(query) }.to raise_error(Http::Exceptions::BadRequestError, 'Only SELECT queries are allowed.') end it 'raises an error for multiple queries' do query = 'SELECT * FROM users; SELECT * FROM orders;' - expect { described_class.valid?(query) }.to raise_error(ForestException, 'Only one query is allowed.') + expect { described_class.valid?(query) }.to raise_error(Http::Exceptions::BadRequestError, 'Only one query is allowed.') end it 'raises an error for unbalanced parentheses outside WHERE clause' do query = 'SELECT (id, name FROM users WHERE (id > 1);' - expect { described_class.valid?(query) }.to raise_error(ForestException, 'The query contains unbalanced parentheses.') + expect { described_class.valid?(query) }.to raise_error(Http::Exceptions::BadRequestError, 'The query contains unbalanced parentheses.') end it 'raises an error for a semicolon not at the end of the query' do query = 'SELECT * FROM users; WHERE id > 1' - expect { described_class.valid?(query) }.to raise_error(ForestException, 'Semicolon must only appear as the last character in the query.') + expect { described_class.valid?(query) }.to raise_error(Http::Exceptions::BadRequestError, 'Semicolon must only appear as the last character in the query.') end it 'raises an error for forbidden keywords even inside subqueries' do query = 'SELECT * FROM users WHERE id IN (DROP TABLE users);' - expect { described_class.valid?(query) }.to raise_error(ForestException, 'The query contains forbidden keyword: DROP.') + expect { described_class.valid?(query) }.to raise_error(Http::Exceptions::BadRequestError, 'The query contains forbidden keyword: DROP.') end it 'raises an error for unbalanced parentheses in subqueries' do query = 'SELECT * FROM (SELECT id, name FROM users WHERE id > 1;' - expect { described_class.valid?(query) }.to raise_error(ForestException, 'The query contains unbalanced parentheses.') + expect { described_class.valid?(query) }.to raise_error(Http::Exceptions::BadRequestError, 'The query contains unbalanced parentheses.') end it 'raises an error for an OR-based injection' do query = "SELECT * FROM users WHERE username = 'admin' OR 1=1;" expect { described_class.valid?(query) } - .to raise_error(ForestException, 'The query contains a potential SQL injection pattern.') + .to raise_error(Http::Exceptions::BadRequestError, 'The query contains a potential SQL injection pattern.') end end end diff --git a/packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/datasource.rb b/packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/datasource.rb index 9285f2d72..3c7fde396 100644 --- a/packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/datasource.rb +++ b/packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/datasource.rb @@ -41,7 +41,7 @@ def execute_native_query(connection_name, query, binds) ForestAdminDatasourceToolkit::Utils::HashHelper.convert_keys(result.to_a) rescue StandardError => e - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "Error when executing SQL query: '#{e.full_message}'" end end diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb index 65bb93bb0..59952f63d 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator.rb @@ -72,10 +72,10 @@ def ensure_form_is_correct(form, action_name) pages = is_page_component.call(form.first) form.each do |element| - if pages != is_page_component.call(element) - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, - "You cannot mix pages and other form elements in smart action '#{action_name}' form" - end + next unless pages != is_page_component.call(element) + + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, + "You cannot mix pages and other form elements in smart action '#{action_name}' form" end end diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb index c0c6ded7c..4dba2ddb0 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb @@ -41,9 +41,10 @@ def validate_fields_ids(form = @form, used = []) end else if used.include?(element.id) - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, + raise ForestAdminAgent::Http::Exceptions::ConflictError, "All field must have different 'id'. Conflict come from field '#{element.id}'" end + used << element.id end end diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/dynamic_field.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/dynamic_field.rb index 58a3417af..e854cc8b2 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/dynamic_field.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/dynamic_field.rb @@ -23,8 +23,7 @@ def initialize( super(type: type) if id.nil? && label.nil? - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, - "A field must have an 'id' or a 'label' defined." + raise ForestAdminAgent::Http::Exceptions::BadRequestError, "A field must have an 'id' or a 'label' defined." end @label = label.nil? ? id : label diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_factory.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_factory.rb index a4808e7d5..6a75d595e 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_factory.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_factory.rb @@ -64,7 +64,7 @@ def self.build_widget(field) when 'UserDropdown' WidgetField::UserDropdownField.new(**field) else - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, "Unknow widget type: #{field[:widget]}" + raise ForestAdminAgent::Http::Exceptions::BadRequestError, "Unknow widget type: #{field[:widget]}" end end @@ -79,8 +79,7 @@ def self.build_layout_element(field) when 'Page' FormLayoutElement::PageElement.new(**field) else - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, - "Unknow component type: #{field[:component]}" + raise ForestAdminAgent::Http::Exceptions::BadRequestError, "Unknow component type: #{field[:component]}" end end end diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_layout_element.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_layout_element.rb index f2409f7b1..e4b2ea5f2 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_layout_element.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/form_layout_element.rb @@ -49,14 +49,16 @@ def static? private def validate_fields_presence!(options) - raise ForestException, "Using 'fields' in a 'Row' configuration is mandatory" unless options.key?(:fields) + return if options.key?(:fields) + + raise ForestAdminAgent::Http::Exceptions::BadRequestError, "Using 'fields' in a 'Row' configuration is mandatory" end def validate_no_layout_subfields!(fields) fields.each do |field| if (field.is_a?(DynamicField) && field.type == 'Layout') || (field.is_a?(Hash) && field[:type] == 'Layout') - raise ForestException, "A 'Row' form element doesn't allow layout elements as subfields" + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "A 'Row' form element doesn't allow layout elements as subfields" end end end @@ -90,13 +92,13 @@ def static? def validate_elements_presence!(options) return if options.key?(:elements) - raise ForestException, "Using 'elements' in a 'Page' configuration is mandatory" + raise ForestAdminAgent::Http::Exceptions::BadRequestError, "Using 'elements' in a 'Page' configuration is mandatory" end def validate_no_page_elements!(elements) elements&.each do |element| if element[:component] == 'Page' - raise ForestException, "'Page' component cannot be used within 'elements'" + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "'Page' component cannot be used within 'elements'" end end end diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/widget_field.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/widget_field.rb index f647fe7e9..00ac3780d 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/widget_field.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/widget_field.rb @@ -7,13 +7,12 @@ def self.validate_arg(options, attribute, rule) case rule[attribute] when 'contains' unless rule[:value].include? options[attribute] - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, + raise ForestAdminAgent::Http::Exceptions::BadRequestError, "'#{attribute}' must have a value included in [#{rule[:value]}]" end when 'present' unless options.key? attribute - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, - "key '#{attribute}' must be defined" + raise ForestAdminAgent::Http::Exceptions::BadRequestError, "key '#{attribute}' must be defined" end end end diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/binary/binary_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/binary/binary_collection_decorator.rb index d600850ac..c62ba35ab 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/binary/binary_collection_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/binary/binary_collection_decorator.rb @@ -23,10 +23,11 @@ def initialize(child_collection, datasource) def set_binary_mode(name, type) field = @child_collection.schema[:fields][name] - raise Exceptions::ForestException, 'Invalid binary mode' unless %w[datauri hex].include?(type) + raise ForestAdminAgent::Http::Exceptions::BadRequestError, 'Invalid binary mode' unless %w[datauri + hex].include?(type) unless field&.type == 'Column' && field&.column_type == 'Binary' - raise Exceptions::ForestException, 'Expected a binary field' + raise ForestAdminAgent::Http::Exceptions::BadRequestError, 'Expected a binary field' end @use_hex_conversion[name] = (type == 'hex') diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/chart/chart_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/chart/chart_collection_decorator.rb index 3d34cce60..575a06b2b 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/chart/chart_collection_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/chart/chart_collection_decorator.rb @@ -13,7 +13,9 @@ def initialize(child_collection, datasource) end def add_chart(name, &definition) - raise(Exceptions::ForestException, "Chart #{name} already exists.") if schema[:charts].include?(name) + if schema[:charts].include?(name) + raise ForestAdminAgent::Http::Exceptions::ConflictError, "Chart #{name} already exists." + end @charts[name] = definition mark_schema_as_dirty diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/chart/chart_context.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/chart/chart_context.rb index b77ae60a6..af2782a08 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/chart/chart_context.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/chart/chart_context.rb @@ -19,7 +19,7 @@ def _composite_record_id=(value) def record_id if @composite_record_id.size > 1 - raise Exceptions::ForestException, + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "Collection is using a composite pk: use 'context.composite_record_id'." end diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/chart/chart_datasource_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/chart/chart_datasource_decorator.rb index 8cf7f2641..a8edd5ca1 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/chart/chart_datasource_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/chart/chart_datasource_decorator.rb @@ -15,7 +15,7 @@ def schema duplicate = @charts.keys.find { |name| child_schema[:charts].include?(name) } - raise(Exceptions::ForestException, "Chart #{duplicate} is defined twice.") if duplicate + raise ForestAdminAgent::Http::Exceptions::ConflictError, "Chart #{duplicate} is defined twice." if duplicate child_schema[:charts] = child_schema[:charts].union(@charts.keys) @@ -23,7 +23,9 @@ def schema end def add_chart(name, &definition) - raise(Exceptions::ForestException, "Chart #{name} already exists.") if schema[:charts].include?(name) + if schema[:charts].include?(name) + raise ForestAdminAgent::Http::Exceptions::ConflictError, "Chart #{name} already exists." + end @charts[name] = definition end diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/computed/compute_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/computed/compute_collection_decorator.rb index 0a9f610be..eead5bc88 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/computed/compute_collection_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/computed/compute_collection_decorator.rb @@ -37,14 +37,14 @@ def register_computed(name, computed) # Check that all dependencies exist and are columns computed.dependencies.each do |field| if field.include?(':') && schema[:fields][field.partition(':')[0]].type == 'PolymorphicManyToOne' - raise ForestException, + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "Dependencies over a polymorphic relations(#{self.name}.#{field.partition(":")[0]}) are forbidden" end FieldValidator.validate(self, field) end if computed.dependencies.length <= 0 - raise ForestException, + raise ForestAdminAgent::Http::Exceptions::BadRequestError, "Computed field '#{name}' must have at least one dependency." end diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/operators_emulate/operators_emulate_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/operators_emulate/operators_emulate_collection_decorator.rb index 4642835ca..13418eaed 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/operators_emulate/operators_emulate_collection_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/operators_emulate/operators_emulate_collection_decorator.rb @@ -25,17 +25,18 @@ def replace_field_operator(name, operator, &replace_by) schema = child_collection.schema[:fields][pk] operators = schema.filter_operators - if !operators.include?(Operators::EQUAL) || !operators.include?(Operators::IN) - raise Exceptions::ForestException, "Cannot override operators on collection #{self.name}: " \ - "the primary key columns must support 'Equal' and 'In' operators." - end + next unless !operators.include?(Operators::EQUAL) || !operators.include?(Operators::IN) + + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, + "Cannot override operators on collection #{name}: " \ + "the primary key columns must support 'Equal' and 'In' operators." end # Check that targeted field is valid field = child_collection.schema[:fields][name] Validations::FieldValidator.validate(self, name) unless field.is_a?(ForestAdminDatasourceToolkit::Schema::ColumnSchema) - raise Exceptions::ForestException, 'Cannot replace operator for relation' + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Cannot replace operator for relation' end # Mark the field operator as replaced. @@ -86,7 +87,8 @@ def compute_equivalent(caller, leaf, replacements) replacement_id = "#{name}.#{leaf.field}[#{leaf.operator}]" sub_replacements = replacements.union([replacement_id]) if replacements.include?(replacement_id) - raise Exceptions::ForestException, "Operator replacement cycle: #{sub_replacements.join(" -> ")}" + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, + "Operator replacement cycle: #{sub_replacements.join(" -> ")}" end result = handler.call(leaf.value, Context::CollectionCustomizationContext.new(self, caller)) diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/publication/publication_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/publication/publication_collection_decorator.rb index 1ad8cf884..295a81834 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/publication/publication_collection_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/publication/publication_collection_decorator.rb @@ -12,8 +12,8 @@ def initialize(child_collection, datasource) def change_field_visibility(name, visible) field = child_collection.schema[:fields][name] - raise ForestException, "No such field '#{name}'" unless field - raise ForestException, 'Cannot hide primary key' if ForestAdminDatasourceToolkit::Utils::Schema.primary_key?( + raise ForestAdminAgent::Http::Exceptions::NotFoundError, "No such field '#{name}'" unless field + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Cannot hide primary key' if ForestAdminDatasourceToolkit::Utils::Schema.primary_key?( child_collection, name ) @@ -21,8 +21,9 @@ def change_field_visibility(name, visible) next unless field_schema.type == 'PolymorphicManyToOne' && [field_schema.foreign_key, field_schema.foreign_key_type_field].include?(name) - raise ForestException, "Cannot remove field '#{self.name}.#{name}', because it's implied " \ - "in a polymorphic relation '#{self.name}.#{field_name}'" + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, + "Cannot remove field '#{self.name}.#{name}', because it's implied " \ + "in a polymorphic relation '#{self.name}.#{field_name}'" end if visible @blacklist.delete(name) diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/publication/publication_datasource_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/publication/publication_datasource_decorator.rb index 960077fc4..027b930aa 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/publication/publication_datasource_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/publication/publication_datasource_decorator.rb @@ -17,7 +17,9 @@ def collections end def get_collection(name) - raise ForestException, "Collection '#{name}' was removed." if @blacklist.include?(name) + if @blacklist.include?(name) + raise ForestAdminAgent::Http::Exceptions::NotFoundError, "Collection '#{name}' was removed." + end super end @@ -57,7 +59,8 @@ def validate_is_removable(collection_name) inverse = ForestAdminDatasourceToolkit::Utils::Collection.get_inverse_relation(collection, field_name) - raise ForestException, "Cannot remove #{collection.name} because it's a potential target of polymorphic relation #{field_schema.foreign_collection}.#{inverse}" + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, + "Cannot remove #{collection.name} because it's a potential target of polymorphic relation #{field_schema.foreign_collection}.#{inverse}" end end diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/relation/relation_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/relation/relation_collection_decorator.rb index b993920ba..464df682c 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/relation/relation_collection_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/relation/relation_collection_decorator.rb @@ -159,18 +159,20 @@ def check_keys(owner, target_owner, key_name, target_name) return unless key.column_type != target.column_type - raise ForestException, + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "Types from '#{owner.name}.#{key_name}' and '#{target_owner.name}.#{target_name}' do not match." end def check_column(owner, name) column = owner.schema[:fields][name] - raise ForestException, "Column not found: '#{owner.name}.#{name}'" if !column || column.type != 'Column' + if !column || column.type != 'Column' + raise ForestAdminAgent::Http::Exceptions::NotFoundError, "Column not found: '#{owner.name}.#{name}'" + end return if column.filter_operators.include?(Operators::IN) - raise ForestException, "Column does not support the In operator: '#{owner.name}.#{name}'" + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "Column does not support the In operator: '#{owner.name}.#{name}'" end def rewrite_field(field) diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/rename_collection/rename_collection_datasource_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/rename_collection/rename_collection_datasource_decorator.rb index 49526258d..6564f925f 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/rename_collection/rename_collection_datasource_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/rename_collection/rename_collection_datasource_decorator.rb @@ -24,7 +24,8 @@ def get_collection(name) # Collection has been renamed, user is using the old name if @from_child_name.key?(name) - raise Exceptions::ForestException, "Collection '#{name}' has been renamed to '#{@from_child_name[name]}'" + raise ForestAdminAgent::Http::Exceptions::NotFoundError, + "Collection '#{name}' has been renamed to '#{@from_child_name[name]}'" end # Collection has not been renamed @@ -42,7 +43,8 @@ def rename_collections(renames) rename_collection(old_name, new_name) end else - raise Exceptions::ForestException, 'Invalid argument for rename_collections, must be a function or a hash' + raise ForestAdminAgent::Http::Exceptions::BadRequestError, + 'Invalid argument for rename_collections, must be a function or a hash' end end @@ -54,13 +56,13 @@ def rename_collection(current_name, new_name) # Check new name is not already used if collections.any? { |name, _collection| name == new_name } - raise Exceptions::ForestException, + raise ForestAdminAgent::Http::Exceptions::ConflictError, "The given new collection name '#{new_name}' is already defined" end # Check we don't rename a collection twice if @to_child_name[current_name] - raise Exceptions::ForestException, + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "Cannot rename a collection twice: #{@to_child_name[current_name]}->#{current_name}->#{new_name}" end @@ -70,7 +72,7 @@ def rename_collection(current_name, new_name) reverse_relation_name = Utils::Collection.get_inverse_relation(get_collection(current_name), field_name) - raise Exceptions::ForestException, + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "Cannot rename collection #{current_name} because it's a target of a polymorphic relation " \ "'#{field_schema.foreign_collection}.#{reverse_relation_name}'" end diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/rename_field/rename_field_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/rename_field/rename_field_collection_decorator.rb index 60dfdb5e4..d10f0def2 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/rename_field/rename_field_collection_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/rename_field/rename_field_collection_decorator.rb @@ -16,7 +16,7 @@ def initialize(child_collection, datasource) def rename_field(current_name, new_name) unless schema[:fields][current_name] - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, "No such field '#{current_name}'" + raise ForestAdminAgent::Http::Exceptions::NotFoundError, "No such field '#{current_name}'" end initial_name = current_name @@ -27,8 +27,9 @@ def rename_field(current_name, new_name) next unless field_schema.type == 'PolymorphicManyToOne' && [field_schema.foreign_key, field_schema.foreign_key_type_field].include?(current_name) - raise ForestException, "Cannot rename '#{name}.#{current_name}', because it's implied " \ - "in a polymorphic relation '#{name}.#{field_name}'" + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, + "Cannot rename '#{name}.#{current_name}', because it's implied " \ + "in a polymorphic relation '#{name}.#{field_name}'" end # Revert previous renaming (avoids conflicts and need to recurse on @to_child_collection). diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/sort/sort_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/sort/sort_collection_decorator.rb index 8b955d728..505c5974c 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/sort/sort_collection_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/sort/sort_collection_decorator.rb @@ -20,7 +20,7 @@ def emulate_field_sorting(name) def replace_field_sorting(name, equivalent_sort) if equivalent_sort.nil? - raise ForestException, 'A new sorting method should be provided to replace field sorting' + raise ForestAdminAgent::Http::Exceptions::BadRequestError, 'A new sorting method should be provided to replace field sorting' end replace_or_emulate_field_sorting(name, equivalent_sort) diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/validation/validation_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/validation/validation_collection_decorator.rb index 3a8dab182..ba85a1d1b 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/validation/validation_collection_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/validation/validation_collection_decorator.rb @@ -17,10 +17,13 @@ def add_validation(name, validation) field = @child_collection.schema[:fields][name] if field.nil? || field.type != 'Column' - raise ForestException, + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Cannot add validators on a relation, use the foreign key instead' end - raise ForestException, 'Cannot add validators on a readonly field' if field.is_read_only + if field.is_read_only + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, + 'Cannot add validators on a readonly field' + end @validation[name] ||= [] @validation[name].push(validation) diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/write/write_replace/write_replace_collection_decorator.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/write/write_replace/write_replace_collection_decorator.rb index 230106c44..96683648a 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/write/write_replace/write_replace_collection_decorator.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/write/write_replace/write_replace_collection_decorator.rb @@ -13,7 +13,10 @@ def initialize(child_collection, datasource) end def replace_field_writing(field_name, &definition) - raise ForestException, 'A new writing method should be provided to replace field writing' unless definition + unless definition + raise ForestAdminAgent::Http::Exceptions::BadRequestError, + 'A new writing method should be provided to replace field writing' + end ForestAdminDatasourceToolkit::Validations::FieldValidator.validate(self, field_name) @@ -64,7 +67,8 @@ def rewrite_patch(caller, action, patch, used_handlers = [], filter = nil) def rewrite_key(context, key, used) if used.include?(key) - raise ForestException, "Conflict value on the field #{key}. It received several values." + raise ForestAdminAgent::Http::Exceptions::ConflictError, + "Conflict value on the field #{key}. It received several values." end field_schema = schema.nil? ? nil : schema[:fields][key] @@ -79,7 +83,8 @@ def rewrite_key(context, key, used) end if field_patch && !field_patch.is_a?(Hash) - raise ForestException, "The write handler of #{key} should return an Hash or nothing." + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, + "The write handler of #{key} should return an Hash or nothing." end # Isolate change to our own value (which should not recurse) and the rest which should @@ -101,7 +106,7 @@ def rewrite_key(context, key, used) { key => relation.rewrite_patch(context.caller, context.action, context.record[key]) } else - raise ForestException, "Unknown field: '#{key}'" + raise ForestAdminAgent::Http::Exceptions::NotFoundError, "Unknown field: '#{key}'" end end @@ -118,7 +123,8 @@ def deep_merge(*patches) elsif sub_value.is_a?(Hash) acc[sub_key] = deep_merge(acc[sub_key], sub_value) else - raise ForestException, "Conflict value on the field #{sub_key}. It received several values." + raise ForestAdminAgent::Http::Exceptions::ConflictError, + "Conflict value on the field #{sub_key}. It received several values." end end end diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/plugins/add_external_relation.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/plugins/add_external_relation.rb index d0f517032..fee9bda59 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/plugins/add_external_relation.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/plugins/add_external_relation.rb @@ -8,7 +8,8 @@ def run(_datasource_customizer, collection_customizer = nil, options = {}) primary_keys = ForestAdminDatasourceToolkit::Utils::Schema.primary_keys(collection_customizer.collection) unless options.key?(:name) && options.key?(:schema) && options.key?(:listRecords) - raise ForestException, 'The options parameter must contains the following keys: `name, schema, listRecords`' + raise ForestAdminAgent::Http::Exceptions::BadRequestError, + 'The options parameter must contains the following keys: `name, schema, listRecords`' end collection_customizer.add_field( diff --git a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/plugins/import_field.rb b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/plugins/import_field.rb index a04f434a5..729be2eea 100644 --- a/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/plugins/import_field.rb +++ b/packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/plugins/import_field.rb @@ -6,7 +6,8 @@ class ImportField < Plugin def run(datasource_customizer, collection_customizer = nil, options = {}) if options[:name].nil? || options[:path].nil? - raise ForestException, 'The options parameter must contains the following keys: `name, path`' + raise ForestAdminAgent::Http::Exceptions::BadRequestError, + 'The options parameter must contains the following keys: `name, path`' end options[:readonly] = false unless options.key?(:readonly) @@ -14,7 +15,8 @@ def run(datasource_customizer, collection_customizer = nil, options = {}) result = options[:path].split(':').reduce({ collection: collection_customizer.name }) do |memo, field| collection = datasource_customizer.get_collection(memo[:collection]) unless collection.schema[:fields].key?(field) - raise ForestException, "Field #{field} not found in collection #{collection.name}" + raise ForestAdminAgent::Http::Exceptions::NotFoundError, + "Field #{field} not found in collection #{collection.name}" end to_one_relations = %w[ManyToOne OneToOne] @@ -53,7 +55,7 @@ def run(datasource_customizer, collection_customizer = nil, options = {}) end if !options[:readonly] && schema.is_read_only - raise ForestException, + raise ForestAdminAgent::Http::Exceptions::BadRequestError, "Readonly option should not be false because the field #{options[:path]} is not writable" end diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/collection_customizer_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/collection_customizer_spec.rb index fb1bd2f77..3fc1661f8 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/collection_customizer_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/collection_customizer_spec.rb @@ -332,7 +332,7 @@ module ForestAdminDatasourceCustomizer it 'throwns an exception when the plugin have options keys missing' do customizer = described_class.new(@datasource_customizer, @datasource_customizer.stack, 'book') customizer.add_external_relation('tags', {}) - expect { @datasource_customizer.datasource({}) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, 'The options parameter must contains the following keys: `name, schema, listRecords`') + expect { @datasource_customizer.datasource({}) }.to raise_error(ForestAdminAgent::Http::Exceptions::BadRequestError, 'The options parameter must contains the following keys: `name, schema, listRecords`') end end diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/datasource_customizer_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/datasource_customizer_spec.rb index b510d3587..780034a98 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/datasource_customizer_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/datasource_customizer_spec.rb @@ -135,7 +135,7 @@ def schema expect do datasource_customizer.get_root_datasource_by_connection('unknown_connection') - end.to raise_error(NotFoundError, "Native query connection 'unknown_connection' is unknown.") + end.to raise_error(ForestAdminAgent::Http::Exceptions::NotFoundError, "Native query connection 'unknown_connection' is unknown.") end it 'return the expected datasource' do diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator_spec.rb index 920d045f0..046c472ce 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/action_collection_decorator_spec.rb @@ -472,7 +472,7 @@ module Action expect do @decorated_book.add_action('make photocopy', action) - end.to raise_error(Exceptions::ForestException, "All field must have different 'id'. Conflict come from field 'id'") + end.to raise_error(ForestAdminAgent::Http::Exceptions::ConflictError, "All field must have different 'id'. Conflict come from field 'id'") end it 'raise an error if multiple fields with same id are provided in row' do @@ -492,7 +492,7 @@ module Action expect do @decorated_book.add_action('make photocopy', action) - end.to raise_error(Exceptions::ForestException, "All field must have different 'id'. Conflict come from field 'id'") + end.to raise_error(ForestAdminAgent::Http::Exceptions::ConflictError, "All field must have different 'id'. Conflict come from field 'id'") end it 'raise an error if field (hash) is provided without id and label' do @@ -505,7 +505,7 @@ module Action expect do @decorated_book.add_action('make photocopy', action) - end.to raise_error(Exceptions::ForestException, "A field must have an 'id' or a 'label' defined.") + end.to raise_error(ForestAdminAgent::Http::Exceptions::BadRequestError, "A field must have an 'id' or a 'label' defined.") end it 'raise an error if form mix form elements and pages' do @@ -525,7 +525,7 @@ module Action expect do @decorated_book.add_action('make photocopy', action) - end.to raise_error(Exceptions::ForestException, "You cannot mix pages and other form elements in smart action 'make photocopy' form") + end.to raise_error(ForestAdminAgent::Http::Exceptions::UnprocessableError, "You cannot mix pages and other form elements in smart action 'make photocopy' form") end end end diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/dynamic_field_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/dynamic_field_spec.rb index dfee79518..0f4e09c41 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/dynamic_field_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/dynamic_field_spec.rb @@ -35,7 +35,7 @@ module Action expect do described_class.new(**plain_field) - end.to raise_error(Exceptions::ForestException, "A field must have an 'id' or a 'label' defined.") + end.to raise_error(ForestAdminAgent::Http::Exceptions::BadRequestError, "A field must have an 'id' or a 'label' defined.") end it 'raise an error if id and label are nil' do @@ -43,7 +43,7 @@ module Action expect do described_class.new(**plain_field) - end.to raise_error(Exceptions::ForestException, "A field must have an 'id' or a 'label' defined.") + end.to raise_error(ForestAdminAgent::Http::Exceptions::BadRequestError, "A field must have an 'id' or a 'label' defined.") end end end diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/form_factory_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/form_factory_spec.rb index 7904db78c..589c948e9 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/form_factory_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/action/form_factory_spec.rb @@ -23,7 +23,7 @@ module Action it 'raises an exception for an unknown widget type' do field = { widget: 'UnknownWidget' } - expect { described_class.build_widget(field) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException) + expect { described_class.build_widget(field) }.to raise_error(ForestAdminAgent::Http::Exceptions::BadRequestError) end context 'when widget is AddressAutocomplete' do @@ -229,12 +229,12 @@ module Action it 'raises an exception when fields are missing' do element.delete(:fields) - expect { described_class.build_layout_element(element) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException) + expect { described_class.build_layout_element(element) }.to raise_error(ForestAdminAgent::Http::Exceptions::BadRequestError) end it 'raises an exception when fields contain a layout element' do element[:fields] << { type: 'Layout' } - expect { described_class.build_layout_element(element) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException) + expect { described_class.build_layout_element(element) }.to raise_error(ForestAdminAgent::Http::Exceptions::UnprocessableError) end end @@ -252,12 +252,12 @@ module Action it 'raises an exception when there is no elements' do element.delete(:elements) - expect { described_class.build_layout_element(element) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, "Using 'elements' in a 'Page' configuration is mandatory") + expect { described_class.build_layout_element(element) }.to raise_error(ForestAdminAgent::Http::Exceptions::BadRequestError, "Using 'elements' in a 'Page' configuration is mandatory") end it 'raises an error when element contains a Page' do element[:elements] = [element] - expect { described_class.build_layout_element(element) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, "'Page' component cannot be used within 'elements'") + expect { described_class.build_layout_element(element) }.to raise_error(ForestAdminAgent::Http::Exceptions::UnprocessableError, "'Page' component cannot be used within 'elements'") end end end diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/binary/binary_collection_decorator_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/binary/binary_collection_decorator_spec.rb index 68bc85376..4eb3043bb 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/binary/binary_collection_decorator_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/binary/binary_collection_decorator_spec.rb @@ -90,15 +90,15 @@ module Binary describe 'set_binary_mode' do it 'raise an error when an invalid mode is provided' do - expect { @decorated_book.set_binary_mode('name', 'invalid') }.to raise_error(Exceptions::ForestException) + expect { @decorated_book.set_binary_mode('name', 'invalid') }.to raise_error(ForestAdminAgent::Http::Exceptions::BadRequestError) end it 'raise an error when the field does not exist' do - expect { @decorated_book.set_binary_mode('invalid', 'hex') }.to raise_error(Exceptions::ForestException) + expect { @decorated_book.set_binary_mode('invalid', 'hex') }.to raise_error(ForestAdminAgent::Http::Exceptions::BadRequestError) end it 'raise an error when the field is not a binary field' do - expect { @decorated_book.set_binary_mode('title', 'hex') }.to raise_error(Exceptions::ForestException) + expect { @decorated_book.set_binary_mode('title', 'hex') }.to raise_error(ForestAdminAgent::Http::Exceptions::BadRequestError) end end diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/chart/chart_collection_decorator_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/chart/chart_collection_decorator_spec.rb index ba15435d3..0da37c7ce 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/chart/chart_collection_decorator_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/chart/chart_collection_decorator_spec.rb @@ -40,10 +40,7 @@ module Chart it 'not let adding a chart with the same name' do expect do @decorated_book.add_chart('child_chart') { { countCurrent: 2 } } - end.to raise_error( - Exceptions::ForestException, - 'Chart child_chart already exists.' - ) + end.to raise_error(ForestAdminAgent::Http::Exceptions::ConflictError, 'Chart child_chart already exists.') end end diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/chart/chart_context_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/chart/chart_context_spec.rb index ca05592f1..2b9e95eed 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/chart/chart_context_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/chart/chart_context_spec.rb @@ -24,7 +24,7 @@ module Chart describe 'record_id' do it 'raise an error' do expect { @context.record_id }.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::UnprocessableError, "Collection is using a composite pk: use 'context.composite_record_id'." ) end diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/chart/chart_datasource_decorator_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/chart/chart_datasource_decorator_spec.rb index bfddfd378..87610ddfe 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/chart/chart_datasource_decorator_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/chart/chart_datasource_decorator_spec.rb @@ -38,7 +38,7 @@ module Chart it 'raise an error if a chart already exists' do expect do decorator.add_chart('my_chart') - end.to raise_error(Exceptions::ForestException, 'Chart my_chart already exists.') + end.to raise_error(ForestAdminAgent::Http::Exceptions::ConflictError, 'Chart my_chart already exists.') end it 'schema should not be empty' do @@ -61,7 +61,7 @@ module Chart it 'raise an error when adding a duplicate' do expect do decorator.add_chart('my_chart') - end.to raise_error(Exceptions::ForestException, 'Chart my_chart already exists.') + end.to raise_error(ForestAdminAgent::Http::Exceptions::ConflictError, 'Chart my_chart already exists.') end end @@ -78,7 +78,7 @@ module Chart expect(first_decorator.schema).to eq({ charts: ['my_chart'] }) expect do second_decorator.schema - end.to raise_error(Exceptions::ForestException, 'Chart my_chart is defined twice.') + end.to raise_error(ForestAdminAgent::Http::Exceptions::ConflictError, 'Chart my_chart is defined twice.') end end end diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/computed/compute_collection_decorator_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/computed/compute_collection_decorator_spec.rb index c5c811639..16663e270 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/computed/compute_collection_decorator_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/computed/compute_collection_decorator_spec.rb @@ -105,7 +105,7 @@ module Computed values: proc { |records| records } ) ) - end.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, "Computed field 'newField' must have at least one dependency.") + end.to raise_error(ForestAdminAgent::Http::Exceptions::BadRequestError, "Computed field 'newField' must have at least one dependency.") end it 'registerComputed should throw if defining a field with polymorphic dependencies' do @@ -118,10 +118,7 @@ module Computed values: proc { |records| records } ) ) - end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, - 'Dependencies over a polymorphic relations(address.addressable) are forbidden' - ) + end.to raise_error(ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Dependencies over a polymorphic relations(address.addressable) are forbidden') end it 'registerComputed should throw if defining a field with missing dependencies' do diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/operators_emulate/operators_emulate_collection_decorator_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/operators_emulate/operators_emulate_collection_decorator_spec.rb index e995f39ad..14af83002 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/operators_emulate/operators_emulate_collection_decorator_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/operators_emulate/operators_emulate_collection_decorator_spec.rb @@ -38,8 +38,8 @@ module OperatorsEmulate expect do @decorated_book.emulate_field_operator('title', Operators::GREATER_THAN) end.to raise_error( - Exceptions::ForestException, - 'Cannot override operators on collection book: ' \ + ForestAdminAgent::Http::Exceptions::UnprocessableError, + 'Cannot override operators on collection title: ' \ "the primary key columns must support 'Equal' and 'In' operators." ) end @@ -109,7 +109,7 @@ module OperatorsEmulate it 'raise if the field is in a relation' do expect do @decorated_book.emulate_field_operator('author:first_name', Operators::EQUAL) - end.to raise_error(Exceptions::ForestException, 'Cannot replace operator for relation') + end.to raise_error(ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Cannot replace operator for relation') end describe 'when implementing an operator from an unsupported one' do @@ -125,7 +125,7 @@ module OperatorsEmulate expect do @decorated_book.list(caller, filter, projection) - end.to raise_error(Exceptions::ForestException, "The given operator 'like' is not supported by the column: 'title'. The column is not filterable") + end.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ValidationError, "The given operator 'like' is not supported by the column: 'title'. The column is not filterable") end end @@ -146,7 +146,7 @@ module OperatorsEmulate expect do @decorated_book.list(caller, filter, projection) - end.to raise_error(Exceptions::ForestException, 'Operator replacement cycle: book.title[starts_with] -> book.title[like]') + end.to raise_error(ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Operator replacement cycle: book.title[starts_with] -> book.title[like]') end end diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/publication/publication_collection_decorator_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/publication/publication_collection_decorator_spec.rb index d9436d1ed..0aa2f9f0f 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/publication/publication_collection_decorator_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/publication/publication_collection_decorator_spec.rb @@ -105,27 +105,27 @@ module Publication end it 'throws when hiding a field which does not exists' do - expect { @decorated_person.change_field_visibility('unknown', false) }.to raise_error(ForestException, "No such field 'unknown'") + expect { @decorated_person.change_field_visibility('unknown', false) }.to raise_error(ForestAdminAgent::Http::Exceptions::NotFoundError, "No such field 'unknown'") end it 'raise when hiding a field referenced in a polymorphic relation' do expect do @decorated_comment.change_field_visibility('commentable_id', false) end.to raise_error( - ForestException, + ForestAdminAgent::Http::Exceptions::UnprocessableError, "Cannot remove field 'comment.commentable_id', because it's implied in a polymorphic relation 'comment.commentable'" ) expect do @decorated_comment.change_field_visibility('commentable_type', false) end.to raise_error( - ForestException, + ForestAdminAgent::Http::Exceptions::UnprocessableError, "Cannot remove field 'comment.commentable_type', because it's implied in a polymorphic relation 'comment.commentable'" ) end it 'throws when hiding the primary key' do - expect { @decorated_person.change_field_visibility('id', false) }.to raise_error(ForestException, 'Cannot hide primary key') + expect { @decorated_person.change_field_visibility('id', false) }.to raise_error(ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Cannot hide primary key') end it 'the schema should be the same when doing nothing' do diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/publication/publication_datasource_decorator_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/publication/publication_datasource_decorator_spec.rb index 24f5220c2..cda56f202 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/publication/publication_datasource_decorator_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/publication/publication_datasource_decorator_spec.rb @@ -129,13 +129,13 @@ module Publication context 'when keep_collections_matching is called' do it 'throws an error if a name is unknown' do - expect { @datasource_decorator.keep_collections_matching(['unknown']) }.to raise_error(ForestException, 'Collection unknown not found.') - expect { @datasource_decorator.keep_collections_matching(nil, ['unknown']) }.to raise_error(ForestException, 'Collection unknown not found.') + expect { @datasource_decorator.keep_collections_matching(['unknown']) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Collection unknown not found.') + expect { @datasource_decorator.keep_collections_matching(nil, ['unknown']) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Collection unknown not found.') end it 'throws an error if a collection is a target of polymorphic ManyToOne' do expect { @datasource_decorator.keep_collections_matching(nil, ['user']) }.to raise_error( - ForestException, + ForestAdminAgent::Http::Exceptions::UnprocessableError, "Cannot remove user because it's a potential target of polymorphic relation address.addressable" ) end @@ -151,7 +151,7 @@ module Publication it 'is able to remove "book" collection' do @datasource_decorator.keep_collections_matching(nil, ['book']) - expect { @datasource_decorator.get_collection('book') }.to raise_error(ForestException, "Collection 'book' was removed.") + expect { @datasource_decorator.get_collection('book') }.to raise_error(ForestAdminAgent::Http::Exceptions::NotFoundError, "Collection 'book' was removed.") expect(@datasource_decorator.get_collection('library_book').schema[:fields]).not_to have_key('my_book') expect(@datasource_decorator.get_collection('library').schema[:fields]).not_to have_key('my_books') end diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/relation/relation_collection_decorator_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/relation/relation_collection_decorator_spec.rb index 8c21dc7b2..f24735430 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/relation/relation_collection_decorator_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/relation/relation_collection_decorator_spec.rb @@ -123,7 +123,7 @@ module Relation foreign_collection: 'passport', origin_key: '__nonExisting__' }) - end.to raise_error(ForestException, "Column not found: 'passport.__nonExisting__'") + end.to raise_error(ForestAdminAgent::Http::Exceptions::NotFoundError, "Column not found: 'passport.__nonExisting__'") end end @@ -135,7 +135,7 @@ module Relation foreign_collection: 'passport', origin_key: 'picture_id' }) - end.to raise_error(ForestException, "Column does not support the In operator: 'passport.picture_id'") + end.to raise_error(ForestAdminAgent::Http::Exceptions::UnprocessableError, "Column does not support the In operator: 'passport.picture_id'") end end @@ -147,7 +147,7 @@ module Relation origin_key: 'owner_id', origin_key_target: 'name' }) - end.to raise_error(ForestException, "Types from 'passport.owner_id' and 'person.name' do not match.") + end.to raise_error(ForestAdminAgent::Http::Exceptions::UnprocessableError, "Types from 'passport.owner_id' and 'person.name' do not match.") end context 'when there is a given originKeyTarget' do @@ -185,7 +185,7 @@ module Relation origin_key: 'owner_id', origin_key_target: 'name' }) - end.to raise_error(ForestException, "Types from 'passport.owner_id' and 'person.name' do not match.") + end.to raise_error(ForestAdminAgent::Http::Exceptions::UnprocessableError, "Types from 'passport.owner_id' and 'person.name' do not match.") end context 'when there is a given originKeyTarget' do @@ -223,7 +223,7 @@ module Relation foreign_collection: '__nonExisting__', foreign_key: 'owner_id' }) - end.to raise_error(ForestException, 'Collection __nonExisting__ not found.') + end.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Collection __nonExisting__ not found.') end it 'throws with a non existent fk' do @@ -233,7 +233,7 @@ module Relation foreign_collection: 'person', foreign_key: '__nonExisting__' }) - end.to raise_error(ForestException, "Column not found: 'passport.__nonExisting__'") + end.to raise_error(ForestAdminAgent::Http::Exceptions::NotFoundError, "Column not found: 'passport.__nonExisting__'") end end @@ -245,7 +245,7 @@ module Relation foreign_collection: 'person', foreign_key: 'picture_id' }) - end.to raise_error(ForestException, "Column does not support the In operator: 'passport.picture_id'") + end.to raise_error(ForestAdminAgent::Http::Exceptions::UnprocessableError, "Column does not support the In operator: 'passport.picture_id'") end end @@ -286,7 +286,7 @@ module Relation origin_key: 'owner_id', through_collection: '__nonExisting__' }) - end.to raise_error(ForestException, 'Collection __nonExisting__ not found.') + end.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Collection __nonExisting__ not found.') end it 'throws with a non existent originKey' do @@ -298,7 +298,7 @@ module Relation origin_key: '__nonExisting__', through_collection: 'passport' }) - end.to raise_error(ForestException, "Column not found: 'passport.__nonExisting__'") + end.to raise_error(ForestAdminAgent::Http::Exceptions::NotFoundError, "Column not found: 'passport.__nonExisting__'") end it 'throws with a non existent fk' do @@ -310,7 +310,7 @@ module Relation origin_key: 'owner_id', through_collection: 'passport' }) - end.to raise_error(ForestException, "Column not found: 'passport.__nonExisting__'") + end.to raise_error(ForestAdminAgent::Http::Exceptions::NotFoundError, "Column not found: 'passport.__nonExisting__'") end end @@ -325,7 +325,7 @@ module Relation through_collection: 'passport', origin_key_target: 'name' }) - end.to raise_error(ForestException, "Types from 'passport.owner_id' and 'person.name' do not match.") + end.to raise_error(ForestAdminAgent::Http::Exceptions::UnprocessableError, "Types from 'passport.owner_id' and 'person.name' do not match.") end end diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/rename_collection/rename_collection_datasource_decorator_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/rename_collection/rename_collection_datasource_decorator_spec.rb index 5e25e2d06..e713d6519 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/rename_collection/rename_collection_datasource_decorator_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/rename_collection/rename_collection_datasource_decorator_spec.rb @@ -33,7 +33,7 @@ module RenameCollection expect(decorated_datasource.get_collection('new_name')).not_to be_nil expect do decorated_datasource.get_collection('foo') - end.to raise_error(Exceptions::ForestException) + end.to raise_error(ForestAdminAgent::Http::Exceptions::NotFoundError) end context 'with ManyToMany relation' do @@ -96,7 +96,7 @@ module RenameCollection expect do @datasource.rename_collection('library_book', 'book') end.to raise_error( - Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::ConflictError, "The given new collection name 'book' is already defined" ) end @@ -105,19 +105,13 @@ module RenameCollection @datasource.rename_collection('book', 'book2') expect do @datasource.rename_collection('book2', 'book3') - end.to raise_error( - Exceptions::ForestException, - 'Cannot rename a collection twice: book->book2->book3' - ) + end.to raise_error(ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Cannot rename a collection twice: book->book2->book3') end it 'raise an error if the given old name does not exist' do expect do @datasource.rename_collection('doesNotExist', 'book') - end.to raise_error( - Exceptions::ForestException, - 'Collection doesNotExist not found.' - ) + end.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Collection doesNotExist not found.') end it 'change the foreign collection when it is a many to many' do @@ -167,10 +161,7 @@ module RenameCollection it 'raise an error if the argument is not a function or a hash' do expect do @datasource.rename_collections('not a function') - end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, - 'Invalid argument for rename_collections, must be a function or a hash' - ) + end.to raise_error(ForestAdminAgent::Http::Exceptions::BadRequestError, 'Invalid argument for rename_collections, must be a function or a hash') end end end @@ -299,7 +290,7 @@ module RenameCollection describe 'rename_collection' do it 'raise an error when collection has polymorphic relation' do expect { @datasource.rename_collection('user', 'renamed_user') }.to raise_error( - Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::UnprocessableError, "Cannot rename collection user because it's a target of a polymorphic relation 'address.addressable'" ) end diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/rename_field/rename_field_collection_decorator_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/rename_field/rename_field_collection_decorator_spec.rb index 5359f061e..3e524573b 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/rename_field/rename_field_collection_decorator_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/rename_field/rename_field_collection_decorator_spec.rb @@ -121,21 +121,21 @@ module RenameField it 'raise an error when renaming a field which does not exists' do expect do @new_person.rename_field('unknown', 'somethingnew') - end.to raise_error(Exceptions::ForestException, "No such field 'unknown'") + end.to raise_error(ForestAdminAgent::Http::Exceptions::NotFoundError, "No such field 'unknown'") end it 'raise if renaming a field referenced in a polymorphic relation' do expect do @new_comment.rename_field('commentable_id', 'somethingnew') end.to raise_error( - Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::UnprocessableError, "Cannot rename 'comment.commentable_id', because it's implied in a polymorphic relation 'comment.commentable'" ) expect do @new_comment.rename_field('commentable_type', 'somethingnew') end.to raise_error( - Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::UnprocessableError, "Cannot rename 'comment.commentable_type', because it's implied in a polymorphic relation 'comment.commentable'" ) end @@ -145,7 +145,7 @@ module RenameField expect do @new_person.rename_field('id', 'primaryKey') - end.to raise_error(Exceptions::ForestException, "No such field 'id'") + end.to raise_error(ForestAdminAgent::Http::Exceptions::NotFoundError, "No such field 'id'") end it 'raise an error when renaming with a name including space' do diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator_spec.rb index 84af63f63..ff4394c5e 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/segment/segment_collection_decorator_spec.rb @@ -88,7 +88,7 @@ module Segment expect do @decorated_collection.refine_filter(caller, Filter.new(segment: 'segment_name')) - end.to raise_error(Exceptions::ForestException, 'Column not found book.do not exists') + end.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Column not found book.do not exists') end end end diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/sort/sort_collection_decorator_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/sort/sort_collection_decorator_spec.rb index b7d639a3e..9cd5018ce 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/sort/sort_collection_decorator_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/sort/sort_collection_decorator_spec.rb @@ -102,7 +102,7 @@ module Sort end it 'replace_field_sorting() should throw if no equivalent_sort is provided' do - expect { @decorated_book.replace_field_sorting('author_id', nil) }.to raise_error(ForestException, 'A new sorting method should be provided to replace field sorting') + expect { @decorated_book.replace_field_sorting('author_id', nil) }.to raise_error(ForestAdminAgent::Http::Exceptions::BadRequestError, 'A new sorting method should be provided to replace field sorting') end context 'when emulating sort on book.title (no relations)' do diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/validation/validation_collection_decorator_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/validation/validation_collection_decorator_spec.rb index cd6968c31..c3be0a35b 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/validation/validation_collection_decorator_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/validation/validation_collection_decorator_spec.rb @@ -60,19 +60,19 @@ module Validation end it 'addValidation() should throw if the field does not exists' do - expect { @decorated_book.add_validation('__dontExist', { operator: Operators::PRESENT }) }.to raise_error(ValidationError, "Column not found: 'book.__dontExist'") + expect { @decorated_book.add_validation('__dontExist', { operator: Operators::PRESENT }) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ValidationError, "Column not found: 'book.__dontExist'") end it 'addValidation() should throw if the field is readonly' do - expect { @decorated_book.add_validation('id', { operator: Operators::PRESENT }) }.to raise_error(ForestException, 'Cannot add validators on a readonly field') + expect { @decorated_book.add_validation('id', { operator: Operators::PRESENT }) }.to raise_error(ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Cannot add validators on a readonly field') end it 'addValidation() should throw if the field is a relation' do - expect { @decorated_book.add_validation('author', { operator: Operators::PRESENT }) }.to raise_error(ValidationError, "Unexpected field type: 'book.author' (found 'ManyToOne' expected 'Column')") + expect { @decorated_book.add_validation('author', { operator: Operators::PRESENT }) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ValidationError, "Unexpected field type: 'book.author' (found 'ManyToOne' expected 'Column')") end it 'addValidation() should throw if the field is in a relation' do - expect { @decorated_book.add_validation('author:first_name', { operator: Operators::PRESENT }) }.to raise_error(ForestException, 'Cannot add validators on a relation, use the foreign key instead') + expect { @decorated_book.add_validation('author:first_name', { operator: Operators::PRESENT }) }.to raise_error(ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Cannot add validators on a relation, use the foreign key instead') end context 'with field selection when validating' do @@ -84,7 +84,7 @@ module Validation it 'validates all fields when creating a record' do allow(@collection_book).to receive(:create).and_return(nil) - expect { @decorated_book.create(caller, [{ 'title' => 'longtitle', 'sub_title' => '' }]) }.to raise_error(ValidationError, 'sub_title failed validation rule : longer_than(5)') + expect { @decorated_book.create(caller, [{ 'title' => 'longtitle', 'sub_title' => '' }]) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ValidationError, 'sub_title failed validation rule : longer_than(5)') end it 'validates only changed fields when updating' do @@ -129,13 +129,13 @@ module Validation it 'rejects create that do not respect the rule' do allow(@collection_book).to receive(:create).and_return(nil) - expect { @decorated_book.create(caller, [{ 'title' => '1234' }]) }.to raise_error(ValidationError, 'title failed validation rule : longer_than(5)') + expect { @decorated_book.create(caller, [{ 'title' => '1234' }]) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ValidationError, 'title failed validation rule : longer_than(5)') end it 'rejects updates that do not respect the rule' do allow(@collection_book).to receive(:update).and_return(true) - expect { @decorated_book.update(caller, Filter.new, { 'title' => '1234' }) }.to raise_error(ValidationError, 'title failed validation rule : longer_than(5)') + expect { @decorated_book.update(caller, Filter.new, { 'title' => '1234' }) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ValidationError, 'title failed validation rule : longer_than(5)') end end end diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/write/write_replace/collection_basics_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/write/write_replace/collection_basics_spec.rb index b5d374978..a4ee762a1 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/write/write_replace/collection_basics_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/write/write_replace/collection_basics_spec.rb @@ -34,7 +34,7 @@ module WriteReplace @decorated_book.replace_field_writing('__dontExist') do {} end - end.to raise_error(ForestException, "Column not found: 'book.__dontExist'") + end.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ValidationError, "Column not found: 'book.__dontExist'") end it 'marks fields as writable when handler is defined' do @@ -55,7 +55,7 @@ module WriteReplace expect do @decorated_book.replace_field_writing('name') - end.to raise_error(ForestException, 'A new writing method should be provided to replace field writing') + end.to raise_error(ForestAdminAgent::Http::Exceptions::BadRequestError, 'A new writing method should be provided to replace field writing') end end end diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/write/write_replace/collection_simple_creation_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/write/write_replace/collection_simple_creation_spec.rb index c05029e20..4974f49e6 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/write/write_replace/collection_simple_creation_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/write/write_replace/collection_simple_creation_spec.rb @@ -125,7 +125,7 @@ module WriteReplace { 'price' => '456' } end - expect { @decorated_book.create(caller, { 'name' => 'a name', 'age' => 'an age' }) }.to raise_error(ForestException, 'Conflict value on the field price. It received several values.') + expect { @decorated_book.create(caller, { 'name' => 'a name', 'age' => 'an age' }) }.to raise_error(ForestAdminAgent::Http::Exceptions::ConflictError, 'Conflict value on the field price. It received several values.') end it 'when handlers call themselves recursively' do @@ -139,7 +139,7 @@ module WriteReplace { 'name' => 'some name' } end - expect { @decorated_book.create(caller, { 'name' => 'a name' }) }.to raise_error(ForestException, 'Conflict value on the field name. It received several values.') + expect { @decorated_book.create(caller, { 'name' => 'a name' }) }.to raise_error(ForestAdminAgent::Http::Exceptions::ConflictError, 'Conflict value on the field name. It received several values.') end it 'when the handler returns a unexpected type' do @@ -147,7 +147,7 @@ module WriteReplace 'RETURN_SHOULD_FAIL' end - expect { @decorated_book.create(caller, { 'age' => '10' }) }.to raise_error(ForestException, 'The write handler of age should return an Hash or nothing.') + expect { @decorated_book.create(caller, { 'age' => '10' }) }.to raise_error(ForestAdminAgent::Http::Exceptions::UnprocessableError, 'The write handler of age should return an Hash or nothing.') end it 'when the handler returns non existent fields' do @@ -155,7 +155,7 @@ module WriteReplace { 'author' => 'Asimov' } end - expect { @decorated_book.create(caller, { 'age' => '10' }) }.to raise_error(ForestException, "Unknown field: 'author'") + expect { @decorated_book.create(caller, { 'age' => '10' }) }.to raise_error(ForestAdminAgent::Http::Exceptions::NotFoundError, "Unknown field: 'author'") end it 'when the handler returns non existent relations' do @@ -163,7 +163,7 @@ module WriteReplace { 'author' => { 'lastname' => 'Asimov' } } end - expect { @decorated_book.create(caller, { 'age' => '10' }) }.to raise_error(ForestException, "Unknown field: 'author'") + expect { @decorated_book.create(caller, { 'age' => '10' }) }.to raise_error(ForestAdminAgent::Http::Exceptions::NotFoundError, "Unknown field: 'author'") end it 'if the customer attemps to update the patch in the handler' do diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/write/write_replace/collection_simple_update_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/write/write_replace/collection_simple_update_spec.rb index fbfee9c36..c7b314db2 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/write/write_replace/collection_simple_update_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/decorators/write/write_replace/collection_simple_update_spec.rb @@ -95,7 +95,7 @@ module WriteReplace { 'price' => '456' } end - expect { @decorated_book.update(caller, filter, { 'name' => 'a name', 'age' => 'an age' }) }.to raise_error(ForestException, 'Conflict value on the field price. It received several values.') + expect { @decorated_book.update(caller, filter, { 'name' => 'a name', 'age' => 'an age' }) }.to raise_error(ForestAdminAgent::Http::Exceptions::ConflictError, 'Conflict value on the field price. It received several values.') end it 'throws when handlers call themselves recursively' do @@ -111,7 +111,7 @@ module WriteReplace { 'name' => 'some name' } end - expect { @decorated_book.update(caller, filter, { 'name' => 'a name' }) }.to raise_error(ForestException, 'Conflict value on the field name. It received several values.') + expect { @decorated_book.update(caller, filter, { 'name' => 'a name' }) }.to raise_error(ForestAdminAgent::Http::Exceptions::ConflictError, 'Conflict value on the field name. It received several values.') end it 'throws when the handler returns a unexpected type' do @@ -119,7 +119,7 @@ module WriteReplace 'RETURN_SHOULD_FAIL' end - expect { @decorated_book.update(caller, filter, { 'age' => '10' }) }.to raise_error(ForestException, 'The write handler of age should return an Hash or nothing.') + expect { @decorated_book.update(caller, filter, { 'age' => '10' }) }.to raise_error(ForestAdminAgent::Http::Exceptions::UnprocessableError, 'The write handler of age should return an Hash or nothing.') end it 'throws when the handler returns non existent fields' do @@ -127,7 +127,7 @@ module WriteReplace { 'author' => 'Asimov' } end - expect { @decorated_book.update(caller, filter, { 'age' => '10' }) }.to raise_error(ForestException, "Unknown field: 'author'") + expect { @decorated_book.update(caller, filter, { 'age' => '10' }) }.to raise_error(ForestAdminAgent::Http::Exceptions::NotFoundError, "Unknown field: 'author'") end it 'throws when the handler returns non existent relations' do @@ -135,7 +135,7 @@ module WriteReplace { 'author' => { 'lastname' => 'Asimov' } } end - expect { @decorated_book.update(caller, filter, { 'age' => '10' }) }.to raise_error(ForestException, "Unknown field: 'author'") + expect { @decorated_book.update(caller, filter, { 'age' => '10' }) }.to raise_error(ForestAdminAgent::Http::Exceptions::NotFoundError, "Unknown field: 'author'") end it 'throws if the customer attemps to update the patch in the handler' do diff --git a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/plugins/add_external_spec.rb b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/plugins/add_external_spec.rb index 3f32d99cf..b0d904e35 100644 --- a/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/plugins/add_external_spec.rb +++ b/packages/forest_admin_datasource_customizer/spec/lib/forest_admin_datasource_customizer/plugins/add_external_spec.rb @@ -48,7 +48,7 @@ module Plugins it 'raises an error when options are missing required keys' do expect do plugin.run(nil, collection_customizer, { name: 'missing_schema' }) - end.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, 'The options parameter must contains the following keys: `name, schema, listRecords`') + end.to raise_error(ForestAdminAgent::Http::Exceptions::BadRequestError, 'The options parameter must contains the following keys: `name, schema, listRecords`') end end end diff --git a/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/datasource.rb b/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/datasource.rb index 8811caf18..11224380c 100644 --- a/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/datasource.rb +++ b/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/datasource.rb @@ -71,23 +71,20 @@ def check_as_fields(schema, prefix, local_as_fields) name = prefix ? "#{prefix}.#{field}" : field if !field.include?('.') && prefix - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, - "asFields contains '#{name}', which can't be flattened further because " \ - "asModels contains '#{prefix}', so it is already at the root of a collection." + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "asFields contains '#{name}', which can't be flattened further because " \ + "asModels contains '#{prefix}', so it is already at the root of a collection." end unless field.include?('.') - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, - "asFields contains '${name}', which can't be flattened because it is already at " \ - 'the root of the model.' + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "asFields contains '${name}', which can't be flattened because it is already at " \ + 'the root of the model.' end next unless contains_intermediary_array(local_schema, field) - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, - "asFields contains '${name}', " \ - "which can't be moved to the root of the model, because it is inside of an array. " \ - 'Either add all intermediary arrays to asModels, or remove it from asFields.' + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "asFields contains '${name}', " \ + "which can't be moved to the root of the model, because it is inside of an array. " \ + 'Either add all intermediary arrays to asModels, or remove it from asFields.' end end @@ -99,10 +96,9 @@ def check_as_models(schema, prefix, local_as_models) next unless contains_intermediary_array(local_schema, field) - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, - "asModels contains '#{name}', " \ - "which can't be transformed into a model, because it is inside of an array. " \ - 'Either add all intermediary arrays to asModels, or remove it from asModels.' + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "asModels contains '#{name}', " \ + "which can't be transformed into a model, because it is inside of an array. " \ + 'Either add all intermediary arrays to asModels, or remove it from asModels.' end end diff --git a/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/utils/pipeline/filter_generator.rb b/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/utils/pipeline/filter_generator.rb index 171306ca1..40c83f851 100644 --- a/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/utils/pipeline/filter_generator.rb +++ b/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/utils/pipeline/filter_generator.rb @@ -175,7 +175,7 @@ def self.build_match_condition(operator, value) when Operators::PRESENT { '$exists' => true, '$ne' => null } else - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, "Unsupported '#{operator}' operator" + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "Unsupported '#{operator}' operator" end end diff --git a/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/utils/pipeline/lookup_generator.rb b/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/utils/pipeline/lookup_generator.rb index be9620643..cb9108a12 100644 --- a/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/utils/pipeline/lookup_generator.rb +++ b/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/utils/pipeline/lookup_generator.rb @@ -89,7 +89,7 @@ def self.lookup_relation(current_path, schema_stack, name, projection, options) return lookup_projection(as, [*schema_stack, last_schema[name]], projection, options) if last_schema[name] # We should have handled all possible cases. - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, "Unexpected relation: '#{name}'" + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "Unexpected relation: '#{name}'" end end end diff --git a/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/utils/pipeline/virtual_field_generator.rb b/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/utils/pipeline/virtual_field_generator.rb index 20037b773..e9d7d372f 100644 --- a/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/utils/pipeline/virtual_field_generator.rb +++ b/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/utils/pipeline/virtual_field_generator.rb @@ -51,9 +51,10 @@ def self.get_path(field) # As this is a use case that never happens from the UI, and that can be worked around when # using the API, we decided to not implement it. - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Fetching virtual parent_id deeper than 1 level is not supported.' end + suffix = field[0...(field.length - parent_id_identifier.length)] return ConditionGenerator.tag_record_if_not_exist_by_value(suffix, '$_id') diff --git a/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb b/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb index 6952746d8..f75b777c4 100644 --- a/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb +++ b/packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/utils/schema/mongoid_schema.rb @@ -21,7 +21,7 @@ def schema_node end def schema_type - raise ForestException, 'Schema is not a leaf.' unless @is_leaf + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Schema is not a leaf.' unless @is_leaf @fields[:content] end @@ -100,7 +100,9 @@ def get_sub_schema(path) if child.is_a?(Hash) relation_name = @model.relations[prefix].class_name - raise ForestException, "Collection '#{relation_name}' not found." unless @models.key?(relation_name) + unless @models.key?(relation_name) + raise ForestAdminAgent::Http::Exceptions::NotFoundError, "Collection '#{relation_name}' not found." + end # Traverse arrays if child.is_a?(Hash) && child['[]'] @@ -114,7 +116,7 @@ def get_sub_schema(path) return MongoidSchema.new(@models[relation_name], child, is_array, is_leaf).get_sub_schema(suffix) elsif child.nil? - raise ForestException, "Field '#{prefix}' not found. Available fields are: #{list_fields}" + raise ForestAdminAgent::Http::Exceptions::NotFoundError, "Field '#{prefix}' not found. Available fields are: #{list_fields}" end # We ended up on a field => box it. @@ -127,7 +129,7 @@ def get_sub_schema(path) end def apply_stack(stack, skip_as_models: false) - raise ForestException, 'Stack can never be empty.' if stack.empty? + raise ForestAdminAgent::Http::Exceptions::BadRequestError, 'Stack can never be empty.' if stack.empty? step = stack.pop sub_schema = get_sub_schema(step[:prefix]) @@ -176,8 +178,10 @@ def apply_stack(stack, skip_as_models: false) # List leafs and arrays up to a certain level # Arrays are never traversed def list_fields(level = Float::INFINITY) - raise ForestException, 'Cannot list fields on a leaf schema.' if @is_leaf - raise ForestException, 'Level must be greater than 0.' if level.zero? + if @is_leaf + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Cannot list fields on a leaf schema.' + end + raise ForestAdminAgent::Http::Exceptions::BadRequestError, 'Level must be greater than 0.' if level.zero? return @fields.keys if level == 1 diff --git a/packages/forest_admin_datasource_mongoid/spec/lib/forest_admin_datasource_mongoid/datasource_spec.rb b/packages/forest_admin_datasource_mongoid/spec/lib/forest_admin_datasource_mongoid/datasource_spec.rb index d7c6798c4..60c9fe573 100644 --- a/packages/forest_admin_datasource_mongoid/spec/lib/forest_admin_datasource_mongoid/datasource_spec.rb +++ b/packages/forest_admin_datasource_mongoid/spec/lib/forest_admin_datasource_mongoid/datasource_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' module ForestAdminDatasourceMongoid - include ForestAdminDatasourceToolkit::Exceptions + include ForestAdminAgent::Http::Exceptions describe Datasource do let(:datasource) { described_class.new } @@ -37,7 +37,7 @@ module ForestAdminDatasourceMongoid logger = instance_double(Logger, log: nil) allow(ForestAdminAgent::Facades::Container).to receive(:logger).and_return(logger) expect { datasource.add_collection(Collection.new(datasource, Post, [{ prefix: nil, as_fields: [], as_models: [] }])) } - .to raise_error(ForestException, 'Collection Post already defined in datasource') + .to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Collection Post already defined in datasource') end describe 'with simple schema' do @@ -82,7 +82,7 @@ module ForestAdminDatasourceMongoid stub_const('Dummy::Book', class_book) allow(ObjectSpace).to receive(:each_object).and_return([Dummy::Book]) - expect { described_class.new }.to raise_error(ForestException, "Collection 'Author' not found.") + expect { described_class.new }.to raise_error(ForestAdminAgent::Http::Exceptions::NotFoundError, "Collection 'Author' not found.") end end end diff --git a/packages/forest_admin_datasource_mongoid/spec/lib/forest_admin_datasource_mongoid/utils/pipeline/lookup_generator_spec.rb b/packages/forest_admin_datasource_mongoid/spec/lib/forest_admin_datasource_mongoid/utils/pipeline/lookup_generator_spec.rb index e553a458b..b94619b22 100644 --- a/packages/forest_admin_datasource_mongoid/spec/lib/forest_admin_datasource_mongoid/utils/pipeline/lookup_generator_spec.rb +++ b/packages/forest_admin_datasource_mongoid/spec/lib/forest_admin_datasource_mongoid/utils/pipeline/lookup_generator_spec.rb @@ -12,7 +12,7 @@ module Pipeline projection = Projection.new(['myAuthor:firstname']) expect do described_class.lookup(Post, stack, projection, {}) - end.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, "Unexpected relation: 'myAuthor'") + end.to raise_error(ForestAdminAgent::Http::Exceptions::UnprocessableError, "Unexpected relation: 'myAuthor'") end it 'does nothing with projection that only contains columns' do diff --git a/packages/forest_admin_datasource_mongoid/spec/lib/forest_admin_datasource_mongoid/utils/pipeline/virtual_field_generator_spec.rb b/packages/forest_admin_datasource_mongoid/spec/lib/forest_admin_datasource_mongoid/utils/pipeline/virtual_field_generator_spec.rb index 194d019b5..1d4be2db5 100644 --- a/packages/forest_admin_datasource_mongoid/spec/lib/forest_admin_datasource_mongoid/utils/pipeline/virtual_field_generator_spec.rb +++ b/packages/forest_admin_datasource_mongoid/spec/lib/forest_admin_datasource_mongoid/utils/pipeline/virtual_field_generator_spec.rb @@ -88,7 +88,7 @@ module Pipeline stack = [{ prefix: nil, as_fields: [], as_models: ['author'] }] expect do described_class.add_virtual(Post, stack, projection) - end.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Fetching virtual parent_id deeper than 1 level is not supported.') + end.to raise_error(ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Fetching virtual parent_id deeper than 1 level is not supported.') end end end diff --git a/packages/forest_admin_datasource_rpc/lib/forest_admin_datasource_rpc/Utils/rpc_client.rb b/packages/forest_admin_datasource_rpc/lib/forest_admin_datasource_rpc/Utils/rpc_client.rb index c7a21f672..1fbdd8ae3 100644 --- a/packages/forest_admin_datasource_rpc/lib/forest_admin_datasource_rpc/Utils/rpc_client.rb +++ b/packages/forest_admin_datasource_rpc/lib/forest_admin_datasource_rpc/Utils/rpc_client.rb @@ -78,11 +78,9 @@ def raise_appropriate_error(response) if exception_class raise exception_class, message elsif status >= 500 - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, - "Server Error: #{message}" + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "Server Error: #{message}" else - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, - "RPC request failed: #{status} - #{message}" + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "RPC request failed: #{status} - #{message}" end end diff --git a/packages/forest_admin_datasource_rpc/spec/lib/forest_admin_datasource_rpc/utils/rpc_client_spec.rb b/packages/forest_admin_datasource_rpc/spec/lib/forest_admin_datasource_rpc/utils/rpc_client_spec.rb index 570e480f2..918b28a72 100644 --- a/packages/forest_admin_datasource_rpc/spec/lib/forest_admin_datasource_rpc/utils/rpc_client_spec.rb +++ b/packages/forest_admin_datasource_rpc/spec/lib/forest_admin_datasource_rpc/utils/rpc_client_spec.rb @@ -226,7 +226,7 @@ module Utils it 'raises ForestException with server error message' do expect { rpc_client.call_rpc(url, method: :get) }.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::UnprocessableError, /Server Error.*Something went wrong/ ) end @@ -245,7 +245,7 @@ module Utils it 'parses string error as message' do expect { rpc_client.call_rpc(url, method: :get) }.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::UnprocessableError, /Internal Server Error/ ) end @@ -283,7 +283,7 @@ module Utils it 'raises error with default message' do expect { rpc_client.call_rpc(url, method: :get) }.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::UnprocessableError, /Server Error.*Unknown error/ ) end diff --git a/packages/forest_admin_rails/app/controllers/forest_admin_rails/forest_controller.rb b/packages/forest_admin_rails/app/controllers/forest_admin_rails/forest_controller.rb index 3826f6b78..48c41cd92 100644 --- a/packages/forest_admin_rails/app/controllers/forest_admin_rails/forest_controller.rb +++ b/packages/forest_admin_rails/app/controllers/forest_admin_rails/forest_controller.rb @@ -61,7 +61,11 @@ def handle_streaming_response(data) end def exception_handler(exception) - http_status = get_error_status(exception) + # Translate BusinessError to HttpError and get all error information + error_info = translate_error(exception) + + # Add custom headers to the response + response.headers.merge!(error_info[:headers]) if error_info[:headers].any? data = case exception when ForestAdminAgent::Http::Exceptions::AuthenticationOpenIdClient, @@ -75,11 +79,12 @@ def exception_handler(exception) { errors: [ { - name: exception.respond_to?(:name) ? exception.name : exception.class.name, - detail: get_error_message(exception), - status: http_status, + name: error_info[:name], + detail: error_info[:message], + status: error_info[:status], + meta: error_info[:meta].presence, data: exception.try(:data) - } + }.compact ] } end @@ -88,7 +93,7 @@ def exception_handler(exception) ForestAdminAgent::Facades::Container.logger.log('Error', exception.full_message) end - render json: data, status: http_status + render json: data, status: error_info[:status] end end end diff --git a/packages/forest_admin_rails/lib/forest_admin_rails/engine.rb b/packages/forest_admin_rails/lib/forest_admin_rails/engine.rb index d5c24d897..5ea2bcbfd 100644 --- a/packages/forest_admin_rails/lib/forest_admin_rails/engine.rb +++ b/packages/forest_admin_rails/lib/forest_admin_rails/engine.rb @@ -33,12 +33,13 @@ class Engine < ::Rails::Engine end config.after_initialize do - Rails.error.handle(ForestAdminDatasourceToolkit::Exceptions::ForestException) do - agent_factory = ForestAdminAgent::Builder::AgentFactory.instance - agent_factory.setup(ForestAdminRails.config) - load_configuration - load_cors - end + agent_factory = ForestAdminAgent::Builder::AgentFactory.instance + agent_factory.setup(ForestAdminRails.config) + load_configuration + load_cors + rescue ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::BusinessError => e + Rails.error.report(e, handled: true, severity: :warning, context: {}) end def load_configuration diff --git a/packages/forest_admin_rails/spec/controllers/forest_admin_rails/forest_controller_spec.rb b/packages/forest_admin_rails/spec/controllers/forest_admin_rails/forest_controller_spec.rb index 6fef64ae0..2688cc3a8 100644 --- a/packages/forest_admin_rails/spec/controllers/forest_admin_rails/forest_controller_spec.rb +++ b/packages/forest_admin_rails/spec/controllers/forest_admin_rails/forest_controller_spec.rb @@ -101,7 +101,7 @@ module ForestAdminRails exception = ForestAdminAgent::Http::Exceptions::ValidationError.new('Email is invalid') # Mock get_error_message to work with real HttpException instances # Note: get_error_message has a bug where it checks error.ancestors instead of error.class.ancestors - allow(controller).to receive(:get_error_message).with(exception).and_return('Email is invalid') + allow(controller).to receive(:get_error_message).and_return('Email is invalid') controller.send(:exception_handler, exception) @@ -121,24 +121,24 @@ module ForestAdminRails controller.send(:exception_handler, exception) - expect(controller).to have_received(:get_error_message).with(exception) + expect(controller).to have_received(:get_error_message) end it 'supports custom name' do - exception = ForestAdminAgent::Http::Exceptions::ValidationError.new('Invalid data', 'CustomValidationError') + exception = ForestAdminAgent::Http::Exceptions::ValidationError.new('Invalid data') allow(controller).to receive(:get_error_message).and_return('Invalid data') controller.send(:exception_handler, exception) json = JSON.parse(response_mock.body) - expect(json['errors'][0]['name']).to eq('CustomValidationError') + expect(json['errors'][0]['name']).to eq('ValidationError') end end context 'with ForbiddenError' do it 'returns errors format with 403 status' do exception = ForestAdminAgent::Http::Exceptions::ForbiddenError.new('Access denied') - allow(controller).to receive(:get_error_message).with(exception).and_return('Access denied') + allow(controller).to receive(:get_error_message).and_return('Access denied') controller.send(:exception_handler, exception) @@ -153,7 +153,7 @@ module ForestAdminRails context 'with NotFoundError' do it 'returns errors format with 404 status' do exception = ForestAdminAgent::Http::Exceptions::NotFoundError.new('Resource not found') - allow(controller).to receive(:get_error_message).with(exception).and_return('Resource not found') + allow(controller).to receive(:get_error_message).and_return('Resource not found') controller.send(:exception_handler, exception) @@ -193,10 +193,12 @@ module ForestAdminRails allow(exception.class).to receive(:name).and_return('StandardError') allow(exception).to receive(:try).with(:status).and_return(nil) allow(exception).to receive(:try).with(:data).and_return(nil) + allow(exception).to receive(:is_a?).with(ForestAdminAgent::Http::Exceptions::HttpError).and_return(false) + allow(exception).to receive(:is_a?).with(ForestAdminAgent::Http::Exceptions::BusinessError).and_return(false) allow(exception).to receive(:is_a?).with(ForestAdminAgent::Http::Exceptions::AuthenticationOpenIdClient).and_return(false) allow(exception).to receive(:is_a?).with(OpenIDConnect::Exception).and_return(false) allow(exception).to receive(:full_message).and_return('Full error') - allow(controller).to receive(:get_error_message).with(exception).and_return('Unexpected error') + allow(controller).to receive(:get_error_message).and_return('Unexpected error') controller.send(:exception_handler, exception) @@ -220,10 +222,12 @@ module ForestAdminRails allow(exception.class).to receive(:name).and_return('StandardError') allow(exception).to receive(:try).with(:status).and_return(nil) allow(exception).to receive(:try).with(:data).and_return(nil) + allow(exception).to receive(:is_a?).with(ForestAdminAgent::Http::Exceptions::HttpError).and_return(false) + allow(exception).to receive(:is_a?).with(ForestAdminAgent::Http::Exceptions::BusinessError).and_return(false) allow(exception).to receive(:is_a?).with(ForestAdminAgent::Http::Exceptions::AuthenticationOpenIdClient).and_return(false) allow(exception).to receive(:is_a?).with(OpenIDConnect::Exception).and_return(false) allow(exception).to receive(:full_message).and_return('Full error') - allow(controller).to receive(:get_error_message).with(exception).and_return('Unexpected error') + allow(controller).to receive(:get_error_message).and_return('Unexpected error') controller.send(:exception_handler, exception) @@ -231,7 +235,7 @@ module ForestAdminRails json = JSON.parse(response_mock.body) expect(json['errors']).to be_an(Array) expect(json['errors'][0]['name']).to eq('StandardError') - expect(json['errors'][0]['status']).to be_nil + expect(json['errors'][0]['status']).to eq(500) end end @@ -248,10 +252,12 @@ module ForestAdminRails allow(exception.class).to receive(:name).and_return('StandardError') allow(exception).to receive(:try).with(:status).and_return(nil) allow(exception).to receive(:try).with(:data).and_return(nil) + allow(exception).to receive(:is_a?).with(ForestAdminAgent::Http::Exceptions::HttpError).and_return(false) + allow(exception).to receive(:is_a?).with(ForestAdminAgent::Http::Exceptions::BusinessError).and_return(false) allow(exception).to receive(:is_a?).with(ForestAdminAgent::Http::Exceptions::AuthenticationOpenIdClient).and_return(false) allow(exception).to receive(:is_a?).with(OpenIDConnect::Exception).and_return(false) allow(exception).to receive(:full_message).and_return('Full error') - allow(controller).to receive(:get_error_message).with(exception).and_return('Unexpected error') + allow(controller).to receive(:get_error_message).and_return('Unexpected error') controller.send(:exception_handler, exception) @@ -273,15 +279,17 @@ module ForestAdminRails allow(exception.class).to receive(:name).and_return('StandardError') allow(exception).to receive(:try).with(:status).and_return(nil) allow(exception).to receive(:try).with(:data).and_return(nil) + allow(exception).to receive(:is_a?).with(ForestAdminAgent::Http::Exceptions::HttpError).and_return(false) + allow(exception).to receive(:is_a?).with(ForestAdminAgent::Http::Exceptions::BusinessError).and_return(false) allow(exception).to receive(:is_a?).with(ForestAdminAgent::Http::Exceptions::AuthenticationOpenIdClient).and_return(false) allow(exception).to receive(:is_a?).with(OpenIDConnect::Exception).and_return(false) allow(exception).to receive(:full_message).and_return('Full error trace') - allow(controller).to receive(:get_error_message).with(exception).and_return('Unexpected error') + allow(controller).to receive(:get_error_message).and_return('Unexpected error') allow(ForestAdminAgent::Facades::Container).to receive(:cache).with(:is_production).and_return(false) controller.send(:exception_handler, exception) - expect(logger).to have_received(:log).with('Debug', 'Full error trace') + expect(logger).to have_received(:log).with('Error', 'Full error trace') end it 'does not log exception in production mode' do @@ -296,10 +304,12 @@ module ForestAdminRails allow(exception.class).to receive(:name).and_return('StandardError') allow(exception).to receive(:try).with(:status).and_return(nil) allow(exception).to receive(:try).with(:data).and_return(nil) + allow(exception).to receive(:is_a?).with(ForestAdminAgent::Http::Exceptions::HttpError).and_return(false) + allow(exception).to receive(:is_a?).with(ForestAdminAgent::Http::Exceptions::BusinessError).and_return(false) allow(exception).to receive(:is_a?).with(ForestAdminAgent::Http::Exceptions::AuthenticationOpenIdClient).and_return(false) allow(exception).to receive(:is_a?).with(OpenIDConnect::Exception).and_return(false) allow(exception).to receive(:full_message).and_return('Full error trace') - allow(controller).to receive(:get_error_message).with(exception).and_return('Unexpected error') + allow(controller).to receive(:get_error_message).and_return('Unexpected error') allow(ForestAdminAgent::Facades::Container).to receive(:cache).with(:is_production).and_return(true) controller.send(:exception_handler, exception) diff --git a/packages/forest_admin_rpc_agent/lib/forest_admin_rpc_agent/engine.rb b/packages/forest_admin_rpc_agent/lib/forest_admin_rpc_agent/engine.rb index 28f0a7b3d..81ace70bb 100644 --- a/packages/forest_admin_rpc_agent/lib/forest_admin_rpc_agent/engine.rb +++ b/packages/forest_admin_rpc_agent/lib/forest_admin_rpc_agent/engine.rb @@ -7,7 +7,8 @@ class Engine < ::Rails::Engine end config.after_initialize do - Rails.error.handle(ForestAdminDatasourceToolkit::Exceptions::ForestException) do + Rails.error.handle(ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::BusinessError) do agent = ForestAdminRpcAgent::Agent.instance agent.setup(ForestAdminRpcAgent.config)