From 5e34ca67817e4d57123f7dddcf44ed0869107462 Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Wed, 22 Oct 2025 18:24:41 +0200 Subject: [PATCH 01/21] fix: better error handling --- .../forest_admin_agent/auth/auth_manager.rb | 16 +- .../builder/agent_factory.rb | 14 +- .../http/Exceptions/README.md | 224 ++++++++++++++++++ .../http/Exceptions/business_error.rb | 113 +++++++++ .../http/Exceptions/conflict_error.rb | 2 +- .../http/Exceptions/http_error.rb | 41 ++++ .../http/Exceptions/validation_error.rb | 2 + .../forest_admin_agent/http/error_handling.rb | 71 +++++- .../http/error_translator.rb | 86 +++++++ .../http/forest_admin_api_requester.rb | 36 ++- .../routes/resources/native_query.rb | 7 +- .../routes/security/authentication.rb | 7 +- .../services/ip_whitelist.rb | 24 +- .../services/permissions.rb | 17 +- .../services/sse_cache_invalidation.rb | 9 +- .../forest_admin_agent/utils/caller_parser.rb | 12 +- .../utils/condition_tree_parser.rb | 6 +- .../utils/query_string_parser.rb | 20 +- .../utils/query_validator.rb | 28 ++- .../schema/generator_action_field_widget.rb | 6 +- .../forest_admin_rails/forest_controller.rb | 19 +- 21 files changed, 697 insertions(+), 63 deletions(-) create mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/README.md create mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/business_error.rb create mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/http_error.rb create mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/http/error_translator.rb 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..dd06e1c6e 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 @@ -14,10 +14,7 @@ def start(rendering_id) end def verify_code_and_generate_token(params) - unless params['state'] - raise ForestAdminAgent::Error, - ForestAdminAgent::Utils::ErrorMessages::INVALID_STATE_MISSING - end + raise ForestAdminAgent::Http::Exceptions::MissingParameterError, 'state' unless params['state'] if Facades::Container.cache(:debug) OpenIDConnect.http_config do |options| @@ -40,14 +37,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( + 'Invalid state: missing renderingId', + 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( + 'Invalid rendering ID: must be an integer', + 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/README.md b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/README.md new file mode 100644 index 000000000..a80067a15 --- /dev/null +++ b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/README.md @@ -0,0 +1,224 @@ +# Forest Admin Agent Ruby - Error Handling + +This document describes the improved error handling system in Forest Admin Agent Ruby, which mimics the error handling approach from agent-nodejs. + +## Overview + +The new error handling system provides: + +1. **Explicit error types** - Clear, specific error classes for different scenarios +2. **Proper HTTP status codes** - Automatic mapping of business errors to appropriate HTTP status codes +3. **Rich error information** - Support for error details, metadata, and custom headers +4. **Better debugging** - More informative error messages for developers + +## Error Hierarchy + +### BusinessError (Base Class) + +All business errors inherit from `BusinessError`, which provides: +- `message` - The error message +- `details` - A hash of additional details/metadata +- `cause` - The underlying cause (if any) +- `name` - The error class name + +### Common Error Classes + +The system provides error classes for all standard HTTP error codes: + +#### 4xx Client Errors + +- `BadRequestError` (400) - Invalid request data +- `UnauthorizedError` (401) - Authentication failed +- `PaymentRequiredError` (402) - Payment required +- `ForbiddenError` (403) - Insufficient permissions +- `NotFoundError` (404) - Resource not found +- `ConflictError` (409) - Resource conflict +- `ContentTooLargeError` (413) - Request payload too large +- `UnprocessableEntityError` (422) - Validation failed +- `TooManyRequestsError` (429) - Rate limit exceeded + +#### 5xx Server Errors + +- `InternalServerError` (500) - Unexpected server error +- `BadGatewayError` (502) - Bad gateway +- `ServiceUnavailableError` (503) - Service temporarily unavailable + +### Specialized Error Classes + +#### Authentication Errors (all 401) + +- `InvalidApplicationTokenError` +- `InvalidCredentialsError` +- `InvalidRecoveryCodeError` +- `InvalidRefreshTokenError` +- `InvalidSessionHashError` +- `InvalidTimeBasedPasswordError` +- `InvalidTokenError` +- `InvalidUserError` +- `MissingTokenError` +- `PasswordLoginUnavailableError` +- `TwoFactorAuthenticationRequiredError` + +#### Authorization Errors (all 403) + +- `TwoFactorAuthenticationRequiredForbiddenError` + +#### Extended Common Errors + +- `EntityNotFoundError` - Extends `NotFoundError` with entity name support +- `MissingParameterError` - Extends `BadRequestError` for missing parameters +- `ValidationFailedError` - Extends `BadRequestError` for validation errors + +## Usage Examples + +### Basic Error Raising + +```ruby +# Raise a simple forbidden error +raise ForestAdminAgent::Http::Exceptions::ForbiddenError.new( + "You don't have permission to access this resource" +) + +# Raise a not found error with details +raise ForestAdminAgent::Http::Exceptions::EntityNotFoundError.new( + 'User', + details: { user_id: 123 } +) + +# Raise an authentication error +raise ForestAdminAgent::Http::Exceptions::InvalidTokenError.new + +# Raise a validation error with details +raise ForestAdminAgent::Http::Exceptions::ValidationFailedError.new( + 'Validation failed', + details: { + field: 'email', + error: 'Email is required' + } +) +``` + +### Error with Custom Details + +```ruby +raise ForestAdminAgent::Http::Exceptions::ConflictError.new( + 'Organization', + 'An organization with this name already exists', + details: { + organization_name: 'Acme Corp', + suggestion: 'Try a different name' + } +) +``` + +### Error with Cause + +```ruby +begin + external_service.call +rescue StandardError => e + raise ForestAdminAgent::Http::Exceptions::ServiceUnavailableError.new( + 'External service is unavailable', + details: { service: 'external_api' }, + cause: e + ) +end +``` + +### Rate Limiting Error + +```ruby +raise ForestAdminAgent::Http::Exceptions::TooManyRequestsError.new( + 'Too many requests', + 60, # retry_after in seconds + details: { limit: 100, period: '1 minute' } +) +``` + +## Error Response Format + +Errors are automatically converted to JSON responses with the following format: + +```json +{ + "errors": [ + { + "name": "ForbiddenError", + "detail": "You don't have permission to access this resource", + "status": 403, + "meta": { + "resource": "users", + "action": "delete" + } + } + ] +} +``` + +## Custom Headers + +Some errors automatically add custom headers to the response: + +- `NotFoundError` adds `x-error-type: object-not-found` +- `TooManyRequestsError` adds `Retry-After: ` + +## Backward Compatibility + +The new system is backward compatible with existing code: + +- Old `HttpException`-based errors still work +- Legacy error signatures are supported +- The error handling middleware automatically translates both old and new errors + +## Migration Guide + +To migrate existing code to use the new error system: + +### Before + +```ruby +raise ForbiddenError.new("Access denied", "ForbiddenError") +``` + +### After + +```ruby +raise ForestAdminAgent::Http::Exceptions::ForbiddenError.new( + "Access denied" +) +``` + +### Before + +```ruby +raise StandardError, "Something went wrong" +``` + +### After + +```ruby +raise ForestAdminAgent::Http::Exceptions::InternalServerError.new( + "Something went wrong", + details: { context: 'additional info' } +) +``` + +## Benefits + +1. **Type Safety** - Specific error classes make it clear what error is being raised +2. **Automatic HTTP Mapping** - No need to manually set status codes +3. **Rich Debugging Info** - Details field provides context without exposing sensitive data +4. **Consistent API** - All errors follow the same pattern +5. **Better Testing** - Easy to test specific error types + +## Implementation Details + +The error handling system consists of: + +1. **BusinessError classes** (`business_error.rb`) - Base error classes +2. **HttpError wrapper** (`http_error.rb`) - Wraps business errors with HTTP-specific properties +3. **ErrorTranslator** (`error_translator.rb`) - Maps business errors to HTTP status codes +4. **ErrorHandling module** (`error_handling.rb`) - Helper methods for error responses +5. **Rails Controller** - Automatically catches and formats errors + +All errors are automatically caught by the `ForestController` and converted to appropriate JSON responses. 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..c86d0158d --- /dev/null +++ b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/business_error.rb @@ -0,0 +1,113 @@ +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 + + # ==================== + # Lowest level errors + # ==================== + # Note: Some error classes (ForbiddenError, NotFoundError, ConflictError, UnprocessableError, ValidationError) + # are defined in their own files for backward compatibility with HttpException + + class UnavailableForLegalReasonsError < BusinessError + def initialize(message = 'Unavailable for legal reasons', details: {}) + super + end + end + + class BadRequestError < BusinessError + def initialize(message = 'Bad request', details: {}) + super + end + end + + class ContentTooLargeError < BusinessError + def initialize(message = 'Content too large', details: {}) + super + end + end + + class FailedDependencyError < BusinessError + def initialize(message = 'Failed dependency', details: {}) + super + end + end + + class TooEarlyError < BusinessError + def initialize(message = 'Too early', details: {}) + super + end + end + + class InternalServerError < BusinessError + def initialize(message = 'Internal server error', details: {}, cause: nil) + super + end + end + + class PaymentRequiredError < BusinessError + def initialize(message = 'Payment required', details: {}) + 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 + + class TooManyRequestsError < BusinessError + attr_reader :retry_after + + def initialize(message, retry_after, details: {}) + super(message, details: details) + @retry_after = retry_after + end + end + + class UnauthorizedError < BusinessError + def initialize(message = 'Unauthorized', details: {}) + super + end + end + + # =================================================== + # Extending lowest level errors, but still low level + # =================================================== + + class MissingParameterError < BadRequestError + def initialize(parameter_name, details: {}) + super("Missing parameter #{parameter_name}", details: details) + end + end + + class ValidationFailedError < BadRequestError + def initialize(message = 'Validation failed', 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 index 4b3335e88..6883f0581 100644 --- 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 @@ -5,7 +5,7 @@ class ConflictError < HttpException attr_reader :name def initialize(message, name = 'ConflictError') - super(429, message, name) + super(409, message, name) 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/validation_error.rb b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/validation_error.rb index e9f0d7124..f3668cef4 100644 --- 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 @@ -1,3 +1,5 @@ +require_relative 'http_exception' + module ForestAdminAgent module Http module Exceptions 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..5274ab92e 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,91 @@ +require_relative 'error_translator' +require_relative 'exceptions/business_error' +require_relative 'exceptions/http_error' +require_relative 'exceptions/http_exception' +require_relative 'exceptions/validation_error' + 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) + # Handle HttpError instances + 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.is_a?(ForestAdminAgent::Http::Exceptions::HttpError) + return error.status if error.respond_to?(:status) && error.status - return 400 if error.is_a?(ForestAdminDatasourceToolkit::Exceptions::ValidationError) + if error.is_a?(ForestAdminAgent::Http::Exceptions::BusinessError) + http_error = ForestAdminAgent::Http::ErrorTranslator.translate(error) + return http_error.status if http_error + end 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 + # This is just a convenient wrapper around ErrorTranslator.translate + # @param error [Exception] The error to translate + # @return [HttpError, HttpException, Exception] The translated error or the original error + def translate_error(error) + # Delegate all translation logic to ErrorTranslator + ForestAdminAgent::Http::ErrorTranslator.translate(error) + 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..e90830c62 --- /dev/null +++ b/packages/forest_admin_agent/lib/forest_admin_agent/http/error_translator.rb @@ -0,0 +1,86 @@ +require_relative 'exceptions/business_error' +require_relative 'exceptions/http_error' +require_relative 'exceptions/validation_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_401 = Exceptions::HttpErrorFactory.create_for_business_error(401, 'Unauthorized') + ERROR_402 = Exceptions::HttpErrorFactory.create_for_business_error(402, 'Payment Required') + 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_413 = Exceptions::HttpErrorFactory.create_for_business_error(413, 'Content too large') + ERROR_422 = Exceptions::HttpErrorFactory.create_for_business_error(422, 'Unprocessable Entity') + ERROR_424 = Exceptions::HttpErrorFactory.create_for_business_error(424, 'Failed Dependency') + ERROR_425 = Exceptions::HttpErrorFactory.create_for_business_error(425, 'Too Early') + ERROR_429 = Exceptions::HttpErrorFactory.create_for_business_error(429, 'Too Many Requests', { + custom_headers: lambda { |error| + { 'Retry-After' => error.retry_after.to_s } + } + }) + ERROR_451 = Exceptions::HttpErrorFactory.create_for_business_error(451, 'Unavailable For Legal Reasons') + 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 ValidationError + if defined?(ForestAdminDatasourceToolkit::Exceptions::ValidationError) && + error.is_a?(ForestAdminDatasourceToolkit::Exceptions::ValidationError) + return Exceptions::BadRequestError.new(error) + end + + # Translate BusinessError to HttpError with appropriate status code + case error + when Exceptions::BadRequestError + ERROR_400.new(error) + when Exceptions::UnauthorizedError + ERROR_401.new(error) + when Exceptions::PaymentRequiredError + ERROR_402.new(error) + when Exceptions::ContentTooLargeError + ERROR_413.new(error) + when Exceptions::FailedDependencyError + ERROR_424.new(error) + when Exceptions::TooEarlyError + ERROR_425.new(error) + when Exceptions::TooManyRequestsError + ERROR_429.new(error) + when Exceptions::UnavailableForLegalReasonsError + ERROR_451.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..522476743 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,6 +3,7 @@ module ForestAdminAgent module Http class ForestAdminApiRequester + include ForestAdminAgent::Http::Exceptions include ForestAdminDatasourceToolkit::Exceptions def initialize @@ -28,29 +29,46 @@ def post(url, params = nil) end def handle_response_error(error) + # Re-raise if it's already a BusinessError + raise error if error.is_a?(ForestAdminAgent::Http::Exceptions::BusinessError) raise error if error.is_a?(ForestException) 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/resources/native_query.rb b/packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/native_query.rb index 6534adf5e..5b227bed6 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 @@ -60,8 +61,10 @@ def type=(type) end def raise_error(result, key_names) - raise ForestException, - "The result columns must be named #{key_names} instead of '#{result.keys.join("', '")}'" + raise BadRequestError.new( + 'Invalid native query result columns', + details: { expected: key_names, actual: result.keys.join(', ') } + ) end def make_value(result) 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..a2701abbd 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 @@ -79,15 +79,12 @@ def auth protected def get_and_check_rendering_id(params) - unless params['renderingId'] - raise ForestAdminAgent::Error, - ForestAdminAgent::Utils::ErrorMessages::MISSING_RENDERING_ID - end + raise MissingParameterError, 'renderingId' unless params['renderingId'] begin Integer(params['renderingId']) rescue ArgumentError - raise ForestAdminAgent::Error, ForestAdminAgent::Utils::ErrorMessages::INVALID_RENDERING_ID + raise ValidationFailedError, 'Invalid rendering ID: must be an integer' 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..1cf0d7421 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 @@ -2,7 +2,7 @@ module ForestAdminAgent module Services - class IpWhitelist + class IpWhitelist # rubocop:disable Metrics/ClassLength RULE_MATCH_IP = 0 RULE_MATCH_RANGE = 1 RULE_MATCH_SUBNET = 2 @@ -40,7 +40,10 @@ 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.new( + 'Invalid IP whitelist rule type', + details: { rule_type: rule['type'], rule: rule } + ) end end @@ -93,7 +96,10 @@ def fetch_rules status: response.status, response: response.body }) - raise ForestAdminAgent::Error, ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED + raise ForestAdminAgent::Http::Exceptions::InternalServerError.new( + 'Failed to fetch IP whitelist rules', + details: { status: response.status, body: response.body } + ) end begin @@ -104,7 +110,11 @@ def fetch_rules status: response.status, response: response.body }) - raise ForestAdminAgent::Error, ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED + raise ForestAdminAgent::Http::Exceptions::InternalServerError.new( + 'Failed to parse IP whitelist rules response', + details: { error: e.message, body: response.body }, + cause: e + ) end ip_whitelist_data = body['data']['attributes'] @@ -117,7 +127,11 @@ def fetch_rules status: response&.status, response: response&.body }) - raise ForestAdminAgent::Error, ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED + raise ForestAdminAgent::Http::Exceptions::InternalServerError.new( + 'Unexpected error fetching IP whitelist rules', + details: { error: e.message, status: response&.status }, + cause: e + ) 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..35c7be93d 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,12 @@ 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? + if action.nil? + raise NotFoundError.new( + 'Smart action not found', + details: { collection: collection_name, endpoint: endpoint, http_method: http_method } + ) + end action end @@ -277,7 +282,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 +296,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/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..4d8d3e941 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,15 @@ 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 MissingParameterError, 'timezone' unless timezone + + unless Time.find_zone(timezone) + raise BadRequestError.new( + 'Invalid timezone', + details: { timezone: timezone } + ) + end 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..1bb4ba444 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,6 +4,7 @@ module ForestAdminAgent module Utils class ConditionTreeParser + include ForestAdminAgent::Http::Exceptions include ForestAdminDatasourceToolkit::Exceptions include ForestAdminDatasourceToolkit::Utils include ForestAdminDatasourceToolkit::Components::Query::ConditionTree @@ -26,7 +27,10 @@ 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.new( + 'Failed to instantiate condition tree: invalid filter format', + details: { filters: filters } + ) end def self.parse_value(collection, leaf) 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..8e046b98a 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,10 @@ 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.new( + 'Invalid pagination parameters', + details: { limit: items_per_pages, page: page } + ) end offset = (page.to_i - 1) * items_per_pages.to_i @@ -107,7 +111,12 @@ 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? + if search && !collection.is_searchable? + raise BadRequestError.new( + 'Collection is not searchable', + details: { collection: collection.name } + ) + end search end @@ -148,7 +157,12 @@ def self.parse_segment(collection, args) return unless segment - raise ForestException, "Invalid segment: #{segment}" unless collection.schema[:segments].include?(segment) + unless collection.schema[:segments].include?(segment) + raise BadRequestError.new( + 'Invalid segment', + details: { segment: segment, available_segments: collection.schema[:segments] } + ) + end 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..160c4c435 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,6 +2,7 @@ module ForestAdminAgent module Utils module Schema class GeneratorActionFieldWidget + include ForestAdminAgent::Http::Exceptions include ForestAdminDatasourceToolkit::Exceptions def self.build_widget_options(field) @@ -43,7 +44,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', + details: { widget: field&.widget, field_type: field&.type } + ) end class << self 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..a0830fab9 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,17 @@ def handle_streaming_response(data) end def exception_handler(exception) - http_status = get_error_status(exception) + # Translate BusinessError to HttpError if applicable + translated_error = translate_error(exception) + + http_status = get_error_status(translated_error) + error_message = get_error_message(translated_error) + error_name = get_error_name(translated_error) + error_meta = get_error_meta(translated_error) + error_headers = get_error_headers(translated_error) + + # Add custom headers to the response + response.headers.merge!(error_headers) if error_headers.any? data = case exception when ForestAdminAgent::Http::Exceptions::AuthenticationOpenIdClient, @@ -75,11 +85,12 @@ def exception_handler(exception) { errors: [ { - name: exception.respond_to?(:name) ? exception.name : exception.class.name, - detail: get_error_message(exception), + name: error_name, + detail: error_message, status: http_status, + meta: error_meta.presence, data: exception.try(:data) - } + }.compact ] } end From 2c8256f9a385303bd75a0186fbf0d26c7f8addf6 Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Thu, 23 Oct 2025 08:30:00 +0200 Subject: [PATCH 02/21] fix: tests and error messages --- .../forest_admin_agent/auth/auth_manager.rb | 9 ++++-- .../http/Exceptions/README.md | 1 - .../http/Exceptions/business_error.rb | 14 ++++----- .../http/Exceptions/not_found_error.rb | 11 ------- .../forest_admin_agent/http/error_handling.rb | 12 ++++++++ .../routes/resources/native_query.rb | 6 ++-- .../routes/security/authentication.rb | 7 +++-- .../services/ip_whitelist.rb | 27 +++++------------ .../services/permissions.rb | 7 +---- .../forest_admin_agent/utils/caller_parser.rb | 9 ++---- .../utils/condition_tree_parser.rb | 5 +--- .../utils/query_string_parser.rb | 19 ++---------- .../builder/agent_factory_spec.rb | 2 +- .../http/forest_admin_api_requester_spec.rb | 12 ++++---- .../routes/resources/native_query_spec.rb | 10 +++---- .../routes/security/authentication_spec.rb | 4 +-- .../services/ip_whitelist_spec.rb | 10 +++---- .../services/permissions_spec.rb | 4 +-- .../utils/condition_tree_parser_spec.rb | 2 +- .../utils/query_string_parser_spec.rb | 30 +++++++++---------- .../utils/query_validator_spec.rb | 16 +++++----- 21 files changed, 90 insertions(+), 127 deletions(-) delete mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/not_found_error.rb 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 dd06e1c6e..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 @@ -14,7 +14,10 @@ def start(rendering_id) end def verify_code_and_generate_token(params) - raise ForestAdminAgent::Http::Exceptions::MissingParameterError, 'state' unless params['state'] + unless params['state'] + raise ForestAdminAgent::Http::Exceptions::BadRequestError, + ForestAdminAgent::Utils::ErrorMessages::INVALID_STATE_MISSING + end if Facades::Container.cache(:debug) OpenIDConnect.http_config do |options| @@ -38,7 +41,7 @@ def get_rendering_id_from_state(state) state = JSON.parse(state.tr("'", '"').gsub('=>', ':')) unless state.key? 'renderingId' raise ForestAdminAgent::Http::Exceptions::BadRequestError.new( - 'Invalid state: missing renderingId', + ForestAdminAgent::Utils::ErrorMessages::INVALID_STATE_RENDERING_ID, details: { state: state } ) end @@ -47,7 +50,7 @@ def get_rendering_id_from_state(state) Integer(state['renderingId']) rescue ArgumentError raise ForestAdminAgent::Http::Exceptions::ValidationFailedError.new( - 'Invalid rendering ID: must be an integer', + ForestAdminAgent::Utils::ErrorMessages::INVALID_RENDERING_ID, details: { renderingId: state['renderingId'] } ) end diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/README.md b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/README.md index a80067a15..b2d9e39d7 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/README.md +++ b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/README.md @@ -66,7 +66,6 @@ The system provides error classes for all standard HTTP error codes: #### Extended Common Errors - `EntityNotFoundError` - Extends `NotFoundError` with entity name support -- `MissingParameterError` - Extends `BadRequestError` for missing parameters - `ValidationFailedError` - Extends `BadRequestError` for validation errors ## Usage Examples 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 index c86d0158d..dc92a217b 100644 --- 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 @@ -93,18 +93,14 @@ def initialize(message = 'Unauthorized', details: {}) end end - # =================================================== - # Extending lowest level errors, but still low level - # =================================================== - - class MissingParameterError < BadRequestError - def initialize(parameter_name, details: {}) - super("Missing parameter #{parameter_name}", details: details) + class ValidationFailedError < BadRequestError + def initialize(message = 'Validation failed', details: {}) + super end end - class ValidationFailedError < BadRequestError - def initialize(message = 'Validation failed', details: {}) + class NotFoundError < BusinessError + def initialize(message, details: {}) super 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/error_handling.rb b/packages/forest_admin_agent/lib/forest_admin_agent/http/error_handling.rb index 5274ab92e..fb1d004f6 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 @@ -20,6 +20,12 @@ def get_error_message(error) return error.message if error.is_a?(ForestAdminAgent::Http::Exceptions::BusinessError) + # Handle DatasourceToolkit ValidationError + if defined?(ForestAdminDatasourceToolkit::Exceptions::ValidationError) && + error.is_a?(ForestAdminDatasourceToolkit::Exceptions::ValidationError) + return error.message + end + if defined?(ForestAdminAgent::Facades) && (customizer = ForestAdminAgent::Facades::Container.cache(:customize_error_message)) message = eval(customizer).call(error) @@ -42,6 +48,12 @@ def get_error_status(error) return http_error.status if http_error end + # Handle DatasourceToolkit ValidationError + if defined?(ForestAdminDatasourceToolkit::Exceptions::ValidationError) && + error.is_a?(ForestAdminDatasourceToolkit::Exceptions::ValidationError) + return 400 + end + 500 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 5b227bed6..e24c1ff24 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 @@ -61,10 +61,8 @@ def type=(type) end def raise_error(result, key_names) - raise BadRequestError.new( - 'Invalid native query result columns', - details: { expected: key_names, actual: result.keys.join(', ') } - ) + raise BadRequestError, + "The result columns must be named #{key_names} instead of '#{result.keys.join("', '")}'" end def make_value(result) 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 a2701abbd..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 @@ -79,12 +79,15 @@ def auth protected def get_and_check_rendering_id(params) - raise MissingParameterError, 'renderingId' unless params['renderingId'] + unless params['renderingId'] + raise BadRequestError, + ForestAdminAgent::Utils::ErrorMessages::MISSING_RENDERING_ID + end begin Integer(params['renderingId']) rescue ArgumentError - raise ValidationFailedError, 'Invalid rendering ID: must be an integer' + 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 1cf0d7421..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 @@ -2,7 +2,7 @@ module ForestAdminAgent module Services - class IpWhitelist # rubocop:disable Metrics/ClassLength + class IpWhitelist RULE_MATCH_IP = 0 RULE_MATCH_RANGE = 1 RULE_MATCH_SUBNET = 2 @@ -40,10 +40,7 @@ def ip_matches_rule?(ip, rule) when RULE_MATCH_SUBNET ip_match_subnet?(ip, rule['range']) else - raise ForestAdminAgent::Http::Exceptions::InternalServerError.new( - 'Invalid IP whitelist rule type', - details: { rule_type: rule['type'], rule: rule } - ) + raise ForestAdminAgent::Http::Exceptions::InternalServerError, 'Invalid rule type' end end @@ -96,10 +93,8 @@ def fetch_rules status: response.status, response: response.body }) - raise ForestAdminAgent::Http::Exceptions::InternalServerError.new( - 'Failed to fetch IP whitelist rules', - details: { status: response.status, body: response.body } - ) + raise ForestAdminAgent::Http::Exceptions::InternalServerError, + ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED end begin @@ -110,11 +105,8 @@ def fetch_rules status: response.status, response: response.body }) - raise ForestAdminAgent::Http::Exceptions::InternalServerError.new( - 'Failed to parse IP whitelist rules response', - details: { error: e.message, body: response.body }, - cause: e - ) + raise ForestAdminAgent::Http::Exceptions::InternalServerError, + ForestAdminAgent::Utils::ErrorMessages::UNEXPECTED end ip_whitelist_data = body['data']['attributes'] @@ -127,11 +119,8 @@ def fetch_rules status: response&.status, response: response&.body }) - raise ForestAdminAgent::Http::Exceptions::InternalServerError.new( - 'Unexpected error fetching IP whitelist rules', - details: { error: e.message, status: response&.status }, - cause: e - ) + 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 35c7be93d..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,12 +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? } - if action.nil? - raise NotFoundError.new( - 'Smart action not found', - details: { collection: collection_name, endpoint: endpoint, http_method: http_method } - ) - end + raise BadRequestError, "The collection #{collection_name} does not have this smart action" if action.nil? action 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 4d8d3e941..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 @@ -39,14 +39,9 @@ def validate_headers def extract_timezone timezone = @args[:params]['timezone'] - raise MissingParameterError, 'timezone' unless timezone + raise BadRequestError, 'Missing timezone' unless timezone - unless Time.find_zone(timezone) - raise BadRequestError.new( - 'Invalid timezone', - details: { timezone: timezone } - ) - end + 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 1bb4ba444..efb38e7ce 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 @@ -27,10 +27,7 @@ def self.from_plain_object(collection, filters) return conditions.size == 1 ? conditions[0] : ConditionTreeBranch.new(aggregator, conditions) end - raise BadRequestError.new( - 'Failed to instantiate condition tree: invalid filter format', - details: { filters: filters } - ) + 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/query_string_parser.rb b/packages/forest_admin_agent/lib/forest_admin_agent/utils/query_string_parser.rb index 8e046b98a..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 @@ -93,10 +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 BadRequestError.new( - 'Invalid pagination parameters', - details: { limit: items_per_pages, page: page } - ) + raise BadRequestError, "Invalid pagination [limit: #{items_per_pages}, skip: #{page}]" end offset = (page.to_i - 1) * items_per_pages.to_i @@ -111,12 +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) - if search && !collection.is_searchable? - raise BadRequestError.new( - 'Collection is not searchable', - details: { collection: collection.name } - ) - end + raise BadRequestError, 'Collection is not searchable' if search && !collection.is_searchable? search end @@ -157,12 +149,7 @@ def self.parse_segment(collection, args) return unless segment - unless collection.schema[:segments].include?(segment) - raise BadRequestError.new( - 'Invalid segment', - details: { segment: segment, available_segments: collection.schema[:segments] } - ) - end + raise BadRequestError, "Invalid segment: #{segment}" unless collection.schema[:segments].include?(segment) segment end 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/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..6ed704f2b 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 @@ -42,7 +42,7 @@ module Http 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/resources/native_query_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/native_query_spec.rb index fedd8570a..3726b2842 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 @@ -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/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/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/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 From 84dd5ec6a524d01f6349b1a102810dfa4c8a3522 Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Thu, 23 Oct 2025 09:05:48 +0200 Subject: [PATCH 03/21] fix: tests --- .../datasource_customizer_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 54ff91b5108aa08bf3696b74d6cc9d7a49711059 Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Thu, 23 Oct 2025 09:11:51 +0200 Subject: [PATCH 04/21] fix: tests --- .gitignore | 2 ++ .../http/Exceptions/business_error.rb | 6 ----- .../http/Exceptions/not_found_error.rb | 13 ++++++++++ .../http/error_translator.rb | 2 ++ .../forest_controller_spec.rb | 26 +++++++++++++------ 5 files changed, 35 insertions(+), 14 deletions(-) create mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/not_found_error.rb 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/http/Exceptions/business_error.rb b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/business_error.rb index dc92a217b..cc017d5e0 100644 --- 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 @@ -98,12 +98,6 @@ def initialize(message = 'Validation failed', details: {}) super end end - - class NotFoundError < BusinessError - def initialize(message, details: {}) - super - 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 new file mode 100644 index 000000000..1e9c65dcc --- /dev/null +++ b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/not_found_error.rb @@ -0,0 +1,13 @@ +require_relative 'business_error' + +module ForestAdminAgent + module Http + module Exceptions + class NotFoundError < BusinessError + def initialize(message, details: {}) + super + end + 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 index e90830c62..f89417599 100644 --- 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 @@ -60,6 +60,8 @@ def self.translate(error) # rubocop:disable Metrics/MethodLength ERROR_401.new(error) when Exceptions::PaymentRequiredError ERROR_402.new(error) + when Exceptions::NotFoundError + ERROR_404.new(error) when Exceptions::ContentTooLargeError ERROR_413.new(error) when Exceptions::FailedDependencyError 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..653d84db1 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 @@ -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) From 5f4f1027806336e248e772d5704b301cbcd50633 Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Thu, 23 Oct 2025 09:35:28 +0200 Subject: [PATCH 05/21] fix: merge main --- .../lib/forest_admin_agent.rb | 4 + .../http/Exceptions/business_error.rb | 78 +++++++++++++------ .../http/Exceptions/conflict_error.rb | 13 ---- .../http/Exceptions/forbidden_error.rb | 13 ---- .../http/Exceptions/not_found_error.rb | 13 ---- .../http/Exceptions/unprocessable_error.rb | 13 ---- .../http/Exceptions/validation_error.rb | 15 ---- .../forest_admin_agent/http/error_handling.rb | 1 - .../http/error_translator.rb | 9 ++- .../services/smart_action_checker.rb | 4 +- .../forest_controller_spec.rb | 10 +-- 11 files changed, 73 insertions(+), 100 deletions(-) delete mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/conflict_error.rb delete mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/forbidden_error.rb delete mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/not_found_error.rb delete mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/unprocessable_error.rb delete mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/validation_error.rb diff --git a/packages/forest_admin_agent/lib/forest_admin_agent.rb b/packages/forest_admin_agent/lib/forest_admin_agent.rb index 160119525..8b82a870c 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent.rb @@ -6,6 +6,10 @@ loader.inflector.inflect('sse_cache_invalidation' => 'SSECacheInvalidation') loader.setup +# Eager load business_error.rb which contains all error classes +# This ensures all error classes are available immediately +require_relative 'forest_admin_agent/http/Exceptions/business_error' + module ForestAdminAgent class Error < StandardError; end # Your code goes here... 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 index cc017d5e0..3ff044870 100644 --- 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 @@ -19,61 +19,65 @@ def name end # ==================== - # Lowest level errors + # Specific error types # ==================== - # Note: Some error classes (ForbiddenError, NotFoundError, ConflictError, UnprocessableError, ValidationError) - # are defined in their own files for backward compatibility with HttpException - class UnavailableForLegalReasonsError < BusinessError - def initialize(message = 'Unavailable for legal reasons', details: {}) + class BadRequestError < BusinessError + def initialize(message = 'Bad request', details: {}) super end end - class BadRequestError < BusinessError - def initialize(message = 'Bad request', details: {}) + class UnauthorizedError < BusinessError + def initialize(message = 'Unauthorized', details: {}) super end end - class ContentTooLargeError < BusinessError - def initialize(message = 'Content too large', details: {}) + class PaymentRequiredError < BusinessError + def initialize(message = 'Payment required', details: {}) super end end - class FailedDependencyError < BusinessError - def initialize(message = 'Failed dependency', details: {}) + class ForbiddenError < BusinessError + def initialize(message = 'Forbidden', details: {}) super end end - class TooEarlyError < BusinessError - def initialize(message = 'Too early', details: {}) + class NotFoundError < BusinessError + def initialize(message, details: {}) super end end - class InternalServerError < BusinessError - def initialize(message = 'Internal server error', details: {}, cause: nil) + class ConflictError < BusinessError + def initialize(message = 'Conflict', details: {}) super end end - class PaymentRequiredError < BusinessError - def initialize(message = 'Payment required', details: {}) + class ContentTooLargeError < BusinessError + def initialize(message = 'Content too large', details: {}) super end end - class BadGatewayError < BusinessError - def initialize(message = 'Bad gateway error', details: {}, cause: nil) + class UnprocessableError < BusinessError + def initialize(message = 'Unprocessable entity', details: {}) super end end - class ServiceUnavailableError < BusinessError - def initialize(message = 'Service unavailable error', details: {}, cause: nil) + class FailedDependencyError < BusinessError + def initialize(message = 'Failed dependency', details: {}) + super + end + end + + class TooEarlyError < BusinessError + def initialize(message = 'Too early', details: {}) super end end @@ -87,17 +91,45 @@ def initialize(message, retry_after, details: {}) end end - class UnauthorizedError < BusinessError - def initialize(message = 'Unauthorized', details: {}) + class UnavailableForLegalReasonsError < BusinessError + def initialize(message = 'Unavailable for legal reasons', details: {}) + super + 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 6883f0581..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(409, 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/not_found_error.rb b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/not_found_error.rb deleted file mode 100644 index 1e9c65dcc..000000000 --- a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/not_found_error.rb +++ /dev/null @@ -1,13 +0,0 @@ -require_relative 'business_error' - -module ForestAdminAgent - module Http - module Exceptions - class NotFoundError < BusinessError - def initialize(message, details: {}) - super - 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 f3668cef4..000000000 --- a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/validation_error.rb +++ /dev/null @@ -1,15 +0,0 @@ -require_relative 'http_exception' - -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 fb1d004f6..107d8860d 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 @@ -2,7 +2,6 @@ require_relative 'exceptions/business_error' require_relative 'exceptions/http_error' require_relative 'exceptions/http_exception' -require_relative 'exceptions/validation_error' module ForestAdminAgent module Http 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 index f89417599..7b034eaa2 100644 --- 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 @@ -1,6 +1,5 @@ require_relative 'exceptions/business_error' require_relative 'exceptions/http_error' -require_relative 'exceptions/validation_error' module ForestAdminAgent module Http @@ -54,16 +53,22 @@ def self.translate(error) # rubocop:disable Metrics/MethodLength # Translate BusinessError to HttpError with appropriate status code case error - when Exceptions::BadRequestError + when Exceptions::BadRequestError, Exceptions::ValidationError ERROR_400.new(error) when Exceptions::UnauthorizedError ERROR_401.new(error) when Exceptions::PaymentRequiredError ERROR_402.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::ContentTooLargeError ERROR_413.new(error) + when Exceptions::UnprocessableError + ERROR_422.new(error) when Exceptions::FailedDependencyError ERROR_424.new(error) when Exceptions::TooEarlyError 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..2d80615b9 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 @@ -43,7 +43,7 @@ def can_approve? return true end - raise ForbiddenError.new('You don\'t have the permission to trigger this action.', TRIGGER_FORBIDDEN_ERROR) + raise ForbiddenError, 'You don\'t have the permission to trigger this action.' end def can_trigger? @@ -63,7 +63,7 @@ def can_trigger? end end - raise ForbiddenError.new('You don\'t have the permission to trigger this action.', TRIGGER_FORBIDDEN_ERROR) + raise ForbiddenError, 'You don\'t have the permission to trigger this action.' end def match_conditions(condition_name) 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 653d84db1..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) From 7c419810261007c3c677927f0f3a3a77f1d9b74b Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Thu, 23 Oct 2025 10:30:54 +0200 Subject: [PATCH 06/21] fix: tests --- .../services/smart_action_checker.rb | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) 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 2d80615b9..da6183e97 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 @@ -105,16 +105,10 @@ def match_conditions(condition_name) # Wrap other ForestExceptions (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 ConflictError, '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 ConflictError, "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 +116,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 ConflictError, 'The conditions to trigger this action cannot be verified. Please contact an administrator.' end def condition_by_role_id(condition) From bd53fd6d9b6394df457b8041769a01dca7c7570a Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Thu, 23 Oct 2025 10:39:01 +0200 Subject: [PATCH 07/21] fix: tests --- .../lib/forest_admin_agent/http/error_handling.rb | 6 +++--- .../lib/forest_admin_agent/http/error_translator.rb | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) 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 107d8860d..02b60a4fe 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,7 +1,7 @@ require_relative 'error_translator' -require_relative 'exceptions/business_error' -require_relative 'exceptions/http_error' -require_relative 'exceptions/http_exception' +require_relative 'Exceptions/business_error' +require_relative 'Exceptions/http_error' +require_relative 'Exceptions/http_exception' module ForestAdminAgent module Http 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 index 7b034eaa2..d07ecf2f5 100644 --- 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 @@ -1,5 +1,5 @@ -require_relative 'exceptions/business_error' -require_relative 'exceptions/http_error' +require_relative 'Exceptions/business_error' +require_relative 'Exceptions/http_error' module ForestAdminAgent module Http From a877bf01a5c966082b337b041c3e21f2764d72dd Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Thu, 23 Oct 2025 11:50:39 +0200 Subject: [PATCH 08/21] fix: review --- .../lib/forest_admin_agent.rb | 4 --- .../forest_admin_agent/http/error_handling.rb | 13 ---------- .../services/smart_action_checker.rb | 26 ++++++++++++------- .../utils/condition_tree_parser.rb | 1 - .../schema/generator_action_field_widget.rb | 3 +-- 5 files changed, 18 insertions(+), 29 deletions(-) diff --git a/packages/forest_admin_agent/lib/forest_admin_agent.rb b/packages/forest_admin_agent/lib/forest_admin_agent.rb index 8b82a870c..160119525 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent.rb @@ -6,10 +6,6 @@ loader.inflector.inflect('sse_cache_invalidation' => 'SSECacheInvalidation') loader.setup -# Eager load business_error.rb which contains all error classes -# This ensures all error classes are available immediately -require_relative 'forest_admin_agent/http/Exceptions/business_error' - module ForestAdminAgent class Error < StandardError; end # Your code goes here... 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 02b60a4fe..5d84c63c6 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 @@ -10,7 +10,6 @@ module ErrorHandling # @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) - # Handle HttpError instances 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) @@ -19,12 +18,6 @@ def get_error_message(error) return error.message if error.is_a?(ForestAdminAgent::Http::Exceptions::BusinessError) - # Handle DatasourceToolkit ValidationError - if defined?(ForestAdminDatasourceToolkit::Exceptions::ValidationError) && - error.is_a?(ForestAdminDatasourceToolkit::Exceptions::ValidationError) - return error.message - end - if defined?(ForestAdminAgent::Facades) && (customizer = ForestAdminAgent::Facades::Container.cache(:customize_error_message)) message = eval(customizer).call(error) @@ -47,12 +40,6 @@ def get_error_status(error) return http_error.status if http_error end - # Handle DatasourceToolkit ValidationError - if defined?(ForestAdminDatasourceToolkit::Exceptions::ValidationError) && - error.is_a?(ForestAdminDatasourceToolkit::Exceptions::ValidationError) - return 400 - end - 500 end 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 da6183e97..ca6ed409f 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,5 +1,17 @@ module ForestAdminAgent module Services + 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 SmartActionChecker include ForestAdminAgent::Http::Exceptions include ForestAdminAgent::Utils @@ -9,12 +21,8 @@ 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 +51,7 @@ def can_approve? return true end - raise ForbiddenError, 'You don\'t have the permission to trigger this action.' + raise CustomActionTriggerForbiddenError, 'You don\'t have the permission to trigger this action.' end def can_trigger? @@ -63,7 +71,7 @@ def can_trigger? end end - raise ForbiddenError, 'You don\'t have the permission to trigger this action.' + raise CustomActionTriggerForbiddenError, 'You don\'t have the permission to trigger this action.' end def match_conditions(condition_name) @@ -105,10 +113,10 @@ def match_conditions(condition_name) # Wrap other ForestExceptions (like invalid operators) in ConflictError raise if e.message.include?('has no primary keys') - raise ConflictError, 'The conditions to trigger this action cannot be verified. Please contact an administrator.' + 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, "Invalid action condition: #{e.message}. Please contact an administrator." + 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( @@ -116,7 +124,7 @@ def match_conditions(condition_name) "Unexpected error in match_conditions: #{e.class} - #{e.message}" ) - raise ConflictError, 'The conditions to trigger this action cannot be verified. Please contact an administrator.' + 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/utils/condition_tree_parser.rb b/packages/forest_admin_agent/lib/forest_admin_agent/utils/condition_tree_parser.rb index efb38e7ce..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 @@ -5,7 +5,6 @@ module ForestAdminAgent module Utils class ConditionTreeParser include ForestAdminAgent::Http::Exceptions - include ForestAdminDatasourceToolkit::Exceptions include ForestAdminDatasourceToolkit::Utils include ForestAdminDatasourceToolkit::Components::Query::ConditionTree include ForestAdminDatasourceToolkit::Components::Query::ConditionTree::Nodes 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 160c4c435..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 @@ -3,7 +3,6 @@ module Utils module Schema class GeneratorActionFieldWidget include ForestAdminAgent::Http::Exceptions - include ForestAdminDatasourceToolkit::Exceptions def self.build_widget_options(field) return if !ActionFields.widget?(field) || %w[Collection Enum EnumList].include?(field.type) @@ -45,7 +44,7 @@ def self.build_widget_options(field) return build_address_autocomplete_widget_edit(field) if ActionFields.address_autocomplete_field?(field) raise InternalServerError.new( - 'Unsupported widget type', + "Unsupported widget type: #{field&.widget}", details: { widget: field&.widget, field_type: field&.type } ) end From 2dd18a70986f816abf11138a752be556131966e8 Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Thu, 23 Oct 2025 12:09:30 +0200 Subject: [PATCH 09/21] fix: review --- .../lib/forest_admin_agent.rb | 1 + .../forest_admin_agent/http/error_handling.rb | 28 +++++++++++++------ .../http/error_translator.rb | 4 +-- .../http/error_handling_spec.rb | 10 ++++--- .../forest_admin_rails/forest_controller.rb | 22 ++++++--------- 5 files changed, 36 insertions(+), 29 deletions(-) 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/http/error_handling.rb b/packages/forest_admin_agent/lib/forest_admin_agent/http/error_handling.rb index 5d84c63c6..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 @@ -35,11 +35,6 @@ def get_error_status(error) return error.status if error.respond_to?(:status) && error.status - if error.is_a?(ForestAdminAgent::Http::Exceptions::BusinessError) - http_error = ForestAdminAgent::Http::ErrorTranslator.translate(error) - return http_error.status if http_error - end - 500 end @@ -77,12 +72,27 @@ def get_error_name(error) end # Translate any exception to its appropriate HTTP error representation - # This is just a convenient wrapper around ErrorTranslator.translate + # and return all error information needed for response # @param error [Exception] The error to translate - # @return [HttpError, HttpException, Exception] The translated error or the original error + # @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 all translation logic to ErrorTranslator - ForestAdminAgent::Http::ErrorTranslator.translate(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 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 index d07ecf2f5..376a698a6 100644 --- 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 @@ -45,10 +45,10 @@ def self.translate(error) # rubocop:disable Metrics/MethodLength return error if error.is_a?(Exceptions::HttpError) return error if error.respond_to?(:status) && error.status - # Translate DatasourceToolkit ValidationError to Agent ValidationError + # Translate DatasourceToolkit ValidationError to Agent BadRequestError if defined?(ForestAdminDatasourceToolkit::Exceptions::ValidationError) && error.is_a?(ForestAdminDatasourceToolkit::Exceptions::ValidationError) - return Exceptions::BadRequestError.new(error) + error = Exceptions::BadRequestError.new(error.message) end # Translate BusinessError to HttpError with appropriate status code 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_rails/app/controllers/forest_admin_rails/forest_controller.rb b/packages/forest_admin_rails/app/controllers/forest_admin_rails/forest_controller.rb index a0830fab9..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,17 +61,11 @@ def handle_streaming_response(data) end def exception_handler(exception) - # Translate BusinessError to HttpError if applicable - translated_error = translate_error(exception) - - http_status = get_error_status(translated_error) - error_message = get_error_message(translated_error) - error_name = get_error_name(translated_error) - error_meta = get_error_meta(translated_error) - error_headers = get_error_headers(translated_error) + # Translate BusinessError to HttpError and get all error information + error_info = translate_error(exception) # Add custom headers to the response - response.headers.merge!(error_headers) if error_headers.any? + response.headers.merge!(error_info[:headers]) if error_info[:headers].any? data = case exception when ForestAdminAgent::Http::Exceptions::AuthenticationOpenIdClient, @@ -85,10 +79,10 @@ def exception_handler(exception) { errors: [ { - name: error_name, - detail: error_message, - status: http_status, - meta: error_meta.presence, + name: error_info[:name], + detail: error_info[:message], + status: error_info[:status], + meta: error_info[:meta].presence, data: exception.try(:data) }.compact ] @@ -99,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 From 2d9bd28327489fa167ca8b4fe1c438db1fd2934f Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Thu, 23 Oct 2025 12:13:53 +0200 Subject: [PATCH 10/21] fix: review --- .../lib/forest_admin_agent/services/smart_action_checker.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 ca6ed409f..a56fdb6d0 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,5 +1,7 @@ module ForestAdminAgent module Services + include ForestAdminAgent::Http::Exceptions + class CustomActionTriggerForbiddenError < ForbiddenError def initialize(message = 'Custom action trigger forbidden', details: {}) super @@ -13,7 +15,6 @@ def initialize(message = 'Invalid action condition', details: {}) end class SmartActionChecker - include ForestAdminAgent::Http::Exceptions include ForestAdminAgent::Utils include ForestAdminDatasourceToolkit::Utils include ForestAdminDatasourceToolkit::Components::Query From 7ee2c21fa35f57a3f935fb7e53543938a7b1a562 Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Thu, 23 Oct 2025 12:31:45 +0200 Subject: [PATCH 11/21] fix: tests --- .../http/Exceptions/require_approval.rb | 14 -------------- .../services/smart_action_checker.rb | 13 ++++++++----- .../services/smart_action_checker_spec.rb | 4 ++-- 3 files changed, 10 insertions(+), 21 deletions(-) delete mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/require_approval.rb 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/services/smart_action_checker.rb b/packages/forest_admin_agent/lib/forest_admin_agent/services/smart_action_checker.rb index a56fdb6d0..21c21a4ac 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 @@ -14,6 +14,12 @@ def initialize(message = 'Invalid action condition', details: {}) end end + class CustomActionRequiresApprovalError < ForbiddenError + def initialize(message = 'Custom action requires approval', details: {}) + super + end + end + class SmartActionChecker include ForestAdminAgent::Utils include ForestAdminDatasourceToolkit::Utils @@ -22,8 +28,6 @@ class SmartActionChecker attr_reader :parameters, :collection, :smart_action, :caller, :role_id, :filter, :attributes - REQUIRE_APPROVAL_ERROR = 'CustomActionRequiresApprovalError'.freeze - def initialize(parameters, collection, smart_action, caller, role_id, filter) @parameters = parameters @collection = collection @@ -62,10 +66,9 @@ 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 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..6c7d9c5ec 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 From ee96faf880722ae7a7753e9323cc7c883739e1eb Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Thu, 23 Oct 2025 15:06:48 +0200 Subject: [PATCH 12/21] fix: remove useless error classes --- .../http/Exceptions/README.md | 6 +--- .../http/Exceptions/business_error.rb | 36 ------------------- .../http/error_translator.rb | 18 ---------- 3 files changed, 1 insertion(+), 59 deletions(-) diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/README.md b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/README.md index b2d9e39d7..fc0effb3f 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/README.md +++ b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/README.md @@ -28,13 +28,10 @@ The system provides error classes for all standard HTTP error codes: #### 4xx Client Errors - `BadRequestError` (400) - Invalid request data -- `UnauthorizedError` (401) - Authentication failed -- `PaymentRequiredError` (402) - Payment required - `ForbiddenError` (403) - Insufficient permissions - `NotFoundError` (404) - Resource not found - `ConflictError` (409) - Resource conflict -- `ContentTooLargeError` (413) - Request payload too large -- `UnprocessableEntityError` (422) - Validation failed +- `UnprocessableError` (422) - Validation failed - `TooManyRequestsError` (429) - Rate limit exceeded #### 5xx Server Errors @@ -101,7 +98,6 @@ raise ForestAdminAgent::Http::Exceptions::ValidationFailedError.new( ```ruby raise ForestAdminAgent::Http::Exceptions::ConflictError.new( - 'Organization', 'An organization with this name already exists', details: { organization_name: 'Acme Corp', 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 index 3ff044870..9f6da4098 100644 --- 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 @@ -28,18 +28,6 @@ def initialize(message = 'Bad request', details: {}) end end - class UnauthorizedError < BusinessError - def initialize(message = 'Unauthorized', details: {}) - super - end - end - - class PaymentRequiredError < BusinessError - def initialize(message = 'Payment required', details: {}) - super - end - end - class ForbiddenError < BusinessError def initialize(message = 'Forbidden', details: {}) super @@ -58,30 +46,12 @@ def initialize(message = 'Conflict', details: {}) end end - class ContentTooLargeError < BusinessError - def initialize(message = 'Content too large', details: {}) - super - end - end - class UnprocessableError < BusinessError def initialize(message = 'Unprocessable entity', details: {}) super end end - class FailedDependencyError < BusinessError - def initialize(message = 'Failed dependency', details: {}) - super - end - end - - class TooEarlyError < BusinessError - def initialize(message = 'Too early', details: {}) - super - end - end - class TooManyRequestsError < BusinessError attr_reader :retry_after @@ -91,12 +61,6 @@ def initialize(message, retry_after, details: {}) end end - class UnavailableForLegalReasonsError < BusinessError - def initialize(message = 'Unavailable for legal reasons', details: {}) - super - end - end - class InternalServerError < BusinessError def initialize(message = 'Internal server error', details: {}, cause: nil) super 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 index 376a698a6..082ecb63c 100644 --- 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 @@ -8,8 +8,6 @@ module Http class ErrorTranslator # Create specific HTTP error classes for each status code ERROR_400 = Exceptions::HttpErrorFactory.create_for_business_error(400, 'Bad Request') - ERROR_401 = Exceptions::HttpErrorFactory.create_for_business_error(401, 'Unauthorized') - ERROR_402 = Exceptions::HttpErrorFactory.create_for_business_error(402, 'Payment Required') 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| @@ -17,16 +15,12 @@ class ErrorTranslator } }) ERROR_409 = Exceptions::HttpErrorFactory.create_for_business_error(409, 'Conflict') - ERROR_413 = Exceptions::HttpErrorFactory.create_for_business_error(413, 'Content too large') ERROR_422 = Exceptions::HttpErrorFactory.create_for_business_error(422, 'Unprocessable Entity') - ERROR_424 = Exceptions::HttpErrorFactory.create_for_business_error(424, 'Failed Dependency') - ERROR_425 = Exceptions::HttpErrorFactory.create_for_business_error(425, 'Too Early') ERROR_429 = Exceptions::HttpErrorFactory.create_for_business_error(429, 'Too Many Requests', { custom_headers: lambda { |error| { 'Retry-After' => error.retry_after.to_s } } }) - ERROR_451 = Exceptions::HttpErrorFactory.create_for_business_error(451, 'Unavailable For Legal Reasons') 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') @@ -55,28 +49,16 @@ def self.translate(error) # rubocop:disable Metrics/MethodLength case error when Exceptions::BadRequestError, Exceptions::ValidationError ERROR_400.new(error) - when Exceptions::UnauthorizedError - ERROR_401.new(error) - when Exceptions::PaymentRequiredError - ERROR_402.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::ContentTooLargeError - ERROR_413.new(error) when Exceptions::UnprocessableError ERROR_422.new(error) - when Exceptions::FailedDependencyError - ERROR_424.new(error) - when Exceptions::TooEarlyError - ERROR_425.new(error) when Exceptions::TooManyRequestsError ERROR_429.new(error) - when Exceptions::UnavailableForLegalReasonsError - ERROR_451.new(error) when Exceptions::InternalServerError ERROR_500.new(error) when Exceptions::BadGatewayError From 9b8e9166780f129bcba31b289069c77e0c5f52c1 Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Thu, 23 Oct 2025 15:27:14 +0200 Subject: [PATCH 13/21] fix: remove readme --- .../http/Exceptions/README.md | 219 ------------------ 1 file changed, 219 deletions(-) delete mode 100644 packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/README.md diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/README.md b/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/README.md deleted file mode 100644 index fc0effb3f..000000000 --- a/packages/forest_admin_agent/lib/forest_admin_agent/http/Exceptions/README.md +++ /dev/null @@ -1,219 +0,0 @@ -# Forest Admin Agent Ruby - Error Handling - -This document describes the improved error handling system in Forest Admin Agent Ruby, which mimics the error handling approach from agent-nodejs. - -## Overview - -The new error handling system provides: - -1. **Explicit error types** - Clear, specific error classes for different scenarios -2. **Proper HTTP status codes** - Automatic mapping of business errors to appropriate HTTP status codes -3. **Rich error information** - Support for error details, metadata, and custom headers -4. **Better debugging** - More informative error messages for developers - -## Error Hierarchy - -### BusinessError (Base Class) - -All business errors inherit from `BusinessError`, which provides: -- `message` - The error message -- `details` - A hash of additional details/metadata -- `cause` - The underlying cause (if any) -- `name` - The error class name - -### Common Error Classes - -The system provides error classes for all standard HTTP error codes: - -#### 4xx Client Errors - -- `BadRequestError` (400) - Invalid request data -- `ForbiddenError` (403) - Insufficient permissions -- `NotFoundError` (404) - Resource not found -- `ConflictError` (409) - Resource conflict -- `UnprocessableError` (422) - Validation failed -- `TooManyRequestsError` (429) - Rate limit exceeded - -#### 5xx Server Errors - -- `InternalServerError` (500) - Unexpected server error -- `BadGatewayError` (502) - Bad gateway -- `ServiceUnavailableError` (503) - Service temporarily unavailable - -### Specialized Error Classes - -#### Authentication Errors (all 401) - -- `InvalidApplicationTokenError` -- `InvalidCredentialsError` -- `InvalidRecoveryCodeError` -- `InvalidRefreshTokenError` -- `InvalidSessionHashError` -- `InvalidTimeBasedPasswordError` -- `InvalidTokenError` -- `InvalidUserError` -- `MissingTokenError` -- `PasswordLoginUnavailableError` -- `TwoFactorAuthenticationRequiredError` - -#### Authorization Errors (all 403) - -- `TwoFactorAuthenticationRequiredForbiddenError` - -#### Extended Common Errors - -- `EntityNotFoundError` - Extends `NotFoundError` with entity name support -- `ValidationFailedError` - Extends `BadRequestError` for validation errors - -## Usage Examples - -### Basic Error Raising - -```ruby -# Raise a simple forbidden error -raise ForestAdminAgent::Http::Exceptions::ForbiddenError.new( - "You don't have permission to access this resource" -) - -# Raise a not found error with details -raise ForestAdminAgent::Http::Exceptions::EntityNotFoundError.new( - 'User', - details: { user_id: 123 } -) - -# Raise an authentication error -raise ForestAdminAgent::Http::Exceptions::InvalidTokenError.new - -# Raise a validation error with details -raise ForestAdminAgent::Http::Exceptions::ValidationFailedError.new( - 'Validation failed', - details: { - field: 'email', - error: 'Email is required' - } -) -``` - -### Error with Custom Details - -```ruby -raise ForestAdminAgent::Http::Exceptions::ConflictError.new( - 'An organization with this name already exists', - details: { - organization_name: 'Acme Corp', - suggestion: 'Try a different name' - } -) -``` - -### Error with Cause - -```ruby -begin - external_service.call -rescue StandardError => e - raise ForestAdminAgent::Http::Exceptions::ServiceUnavailableError.new( - 'External service is unavailable', - details: { service: 'external_api' }, - cause: e - ) -end -``` - -### Rate Limiting Error - -```ruby -raise ForestAdminAgent::Http::Exceptions::TooManyRequestsError.new( - 'Too many requests', - 60, # retry_after in seconds - details: { limit: 100, period: '1 minute' } -) -``` - -## Error Response Format - -Errors are automatically converted to JSON responses with the following format: - -```json -{ - "errors": [ - { - "name": "ForbiddenError", - "detail": "You don't have permission to access this resource", - "status": 403, - "meta": { - "resource": "users", - "action": "delete" - } - } - ] -} -``` - -## Custom Headers - -Some errors automatically add custom headers to the response: - -- `NotFoundError` adds `x-error-type: object-not-found` -- `TooManyRequestsError` adds `Retry-After: ` - -## Backward Compatibility - -The new system is backward compatible with existing code: - -- Old `HttpException`-based errors still work -- Legacy error signatures are supported -- The error handling middleware automatically translates both old and new errors - -## Migration Guide - -To migrate existing code to use the new error system: - -### Before - -```ruby -raise ForbiddenError.new("Access denied", "ForbiddenError") -``` - -### After - -```ruby -raise ForestAdminAgent::Http::Exceptions::ForbiddenError.new( - "Access denied" -) -``` - -### Before - -```ruby -raise StandardError, "Something went wrong" -``` - -### After - -```ruby -raise ForestAdminAgent::Http::Exceptions::InternalServerError.new( - "Something went wrong", - details: { context: 'additional info' } -) -``` - -## Benefits - -1. **Type Safety** - Specific error classes make it clear what error is being raised -2. **Automatic HTTP Mapping** - No need to manually set status codes -3. **Rich Debugging Info** - Details field provides context without exposing sensitive data -4. **Consistent API** - All errors follow the same pattern -5. **Better Testing** - Easy to test specific error types - -## Implementation Details - -The error handling system consists of: - -1. **BusinessError classes** (`business_error.rb`) - Base error classes -2. **HttpError wrapper** (`http_error.rb`) - Wraps business errors with HTTP-specific properties -3. **ErrorTranslator** (`error_translator.rb`) - Maps business errors to HTTP status codes -4. **ErrorHandling module** (`error_handling.rb`) - Helper methods for error responses -5. **Rails Controller** - Automatically catches and formats errors - -All errors are automatically caught by the `ForestController` and converted to appropriate JSON responses. From d6da6dd559e01b0eb045f4914f93f123e4a8b62b Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Fri, 24 Oct 2025 11:54:19 +0200 Subject: [PATCH 14/21] fix: replace all generic errors to specific HTTP errors --- .../http/forest_admin_api_requester.rb | 4 +--- .../routes/charts/charts.rb | 4 ++-- .../routes/resources/native_query.rb | 2 +- .../resources/related/dissociate_related.rb | 2 +- .../services/smart_action_checker.rb | 9 ++++--- .../lib/forest_admin_agent/utils/id.rb | 7 ++++-- .../http/forest_admin_api_requester_spec.rb | 6 ++--- .../routes/capabilities/collections_spec.rb | 2 +- .../routes/charts/charts_spec.rb | 6 ++--- .../routes/resources/list_spec.rb | 2 +- .../routes/resources/native_query_spec.rb | 2 +- .../related/dissociate_related_spec.rb | 2 +- .../services/smart_action_checker_spec.rb | 10 ++++---- .../lib/forest_admin_agent/utils/id_spec.rb | 6 ++--- .../datasource.rb | 2 +- .../action/action_collection_decorator.rb | 8 +++---- .../decorators/action/base_action.rb | 3 ++- .../decorators/action/dynamic_field.rb | 3 +-- .../decorators/action/form_factory.rb | 5 ++-- .../decorators/action/form_layout_element.rb | 10 ++++---- .../decorators/action/widget_field.rb | 5 ++-- .../binary/binary_collection_decorator.rb | 5 ++-- .../chart/chart_collection_decorator.rb | 4 +++- .../decorators/chart/chart_context.rb | 2 +- .../chart/chart_datasource_decorator.rb | 6 +++-- .../computed/compute_collection_decorator.rb | 4 ++-- .../operators_emulate_collection_decorator.rb | 14 ++++++----- .../publication_collection_decorator.rb | 9 +++---- .../publication_datasource_decorator.rb | 7 ++++-- .../relation/relation_collection_decorator.rb | 8 ++++--- .../rename_collection_datasource_decorator.rb | 12 ++++++---- .../rename_field_collection_decorator.rb | 7 +++--- .../sort/sort_collection_decorator.rb | 2 +- .../validation_collection_decorator.rb | 7 ++++-- .../write_replace_collection_decorator.rb | 16 +++++++++---- .../plugins/add_external_relation.rb | 3 ++- .../plugins/import_field.rb | 8 ++++--- .../collection_customizer_spec.rb | 2 +- .../action_collection_decorator_spec.rb | 8 +++---- .../decorators/action/dynamic_field_spec.rb | 4 ++-- .../decorators/action/form_factory_spec.rb | 10 ++++---- .../binary_collection_decorator_spec.rb | 6 ++--- .../chart/chart_collection_decorator_spec.rb | 5 +--- .../chart/chart_datasource_decorator_spec.rb | 6 ++--- .../compute_collection_decorator_spec.rb | 7 ++---- ...ators_emulate_collection_decorator_spec.rb | 6 ++--- .../publication_collection_decorator_spec.rb | 4 ++-- .../publication_datasource_decorator_spec.rb | 8 +++---- .../relation_collection_decorator_spec.rb | 22 ++++++++--------- ...me_collection_datasource_decorator_spec.rb | 17 ++++--------- .../rename_field_collection_decorator_spec.rb | 4 ++-- .../segment_collection_decorator_spec.rb | 2 +- .../sort/sort_collection_decorator_spec.rb | 2 +- .../validation_collection_decorator_spec.rb | 4 ++-- .../write_replace/collection_basics_spec.rb | 4 ++-- .../collection_simple_creation_spec.rb | 10 ++++---- .../collection_simple_update_spec.rb | 10 ++++---- .../plugins/add_external_spec.rb | 2 +- .../datasource.rb | 24 ++++++++----------- .../utils/pipeline/filter_generator.rb | 2 +- .../utils/pipeline/lookup_generator.rb | 2 +- .../utils/pipeline/virtual_field_generator.rb | 3 ++- .../utils/schema/mongoid_schema.rb | 16 ++++++++----- .../datasource_spec.rb | 4 ++-- .../utils/pipeline/lookup_generator_spec.rb | 2 +- .../pipeline/virtual_field_generator_spec.rb | 2 +- .../Utils/rpc_client.rb | 6 ++--- .../collection.rb | 13 ++++++---- .../components/query/aggregation.rb | 2 +- .../condition_tree/condition_tree_factory.rb | 8 ++++--- .../condition_tree/nodes/condition_tree.rb | 2 +- .../nodes/condition_tree_leaf.rb | 6 +++-- .../components/query/filter.rb | 2 +- .../datasource.rb | 16 +++++++------ .../utils/collection.rb | 18 ++++++++++---- .../utils/record.rb | 2 +- .../utils/schema.rb | 4 ++-- .../validations/chart_validator.rb | 2 +- .../validations/record_validator.rb | 10 +++++--- .../collection_spec.rb | 2 +- .../components/query/aggregation_spec.rb | 2 +- .../condition_tree_factory_spec.rb | 4 ++-- .../nodes/condition_tree_spec.rb | 4 ++-- .../datasource_spec.rb | 10 ++++---- .../utils/collection_spec.rb | 10 ++++---- .../utils/record_spec.rb | 2 +- .../utils/schema_spec.rb | 4 ++-- .../validations/chart_validator_spec.rb | 2 +- .../condition_tree_validator_spec.rb | 4 ++-- .../validations/record_validator_spec.rb | 14 +++++------ .../lib/forest_admin_rails/engine.rb | 3 ++- .../lib/forest_admin_rpc_agent/engine.rb | 3 ++- 92 files changed, 301 insertions(+), 266 deletions(-) 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 522476743..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 @@ -4,7 +4,6 @@ module ForestAdminAgent module Http class ForestAdminApiRequester include ForestAdminAgent::Http::Exceptions - include ForestAdminDatasourceToolkit::Exceptions def initialize @headers = { @@ -30,8 +29,7 @@ def post(url, params = nil) def handle_response_error(error) # Re-raise if it's already a BusinessError - raise error if error.is_a?(ForestAdminAgent::Http::Exceptions::BusinessError) - raise error if error.is_a?(ForestException) + raise error if error.is_a?(BusinessError) if error.response[:message]&.include?('certificate') raise InternalServerError.new( 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..9c13f6d73 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::UnprocessableError, '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 e24c1ff24..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 @@ -54,7 +54,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 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/services/smart_action_checker.rb b/packages/forest_admin_agent/lib/forest_admin_agent/services/smart_action_checker.rb index 21c21a4ac..f49ead7e9 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 @@ -88,9 +88,8 @@ def match_conditions(condition_name) "Action: #{attributes[:smart_action_id]}" ) - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, - "Collection '#{collection.name}' has no primary keys. " \ - 'Actions with conditional permissions require a primary key to identify records.' + raise UnprocessableError, "Collection '#{collection.name}' has no primary keys. " \ + 'Actions with conditional permissions require a primary key to identify records.' end pk = pks[0] @@ -112,9 +111,9 @@ 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, 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 InvalidActionConditionError, 'The conditions to trigger this action cannot be verified. Please contact an administrator.' 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..9b0c9f928 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::UnprocessableError, + "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/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 6ed704f2b..f3540b1ee 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,10 +32,10 @@ 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(ForestAdminDatasourceToolkit::Exceptions::BusinessError.new('test')) + end.to raise_error(ForestAdminDatasourceToolkit::Exceptions::BusinessError) end it 'raises an exception when the error message contains certificate' do diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/capabilities/collections_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/capabilities/collections_spec.rb index f39ec8c86..174c986ff 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/capabilities/collections_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/capabilities/collections_spec.rb @@ -123,7 +123,7 @@ module Capabilities 'timezone' => 'Europe/Paris' } } - expect { capabilities_collections.handle_request(args) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Collection unknown not found.') + expect { capabilities_collections.handle_request(args) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, 'Collection unknown not found.') end end 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..019224d10 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' + ForestAdminDatasourceToolkit::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, + ForestAdminDatasourceToolkit::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..802b642b1 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::UnprocessableError, "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 3726b2842..4649349b8 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' + BadRequestError, 'Invalid Chart type unknown_type' ) 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..6a7349bc8 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, + ForestAdminDatasourceToolkit::Exceptions::BadRequestError, 'Expected no empty id list' ) 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 6c7d9c5ec..5e2555219 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 @@ -610,7 +610,7 @@ module Services expect do smart_action_checker.can_execute? end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminDatasourceToolkit::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, + ForestAdminDatasourceToolkit::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, + ForestAdminDatasourceToolkit::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, + ForestAdminDatasourceToolkit::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, + ForestAdminDatasourceToolkit::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/id_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/id_spec.rb index a0a5cec7f..85515280a 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, + ForestAdminDatasourceToolkit::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, + ForestAdminDatasourceToolkit::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, + ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, 'This collection has no primary key' ) 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..7904feb25 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(ForestAdminDatasourceToolkit::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/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..2abd72c83 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(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(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(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(Exceptions::BadRequestError, "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..f486f5380 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(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(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..5a42b74b6 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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError) 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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError) 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(ForestAdminDatasourceToolkit::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(ForestAdminDatasourceToolkit::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(ForestAdminDatasourceToolkit::Exceptions::BadRequestError, "'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..114d744b2 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(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(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(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..0b48f6ea5 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(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_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..1cc85655e 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(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(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(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..7c0948d44 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(ForestAdminDatasourceToolkit::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(ForestAdminDatasourceToolkit::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..54dd6e299 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 @@ -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(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(Exceptions::UnprocessableError, "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(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..da811eff8 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,7 +105,7 @@ 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(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, "No such field 'unknown'") end it 'raise when hiding a field referenced in a polymorphic relation' do @@ -125,7 +125,7 @@ module Publication 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(ForestAdminDatasourceToolkit::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..a46dbe00f 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::NotFoundError, 'Collection unknown not found.') + expect { @datasource_decorator.keep_collections_matching(nil, ['unknown']) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, '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, + ForestAdminDatasourceToolkit::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(ForestAdminDatasourceToolkit::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..e24bba67d 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(ForestAdminDatasourceToolkit::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(ForestAdminDatasourceToolkit::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(ForestAdminDatasourceToolkit::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(ForestAdminDatasourceToolkit::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::NotFoundError, '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(ForestAdminDatasourceToolkit::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(ForestAdminDatasourceToolkit::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::NotFoundError, '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(ForestAdminDatasourceToolkit::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(ForestAdminDatasourceToolkit::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(ForestAdminDatasourceToolkit::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..968b4607e 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(Exceptions::BadRequestError) end context 'with ManyToMany relation' do @@ -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(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(Exceptions::NotFoundError, '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(ForestAdminDatasourceToolkit::Exceptions::BadRequestError, 'Invalid argument for rename_collections, must be a function or a hash') end end 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..ee99a0bde 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,7 +121,7 @@ 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(Exceptions::NotFoundError, "No such field 'unknown'") end it 'raise if renaming a field referenced in a polymorphic relation' do @@ -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(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..23df0cfd2 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(Exceptions::NotFoundError, '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..f6c8e8e7b 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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, '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..14bacbe09 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 @@ -64,7 +64,7 @@ module Validation 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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, 'Cannot add validators on a readonly field') end it 'addValidation() should throw if the field is a relation' do @@ -72,7 +72,7 @@ module Validation 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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, 'Cannot add validators on a relation, use the foreign key instead') end context 'with field selection when validating' do 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..ace23689c 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::NotFoundError, "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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, '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..16705fe74 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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, '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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, '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(ForestAdminDatasourceToolkit::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(ForestAdminDatasourceToolkit::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(ForestAdminDatasourceToolkit::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..450b1354c 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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, '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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, '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(ForestAdminDatasourceToolkit::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(ForestAdminDatasourceToolkit::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(ForestAdminDatasourceToolkit::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..9bc13b16a 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(ForestAdminDatasourceToolkit::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..f3dc1a496 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 @@ -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::ConflictError, '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(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, "Collection' 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..396e64e4b 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(ForestAdminDatasourceToolkit::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..afc4bfdbd 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(ForestAdminDatasourceToolkit::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_toolkit/lib/forest_admin_datasource_toolkit/collection.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/collection.rb index ebd85f3ae..e1d894d83 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/collection.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/collection.rb @@ -46,7 +46,9 @@ def fields end def add_field(name, field) - raise Exceptions::ForestException, "Field #{name} already defined in collection" if @schema[:fields].key?(name) + if @schema[:fields].key?(name) + raise ForestAdminAgent::Http::Exceptions::ConflictError, "Field #{name} already defined in collection" + end schema[:fields][name] = field end @@ -58,22 +60,23 @@ def add_fields(fields) end def add_action(name, action) - raise Exceptions::ForestException, "Action #{name} already defined in collection" if @schema[:actions].key?(name) + if @schema[:actions].key?(name) + raise ForestAdminAgent::Http::Exceptions::ConflictError, "Action #{name} already defined in collection" + end schema[:actions][name] = action end def add_chart(name) if @schema[:charts].include?(name) - raise Exceptions::ForestException, - "Chart #{name} already defined in collection" + raise ForestAdminAgent::Http::Exceptions::ConflictError, "Chart #{name} already defined in collection" end schema[:charts] << name end def render_chart(_caller, name, _record_id) - raise Exceptions::ForestException, "Chart #{name} is not implemented." + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "Chart #{name} is not implemented." end end end diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/aggregation.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/aggregation.rb index b84bcdf0f..31cc36148 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/aggregation.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/aggregation.rb @@ -19,7 +19,7 @@ def initialize(operation:, field: nil, groups: []) def validate(operation) return if %w[Count Sum Avg Max Min].include? operation - raise ForestException, "Aggregate operation #{operation} not allowed" + raise ForestAdminAgent::Http::Exceptions::BadRequestError, "Aggregate operation #{operation} not allowed" end def projection diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory.rb index a11ffa7b3..a644b4ed1 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory.rb @@ -19,12 +19,14 @@ def self.match_records(collection, records) def self.match_ids(collection, ids) primary_key_names = ForestAdminDatasourceToolkit::Utils::Schema.primary_keys(collection) - raise ForestException, 'Collection must have at least one primary key' if primary_key_names.empty? + if primary_key_names.empty? + raise ForestAdminAgent::Http::Exceptions::BadRequestError, 'Collection must have at least one primary key' + end primary_key_names.each do |name| operators = collection.schema[:fields][name].filter_operators unless operators.include?(Operators::EQUAL) || operators.include?(Operators::IN) - raise ForestException, "Field '#{name}' must support operators: ['Equal', 'In']" + raise ForestAdminAgent::Http::Exceptions::BadRequestError, "Field '#{name}' must support operators: ['Equal', 'In']" end end @@ -55,7 +57,7 @@ def self.from_plain_object(json) branch.conditions.length == 1 ? branch.conditions[0] : branch else - raise ForestException, 'Failed to instantiate condition tree from json' + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Failed to instantiate condition tree from json' end end diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree.rb index 9c1920d3f..9a677ec03 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree.rb @@ -53,7 +53,7 @@ def unnest false end unless every_leaf { |leaf| leaf.field.start_with?(prefix) } - raise ForestException, 'Cannot unnest condition tree.' + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Cannot unnest condition tree.' end replace_leafs { |leaf| leaf.override(field: leaf.field[(prefix.length + 1)..]) } diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_leaf.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_leaf.rb index d64f8366f..0aebef644 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_leaf.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_leaf.rb @@ -31,7 +31,8 @@ def to_h def valid_operator(value) return if Operators.exist?(value) - raise ForestException, "Invalid operators, the #{value} operator does not exist." + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, + "Invalid operators, the #{value} operator does not exist." end def inverse @@ -44,7 +45,8 @@ def inverse when Operators::PRESENT override(operator: Operators::BLANK) else - raise ForestException, "Operator: #{@operator} cannot be inverted." + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, + "Operator: #{@operator} cannot be inverted." end end diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/filter.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/filter.rb index 26b382976..6357a227c 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/filter.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/filter.rb @@ -42,7 +42,7 @@ def override(args) end def nest(prefix) - raise ForestException, "Filter can't be nested" unless nestable? + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "Filter can't be nested" unless nestable? override(condition_tree: @condition_tree&.nest(prefix)) end diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/datasource.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/datasource.rb index 0f7aa0d6f..a7f3c0e8f 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/datasource.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/datasource.rb @@ -10,37 +10,39 @@ def initialize end def get_collection(name) - raise Exceptions::ForestException, "Collection #{name} not found." unless @collections.key? name + unless @collections.key? name + raise ForestAdminAgent::Http::Exceptions::NotFoundError, "Collection #{name} not found." + end @collections[name] end def add_collection(collection) if @collections.key? collection.name - raise Exceptions::ForestException, "Collection #{collection.name} already defined in datasource" + raise ForestAdminAgent::Http::Exceptions::ConflictError, + "Collection #{collection.name} already defined in datasource" end @collections[collection.name] = collection end def render_chart(_caller, name) - raise Exceptions::ForestException, "No chart named #{name} exists on this datasource." + raise ForestAdminAgent::Http::Exceptions::NotFoundError, "No chart named #{name} exists on this datasource." end def execute_native_query(_connection_name, _query, _binds) - raise Exceptions::ForestException, 'this datasource do not support native query.' + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, 'this datasource do not support native query.' end def build_binding_symbol(_connection_name, _binds) - raise Exceptions::ForestException, 'this datasource do not support native query.' + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, 'this datasource do not support native query.' end def add_chart(chart) if @schema[:charts].any? do |c| c[:name] == chart[:name] end - raise Exceptions::ForestException, - "Chart #{chart[:name]} already defined in datasource" + raise ForestAdminAgent::Http::Exceptions::ConflictError, "Chart #{chart[:name]} already defined in datasource" end @schema[:charts] << chart diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/collection.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/collection.rb index 09ff8d313..2d0146bd1 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/collection.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/collection.rb @@ -59,7 +59,9 @@ def self.other_inverse?(field, relation_field) def self.get_field_schema(collection, field_name) fields = collection.schema[:fields] unless field_name.include?(':') - raise ForestException, "Column not found #{collection.name}.#{field_name}" unless fields.key?(field_name) + unless fields.key?(field_name) + raise ForestAdminAgent::Http::Exceptions::NotFoundError, "Column not found #{collection.name}.#{field_name}" + end return fields[field_name] end @@ -67,10 +69,12 @@ def self.get_field_schema(collection, field_name) association_name = field_name.split(':')[0] relation_schema = fields[association_name] - raise ForestException, "Relation not found #{collection.name}.#{association_name}" unless relation_schema + unless relation_schema + raise ForestAdminAgent::Http::Exceptions::NotFoundError, "Relation not found #{collection.name}.#{association_name}" + end if relation_schema.type != 'ManyToOne' && relation_schema.type != 'OneToOne' - raise ForestException, "Unexpected field type #{relation_schema.type}: #{collection.name}.#{association_name}" + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "Unexpected field type #{relation_schema.type}: #{collection.name}.#{association_name}" end get_field_schema( @@ -98,7 +102,9 @@ def self.get_value(collection, caller, id, field) def self.get_through_target(collection, relation_name) relation = collection.schema[:fields][relation_name] - raise ForestException, 'Relation must be many to many' unless relation.is_a?(ManyToManySchema) + unless relation.is_a?(ManyToManySchema) + raise ForestAdminAgent::Http::Exceptions::BadRequestError, 'Relation must be many to many' + end through_collection = collection.datasource.get_collection(relation.through_collection) through_collection.schema[:fields].select do |field_name, field| @@ -115,7 +121,9 @@ def self.get_through_target(collection, relation_name) def self.get_through_origin(collection, relation_name) relation = collection.schema[:fields][relation_name] - raise ForestException, 'Relation must be many to many' unless relation.is_a?(ManyToManySchema) + unless relation.is_a?(ManyToManySchema) + raise ForestAdminAgent::Http::Exceptions::BadRequestError, 'Relation must be many to many' + end through_collection = collection.datasource.get_collection(relation.through_collection) through_collection.schema[:fields].select do |field_name, field| diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/record.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/record.rb index 13975c913..8d633552b 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/record.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/record.rb @@ -3,7 +3,7 @@ module Utils class Record def self.primary_keys(collection, record) Schema.primary_keys(collection).map do |pk| - record[pk] || raise(ForestAdminDatasourceToolkit::Exceptions::ForestException, "Missing primary key: #{pk}") + record[pk] || raise(ForestAdminAgent::Http::Exceptions::NotFoundError, "Missing primary key: #{pk}") end end diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/schema.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/schema.rb index 579cb20cb..451868cb5 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/schema.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/schema.rb @@ -25,13 +25,13 @@ def self.primary_keys(collection) def self.get_to_many_relation(collection, relation_name) unless collection.schema[:fields].key?(relation_name) - raise Exceptions::ForestException, "Relation #{relation_name} not found" + raise ForestAdminAgent::Http::Exceptions::NotFoundError, "Relation #{relation_name} not found" end relation = collection.schema[:fields][relation_name] if relation.type != 'OneToMany' && relation.type != 'PolymorphicOneToMany' && relation.type != 'ManyToMany' - raise Exceptions::ForestException, + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "Relation #{relation_name} has invalid type should be one of OneToMany or ManyToMany." end diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/chart_validator.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/chart_validator.rb index 8240bfc82..11a502155 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/chart_validator.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/chart_validator.rb @@ -4,7 +4,7 @@ class ChartValidator def self.validate?(condition, result, key_names) if condition result_keys = result.keys.join(',') - raise ForestAdminDatasourceToolkit::Exceptions::ForestException, + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "The result columns must be named '#{key_names}' instead of '#{result_keys}'" end diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/record_validator.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/record_validator.rb index 36185ff47..abd14779f 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/record_validator.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/record_validator.rb @@ -4,13 +4,16 @@ class RecordValidator include ForestAdminDatasourceToolkit::Exceptions def self.validate(collection, record_data) - raise ForestException, 'The record data is empty' if !record_data || record_data.empty? + if !record_data || record_data.empty? + raise ForestAdminAgent::Http::Exceptions::BadRequestError, + 'The record data is empty' + end record_data.each_key do |key| schema = collection.schema[:fields][key] if !schema - raise ForestException, "Unknown field #{key}" + raise ForestAdminAgent::Http::Exceptions::NotFoundError, "Unknown field #{key}" elsif schema.type == 'Column' FieldValidator.validate(collection, key, record_data[key]) elsif ['OneToOne', 'ManyToOne'].include?(schema.type) @@ -18,7 +21,8 @@ def self.validate(collection, record_data) association = collection.datasource.get_collection(schema.foreign_collection) RecordValidator.validate(association, sub_record) else - raise ForestException, "Unexpected schema type '#{schema.type}' while traversing record" + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, + "Unexpected schema type '#{schema.type}' while traversing record" end end end diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/collection_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/collection_spec.rb index 1a9fdc5bb..f112c7fc9 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/collection_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/collection_spec.rb @@ -36,7 +36,7 @@ module ForestAdminDatasourceToolkit @collection.add_field('__duplicated__', @field) @collection.add_field('__duplicated__', @field) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminDatasourceToolkit::Exceptions::ConflictError, 'Field __duplicated__ already defined in collection' ) end diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/aggregation_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/aggregation_spec.rb index 3478eab15..8d1124694 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/aggregation_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/aggregation_spec.rb @@ -9,7 +9,7 @@ module Query expect do described_class.new(operation: 'foo') end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminDatasourceToolkit::Exceptions::BadRequestError, 'Aggregate operation foo not allowed' ) end diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory_spec.rb index f48bce947..05713789f 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory_spec.rb @@ -21,7 +21,7 @@ module ConditionTree expect do condition_tree_factory.match_ids(collection, [[]]) - end.to raise_error(ForestException, 'Collection must have at least one primary key') + end.to raise_error(Exceptions::BadRequestError, 'Collection must have at least one primary key') end it 'raises an error when the collection does not support equal and in' do @@ -30,7 +30,7 @@ module ConditionTree expect do condition_tree_factory.match_ids(collection, [[]]) - end.to raise_error(ForestException, "Field 'id' must support operators: ['Equal', 'In']") + end.to raise_error(Exceptions::BadRequestError, "Field 'id' must support operators: ['Equal', 'In']") end it 'generates matchNone with simple PK' do diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_spec.rb index 732698054..447acc8f8 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_spec.rb @@ -42,7 +42,7 @@ module Nodes condition_tree_leaf = ConditionTreeLeaf.new('column1', Operators::TODAY) expect do condition_tree_leaf.inverse - end.to raise_error(ForestException, 'Operator: today cannot be inverted.') + end.to raise_error(Exceptions::UnprocessableError, 'Operator: today cannot be inverted.') end end @@ -204,7 +204,7 @@ module Nodes it 'throws' do expect do @condition_tree_branch.unnest - end.to raise_error(ForestException, 'Cannot unnest condition tree.') + end.to raise_error(Exceptions::UnprocessableError, 'Cannot unnest condition tree.') end end diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/datasource_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/datasource_spec.rb index 1f8d29a5f..94466bd6b 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/datasource_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/datasource_spec.rb @@ -20,7 +20,7 @@ module ForestAdminDatasourceToolkit expect do @datasource.get_collection('__no_such_collection__') end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminDatasourceToolkit::Exceptions::NotFoundError, 'Collection __no_such_collection__ not found.' ) end @@ -29,7 +29,7 @@ module ForestAdminDatasourceToolkit expect do @datasource.render_chart({}, '__no_such_chart__') end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminDatasourceToolkit::Exceptions::NotFoundError, 'No chart named __no_such_chart__ exists on this datasource.' ) end @@ -38,7 +38,7 @@ module ForestAdminDatasourceToolkit expect do @datasource.add_collection(@collection) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminDatasourceToolkit::Exceptions::ConflictError, 'Collection __collection__ already defined in datasource' ) end @@ -47,7 +47,7 @@ module ForestAdminDatasourceToolkit expect do @datasource.execute_native_query('_connection_name', '_query', '_binds') end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, 'this datasource do not support native query.' ) end @@ -56,7 +56,7 @@ module ForestAdminDatasourceToolkit expect do @datasource.build_binding_symbol('_connection_name', '_binds') end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, 'this datasource do not support native query.' ) end diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/collection_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/collection_spec.rb index 6fa476765..92dc2e274 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/collection_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/collection_spec.rb @@ -221,7 +221,7 @@ module Utils expect do described_class.get_field_schema(collection_person, 'foo') - end.to raise_error(ForestException, 'Column not found Person.foo') + end.to raise_error(Exceptions::NotFoundError, 'Column not found Person.foo') end it 'get_field_schema should work with simple column' do @@ -233,14 +233,14 @@ module Utils expect do described_class.get_field_schema(collection_person, 'unknown:foo') - end.to raise_error(ForestException, 'Relation not found Person.unknown') + end.to raise_error(Exceptions::NotFoundError, 'Relation not found Person.unknown') end it 'get_field_schema should throw with invalid relation type' do expect do described_class.get_field_schema(collection_book, 'myBookPersons:bookId') - end.to raise_error(ForestException, 'Unexpected field type OneToMany: Book.myBookPersons') + end.to raise_error(Exceptions::UnprocessableError, 'Unexpected field type OneToMany: Book.myBookPersons') end it 'get_field_schema should work with relation column' do @@ -252,7 +252,7 @@ module Utils expect do described_class.get_through_target(collection_book, 'myBookPersons') - end.to raise_error(ForestException, 'Relation must be many to many') + end.to raise_error(Exceptions::UnprocessableError, 'Relation must be many to many') end it 'get_through_target should work' do @@ -263,7 +263,7 @@ module Utils expect do described_class.get_through_origin(collection_book, 'myBookPersons') - end.to raise_error(ForestException, 'Relation must be many to many') + end.to raise_error(Exceptions::UnprocessableError, 'Relation must be many to many') end it 'get_through_origin should work' do diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/record_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/record_spec.rb index 2aaa3b18c..f707da24d 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/record_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/record_spec.rb @@ -32,7 +32,7 @@ module Utils expect do described_class.primary_keys(collection, { 'notId' => 'foo' }) - end.to raise_error(ForestException, 'Missing primary key: id') + end.to raise_error(Exceptions::BadRequestError, 'Missing primary key: id') end end diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/schema_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/schema_spec.rb index 482f562f1..e2c905004 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/schema_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/schema_spec.rb @@ -71,13 +71,13 @@ module Utils describe 'get_to_many_relation' do it 'raise an error when relation do not exist' do expect { described_class.get_to_many_relation(collection, 'foo') }.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Relation foo not found' + ForestAdminDatasourceToolkit::Exceptions::NotFoundError, 'Relation foo not found' ) end it 'raise an error when the relation is not a to_many relation' do expect { described_class.get_to_many_relation(collection, 'author') }.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, 'Relation author has invalid type should be one of OneToMany or ManyToMany.' ) end diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/chart_validator_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/chart_validator_spec.rb index 6f4535f38..e5f12d15f 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/chart_validator_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/chart_validator_spec.rb @@ -7,7 +7,7 @@ module Validations it 'raises an exception' do expect do described_class.validate?(true, { key1: 1, key2: 2 }, 'key1,key2') - end.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, + end.to raise_error(ForestAdminDatasourceToolkit::Exceptions::BadRequestError, "The result columns must be named 'key1,key2' instead of 'key1,key2'") end end diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/condition_tree_validator/condition_tree_validator_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/condition_tree_validator/condition_tree_validator_spec.rb index 7159a9569..54323e13a 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/condition_tree_validator/condition_tree_validator_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/condition_tree_validator/condition_tree_validator_spec.rb @@ -60,7 +60,7 @@ module Validations expect do described_class.validate(condition_tree, collection) end.to raise_error( - Exceptions::ForestException, + Exceptions::NotFoundError, 'Column not found collection.fieldDoesNotExistInSchema' ) end @@ -121,7 +121,7 @@ module Validations expect do described_class.validate(condition_tree, collection) end.to raise_error( - Exceptions::ForestException, + Exceptions::NotFoundError, 'Column not found collection.fieldDoesNotExistInSchema' ) end diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/record_validator_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/record_validator_spec.rb index eeedfa9f8..504fad82a 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/record_validator_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/record_validator_spec.rb @@ -25,7 +25,7 @@ module Validations it 'throws an error' do expect do described_class.validate(@collection, { 'unknownField' => 'this field is not defined in the collection' }) - end.to raise_error(ForestException, 'Unknown field unknownField') + end.to raise_error(Exceptions::NotFoundError, 'Unknown field unknownField') end end @@ -41,7 +41,7 @@ module Validations it 'throws an error' do expect do described_class.validate(@collection, { 'name' => [100] }) - end.to raise_error(ForestException, "The given value has a wrong type for 'name': 100.\n Expects [\"String\", nil]") + end.to raise_error(Exceptions::BadRequestError, "The given value has a wrong type for 'name': 100.\n Expects [\"String\", nil]") end end end @@ -88,19 +88,19 @@ module Validations it 'throws an error when the record data doest not match the collection' do expect do described_class.validate(datasource.get_collection('book'), { 'relation' => { 'fieldNotExist' => 'a name' } }) - end.to raise_error(ForestException, 'Unknown field fieldNotExist') + end.to raise_error(Exceptions::NotFoundError, 'Unknown field fieldNotExist') end it 'throws an error when the relation is an empty object' do expect do described_class.validate(datasource.get_collection('book'), { 'relation' => {} }) - end.to raise_error(ForestException, 'The record data is empty') + end.to raise_error(Exceptions::BadRequestError, 'The record data is empty') end it 'throws an error when the relation is nil' do expect do described_class.validate(datasource.get_collection('book'), { 'relation' => nil }) - end.to raise_error(ForestException, 'The record data is empty') + end.to raise_error(Exceptions::BadRequestError, 'The record data is empty') end end @@ -141,7 +141,7 @@ module Validations it 'throws an error' do expect do described_class.validate(datasource.get_collection('book'), { 'relation' => { 'name' => 'a name' } }) - end.to raise_error(ForestException, "Unexpected schema type 'OneToMany' while traversing record") + end.to raise_error(Exceptions::UnprocessableError, "Unexpected schema type 'OneToMany' while traversing record") end end @@ -202,7 +202,7 @@ module Validations it 'throws an error' do expect do described_class.validate(@collection, { 'id' => '1' }) - end.to raise_error(ForestException) + end.to raise_error(Exceptions::UnprocessableError) 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..0b6451009 100644 --- a/packages/forest_admin_rails/lib/forest_admin_rails/engine.rb +++ b/packages/forest_admin_rails/lib/forest_admin_rails/engine.rb @@ -33,7 +33,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_factory = ForestAdminAgent::Builder::AgentFactory.instance agent_factory.setup(ForestAdminRails.config) load_configuration 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) From b8558e63d55b5907fb261a92243633d8dee0ecd1 Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Fri, 24 Oct 2025 12:37:15 +0200 Subject: [PATCH 15/21] fix: tests --- .../validations/record_validator.rb | 2 +- .../validations/record_validator_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/record_validator.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/record_validator.rb index abd14779f..a9633157a 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/record_validator.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/record_validator.rb @@ -1,7 +1,7 @@ module ForestAdminDatasourceToolkit module Validations class RecordValidator - include ForestAdminDatasourceToolkit::Exceptions + include ForestAdminAgent::Http::Exceptions def self.validate(collection, record_data) if !record_data || record_data.empty? diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/record_validator_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/record_validator_spec.rb index 504fad82a..1d22ebe09 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/record_validator_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/record_validator_spec.rb @@ -4,7 +4,7 @@ module ForestAdminDatasourceToolkit module Validations include ForestAdminDatasourceToolkit::Schema - include ForestAdminDatasourceToolkit::Exceptions + include ForestAdminAgent::Http::Exceptions describe RecordValidator do let(:datasource) { Datasource.new } From 2d5de32a99c3f730b608bdefadbe8f9aa01ab848 Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Fri, 24 Oct 2025 13:09:36 +0200 Subject: [PATCH 16/21] fix: tests --- .../services/smart_action_checker.rb | 3 ++- .../lib/forest_admin_agent/utils/id.rb | 2 +- .../http/forest_admin_api_requester_spec.rb | 4 ++-- .../routes/capabilities/collections_spec.rb | 2 +- .../routes/resources/list_spec.rb | 2 +- .../related/dissociate_related_spec.rb | 2 +- .../services/smart_action_checker_spec.rb | 10 +++++----- .../lib/forest_admin_agent/utils/id_spec.rb | 6 +++--- .../collection.rb | 13 +++++-------- .../components/query/aggregation.rb | 2 +- .../condition_tree/condition_tree_factory.rb | 8 +++----- .../condition_tree/nodes/condition_tree.rb | 2 +- .../nodes/condition_tree_leaf.rb | 6 ++---- .../components/query/filter.rb | 2 +- .../datasource.rb | 16 +++++++--------- .../utils/collection.rb | 18 +++++------------- .../utils/record.rb | 2 +- .../utils/schema.rb | 4 ++-- .../validations/chart_validator.rb | 2 +- .../validations/record_validator.rb | 12 ++++-------- .../collection_spec.rb | 2 +- .../components/query/aggregation_spec.rb | 2 +- .../condition_tree_factory_spec.rb | 4 ++-- .../nodes/condition_tree_spec.rb | 4 ++-- .../datasource_spec.rb | 10 +++++----- .../utils/collection_spec.rb | 10 +++++----- .../utils/record_spec.rb | 2 +- .../utils/schema_spec.rb | 4 ++-- .../validations/chart_validator_spec.rb | 2 +- .../condition_tree_validator_spec.rb | 4 ++-- .../validations/record_validator_spec.rb | 16 ++++++++-------- 31 files changed, 79 insertions(+), 99 deletions(-) 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 f49ead7e9..abb7ad889 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 @@ -111,7 +111,8 @@ 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, BusinessError => e + rescue ForestAdminDatasourceToolkit::Exceptions::ForestException, + ForestAdminAgent::Http::Exceptions::BusinessError => e # Let primary key validation errors propagate - these are actionable schema issues # Wrap other exceptions (like invalid operators) in ConflictError raise if e.message.include?('has no primary keys') 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 9b0c9f928..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 @@ -22,7 +22,7 @@ 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 ForestAdminAgent::Http::Exceptions::UnprocessableError, + raise ForestAdminAgent::Http::Exceptions::BadRequestError, "Expected #{nb_pks} primary keys, found #{nb_values}" 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 f3540b1ee..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 @@ -34,8 +34,8 @@ module Http context 'when the handle response method is called' do it 'raises an exception when the error is a BusinessError' do expect do - forest_admin_api_requester.handle_response_error(ForestAdminDatasourceToolkit::Exceptions::BusinessError.new('test')) - end.to raise_error(ForestAdminDatasourceToolkit::Exceptions::BusinessError) + 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 diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/capabilities/collections_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/capabilities/collections_spec.rb index 174c986ff..e60b0af20 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/capabilities/collections_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/capabilities/collections_spec.rb @@ -123,7 +123,7 @@ module Capabilities 'timezone' => 'Europe/Paris' } } - expect { capabilities_collections.handle_request(args) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, 'Collection unknown not found.') + expect { capabilities_collections.handle_request(args) }.to raise_error(ForestAdminAgent::Http::Exceptions::NotFoundError, 'Collection unknown not found.') end end 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 802b642b1..199dc7f66 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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, "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(ForestAdminAgent::Http::Exceptions::UnprocessableError, "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/related/dissociate_related_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/resources/related/dissociate_related_spec.rb index 6a7349bc8..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::BadRequestError, + ForestAdminAgent::Http::Exceptions::BadRequestError, 'Expected no empty id list' ) 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 5e2555219..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 @@ -610,7 +610,7 @@ module Services expect do smart_action_checker.can_execute? end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, + 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::UnprocessableError, + 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::UnprocessableError, + 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::UnprocessableError, + 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::UnprocessableError, + 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/id_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/utils/id_spec.rb index 85515280a..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::BadRequestError, + 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::BadRequestError, + 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::UnprocessableError, + ForestAdminAgent::Http::Exceptions::UnprocessableError, 'This collection has no primary key' ) end diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/collection.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/collection.rb index e1d894d83..ebd85f3ae 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/collection.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/collection.rb @@ -46,9 +46,7 @@ def fields end def add_field(name, field) - if @schema[:fields].key?(name) - raise ForestAdminAgent::Http::Exceptions::ConflictError, "Field #{name} already defined in collection" - end + raise Exceptions::ForestException, "Field #{name} already defined in collection" if @schema[:fields].key?(name) schema[:fields][name] = field end @@ -60,23 +58,22 @@ def add_fields(fields) end def add_action(name, action) - if @schema[:actions].key?(name) - raise ForestAdminAgent::Http::Exceptions::ConflictError, "Action #{name} already defined in collection" - end + raise Exceptions::ForestException, "Action #{name} already defined in collection" if @schema[:actions].key?(name) schema[:actions][name] = action end def add_chart(name) if @schema[:charts].include?(name) - raise ForestAdminAgent::Http::Exceptions::ConflictError, "Chart #{name} already defined in collection" + raise Exceptions::ForestException, + "Chart #{name} already defined in collection" end schema[:charts] << name end def render_chart(_caller, name, _record_id) - raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "Chart #{name} is not implemented." + raise Exceptions::ForestException, "Chart #{name} is not implemented." end end end diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/aggregation.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/aggregation.rb index 31cc36148..b84bcdf0f 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/aggregation.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/aggregation.rb @@ -19,7 +19,7 @@ def initialize(operation:, field: nil, groups: []) def validate(operation) return if %w[Count Sum Avg Max Min].include? operation - raise ForestAdminAgent::Http::Exceptions::BadRequestError, "Aggregate operation #{operation} not allowed" + raise ForestException, "Aggregate operation #{operation} not allowed" end def projection diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory.rb index a644b4ed1..a11ffa7b3 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory.rb @@ -19,14 +19,12 @@ def self.match_records(collection, records) def self.match_ids(collection, ids) primary_key_names = ForestAdminDatasourceToolkit::Utils::Schema.primary_keys(collection) - if primary_key_names.empty? - raise ForestAdminAgent::Http::Exceptions::BadRequestError, 'Collection must have at least one primary key' - end + raise ForestException, 'Collection must have at least one primary key' if primary_key_names.empty? primary_key_names.each do |name| operators = collection.schema[:fields][name].filter_operators unless operators.include?(Operators::EQUAL) || operators.include?(Operators::IN) - raise ForestAdminAgent::Http::Exceptions::BadRequestError, "Field '#{name}' must support operators: ['Equal', 'In']" + raise ForestException, "Field '#{name}' must support operators: ['Equal', 'In']" end end @@ -57,7 +55,7 @@ def self.from_plain_object(json) branch.conditions.length == 1 ? branch.conditions[0] : branch else - raise ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Failed to instantiate condition tree from json' + raise ForestException, 'Failed to instantiate condition tree from json' end end diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree.rb index 9a677ec03..9c1920d3f 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree.rb @@ -53,7 +53,7 @@ def unnest false end unless every_leaf { |leaf| leaf.field.start_with?(prefix) } - raise ForestAdminAgent::Http::Exceptions::UnprocessableError, 'Cannot unnest condition tree.' + raise ForestException, 'Cannot unnest condition tree.' end replace_leafs { |leaf| leaf.override(field: leaf.field[(prefix.length + 1)..]) } diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_leaf.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_leaf.rb index 0aebef644..d64f8366f 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_leaf.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_leaf.rb @@ -31,8 +31,7 @@ def to_h def valid_operator(value) return if Operators.exist?(value) - raise ForestAdminAgent::Http::Exceptions::UnprocessableError, - "Invalid operators, the #{value} operator does not exist." + raise ForestException, "Invalid operators, the #{value} operator does not exist." end def inverse @@ -45,8 +44,7 @@ def inverse when Operators::PRESENT override(operator: Operators::BLANK) else - raise ForestAdminAgent::Http::Exceptions::UnprocessableError, - "Operator: #{@operator} cannot be inverted." + raise ForestException, "Operator: #{@operator} cannot be inverted." end end diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/filter.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/filter.rb index 6357a227c..26b382976 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/filter.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/filter.rb @@ -42,7 +42,7 @@ def override(args) end def nest(prefix) - raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "Filter can't be nested" unless nestable? + raise ForestException, "Filter can't be nested" unless nestable? override(condition_tree: @condition_tree&.nest(prefix)) end diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/datasource.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/datasource.rb index a7f3c0e8f..0f7aa0d6f 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/datasource.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/datasource.rb @@ -10,39 +10,37 @@ def initialize end def get_collection(name) - unless @collections.key? name - raise ForestAdminAgent::Http::Exceptions::NotFoundError, "Collection #{name} not found." - end + raise Exceptions::ForestException, "Collection #{name} not found." unless @collections.key? name @collections[name] end def add_collection(collection) if @collections.key? collection.name - raise ForestAdminAgent::Http::Exceptions::ConflictError, - "Collection #{collection.name} already defined in datasource" + raise Exceptions::ForestException, "Collection #{collection.name} already defined in datasource" end @collections[collection.name] = collection end def render_chart(_caller, name) - raise ForestAdminAgent::Http::Exceptions::NotFoundError, "No chart named #{name} exists on this datasource." + raise Exceptions::ForestException, "No chart named #{name} exists on this datasource." end def execute_native_query(_connection_name, _query, _binds) - raise ForestAdminAgent::Http::Exceptions::UnprocessableError, 'this datasource do not support native query.' + raise Exceptions::ForestException, 'this datasource do not support native query.' end def build_binding_symbol(_connection_name, _binds) - raise ForestAdminAgent::Http::Exceptions::UnprocessableError, 'this datasource do not support native query.' + raise Exceptions::ForestException, 'this datasource do not support native query.' end def add_chart(chart) if @schema[:charts].any? do |c| c[:name] == chart[:name] end - raise ForestAdminAgent::Http::Exceptions::ConflictError, "Chart #{chart[:name]} already defined in datasource" + raise Exceptions::ForestException, + "Chart #{chart[:name]} already defined in datasource" end @schema[:charts] << chart diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/collection.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/collection.rb index 2d0146bd1..09ff8d313 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/collection.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/collection.rb @@ -59,9 +59,7 @@ def self.other_inverse?(field, relation_field) def self.get_field_schema(collection, field_name) fields = collection.schema[:fields] unless field_name.include?(':') - unless fields.key?(field_name) - raise ForestAdminAgent::Http::Exceptions::NotFoundError, "Column not found #{collection.name}.#{field_name}" - end + raise ForestException, "Column not found #{collection.name}.#{field_name}" unless fields.key?(field_name) return fields[field_name] end @@ -69,12 +67,10 @@ def self.get_field_schema(collection, field_name) association_name = field_name.split(':')[0] relation_schema = fields[association_name] - unless relation_schema - raise ForestAdminAgent::Http::Exceptions::NotFoundError, "Relation not found #{collection.name}.#{association_name}" - end + raise ForestException, "Relation not found #{collection.name}.#{association_name}" unless relation_schema if relation_schema.type != 'ManyToOne' && relation_schema.type != 'OneToOne' - raise ForestAdminAgent::Http::Exceptions::UnprocessableError, "Unexpected field type #{relation_schema.type}: #{collection.name}.#{association_name}" + raise ForestException, "Unexpected field type #{relation_schema.type}: #{collection.name}.#{association_name}" end get_field_schema( @@ -102,9 +98,7 @@ def self.get_value(collection, caller, id, field) def self.get_through_target(collection, relation_name) relation = collection.schema[:fields][relation_name] - unless relation.is_a?(ManyToManySchema) - raise ForestAdminAgent::Http::Exceptions::BadRequestError, 'Relation must be many to many' - end + raise ForestException, 'Relation must be many to many' unless relation.is_a?(ManyToManySchema) through_collection = collection.datasource.get_collection(relation.through_collection) through_collection.schema[:fields].select do |field_name, field| @@ -121,9 +115,7 @@ def self.get_through_target(collection, relation_name) def self.get_through_origin(collection, relation_name) relation = collection.schema[:fields][relation_name] - unless relation.is_a?(ManyToManySchema) - raise ForestAdminAgent::Http::Exceptions::BadRequestError, 'Relation must be many to many' - end + raise ForestException, 'Relation must be many to many' unless relation.is_a?(ManyToManySchema) through_collection = collection.datasource.get_collection(relation.through_collection) through_collection.schema[:fields].select do |field_name, field| diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/record.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/record.rb index 8d633552b..13975c913 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/record.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/record.rb @@ -3,7 +3,7 @@ module Utils class Record def self.primary_keys(collection, record) Schema.primary_keys(collection).map do |pk| - record[pk] || raise(ForestAdminAgent::Http::Exceptions::NotFoundError, "Missing primary key: #{pk}") + record[pk] || raise(ForestAdminDatasourceToolkit::Exceptions::ForestException, "Missing primary key: #{pk}") end end diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/schema.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/schema.rb index 451868cb5..579cb20cb 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/schema.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/schema.rb @@ -25,13 +25,13 @@ def self.primary_keys(collection) def self.get_to_many_relation(collection, relation_name) unless collection.schema[:fields].key?(relation_name) - raise ForestAdminAgent::Http::Exceptions::NotFoundError, "Relation #{relation_name} not found" + raise Exceptions::ForestException, "Relation #{relation_name} not found" end relation = collection.schema[:fields][relation_name] if relation.type != 'OneToMany' && relation.type != 'PolymorphicOneToMany' && relation.type != 'ManyToMany' - raise ForestAdminAgent::Http::Exceptions::UnprocessableError, + raise Exceptions::ForestException, "Relation #{relation_name} has invalid type should be one of OneToMany or ManyToMany." end diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/chart_validator.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/chart_validator.rb index 11a502155..8240bfc82 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/chart_validator.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/chart_validator.rb @@ -4,7 +4,7 @@ class ChartValidator def self.validate?(condition, result, key_names) if condition result_keys = result.keys.join(',') - raise ForestAdminAgent::Http::Exceptions::UnprocessableError, + raise ForestAdminDatasourceToolkit::Exceptions::ForestException, "The result columns must be named '#{key_names}' instead of '#{result_keys}'" end diff --git a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/record_validator.rb b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/record_validator.rb index a9633157a..36185ff47 100644 --- a/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/record_validator.rb +++ b/packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/record_validator.rb @@ -1,19 +1,16 @@ module ForestAdminDatasourceToolkit module Validations class RecordValidator - include ForestAdminAgent::Http::Exceptions + include ForestAdminDatasourceToolkit::Exceptions def self.validate(collection, record_data) - if !record_data || record_data.empty? - raise ForestAdminAgent::Http::Exceptions::BadRequestError, - 'The record data is empty' - end + raise ForestException, 'The record data is empty' if !record_data || record_data.empty? record_data.each_key do |key| schema = collection.schema[:fields][key] if !schema - raise ForestAdminAgent::Http::Exceptions::NotFoundError, "Unknown field #{key}" + raise ForestException, "Unknown field #{key}" elsif schema.type == 'Column' FieldValidator.validate(collection, key, record_data[key]) elsif ['OneToOne', 'ManyToOne'].include?(schema.type) @@ -21,8 +18,7 @@ def self.validate(collection, record_data) association = collection.datasource.get_collection(schema.foreign_collection) RecordValidator.validate(association, sub_record) else - raise ForestAdminAgent::Http::Exceptions::UnprocessableError, - "Unexpected schema type '#{schema.type}' while traversing record" + raise ForestException, "Unexpected schema type '#{schema.type}' while traversing record" end end end diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/collection_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/collection_spec.rb index f112c7fc9..1a9fdc5bb 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/collection_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/collection_spec.rb @@ -36,7 +36,7 @@ module ForestAdminDatasourceToolkit @collection.add_field('__duplicated__', @field) @collection.add_field('__duplicated__', @field) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ConflictError, + ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Field __duplicated__ already defined in collection' ) end diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/aggregation_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/aggregation_spec.rb index 8d1124694..3478eab15 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/aggregation_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/aggregation_spec.rb @@ -9,7 +9,7 @@ module Query expect do described_class.new(operation: 'foo') end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::BadRequestError, + ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Aggregate operation foo not allowed' ) end diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory_spec.rb index 05713789f..f48bce947 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/condition_tree_factory_spec.rb @@ -21,7 +21,7 @@ module ConditionTree expect do condition_tree_factory.match_ids(collection, [[]]) - end.to raise_error(Exceptions::BadRequestError, 'Collection must have at least one primary key') + end.to raise_error(ForestException, 'Collection must have at least one primary key') end it 'raises an error when the collection does not support equal and in' do @@ -30,7 +30,7 @@ module ConditionTree expect do condition_tree_factory.match_ids(collection, [[]]) - end.to raise_error(Exceptions::BadRequestError, "Field 'id' must support operators: ['Equal', 'In']") + end.to raise_error(ForestException, "Field 'id' must support operators: ['Equal', 'In']") end it 'generates matchNone with simple PK' do diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_spec.rb index 447acc8f8..732698054 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/components/query/condition_tree/nodes/condition_tree_spec.rb @@ -42,7 +42,7 @@ module Nodes condition_tree_leaf = ConditionTreeLeaf.new('column1', Operators::TODAY) expect do condition_tree_leaf.inverse - end.to raise_error(Exceptions::UnprocessableError, 'Operator: today cannot be inverted.') + end.to raise_error(ForestException, 'Operator: today cannot be inverted.') end end @@ -204,7 +204,7 @@ module Nodes it 'throws' do expect do @condition_tree_branch.unnest - end.to raise_error(Exceptions::UnprocessableError, 'Cannot unnest condition tree.') + end.to raise_error(ForestException, 'Cannot unnest condition tree.') end end diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/datasource_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/datasource_spec.rb index 94466bd6b..1f8d29a5f 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/datasource_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/datasource_spec.rb @@ -20,7 +20,7 @@ module ForestAdminDatasourceToolkit expect do @datasource.get_collection('__no_such_collection__') end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::NotFoundError, + ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Collection __no_such_collection__ not found.' ) end @@ -29,7 +29,7 @@ module ForestAdminDatasourceToolkit expect do @datasource.render_chart({}, '__no_such_chart__') end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::NotFoundError, + ForestAdminDatasourceToolkit::Exceptions::ForestException, 'No chart named __no_such_chart__ exists on this datasource.' ) end @@ -38,7 +38,7 @@ module ForestAdminDatasourceToolkit expect do @datasource.add_collection(@collection) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::ConflictError, + ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Collection __collection__ already defined in datasource' ) end @@ -47,7 +47,7 @@ module ForestAdminDatasourceToolkit expect do @datasource.execute_native_query('_connection_name', '_query', '_binds') end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, + ForestAdminDatasourceToolkit::Exceptions::ForestException, 'this datasource do not support native query.' ) end @@ -56,7 +56,7 @@ module ForestAdminDatasourceToolkit expect do @datasource.build_binding_symbol('_connection_name', '_binds') end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, + ForestAdminDatasourceToolkit::Exceptions::ForestException, 'this datasource do not support native query.' ) end diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/collection_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/collection_spec.rb index 92dc2e274..6fa476765 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/collection_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/collection_spec.rb @@ -221,7 +221,7 @@ module Utils expect do described_class.get_field_schema(collection_person, 'foo') - end.to raise_error(Exceptions::NotFoundError, 'Column not found Person.foo') + end.to raise_error(ForestException, 'Column not found Person.foo') end it 'get_field_schema should work with simple column' do @@ -233,14 +233,14 @@ module Utils expect do described_class.get_field_schema(collection_person, 'unknown:foo') - end.to raise_error(Exceptions::NotFoundError, 'Relation not found Person.unknown') + end.to raise_error(ForestException, 'Relation not found Person.unknown') end it 'get_field_schema should throw with invalid relation type' do expect do described_class.get_field_schema(collection_book, 'myBookPersons:bookId') - end.to raise_error(Exceptions::UnprocessableError, 'Unexpected field type OneToMany: Book.myBookPersons') + end.to raise_error(ForestException, 'Unexpected field type OneToMany: Book.myBookPersons') end it 'get_field_schema should work with relation column' do @@ -252,7 +252,7 @@ module Utils expect do described_class.get_through_target(collection_book, 'myBookPersons') - end.to raise_error(Exceptions::UnprocessableError, 'Relation must be many to many') + end.to raise_error(ForestException, 'Relation must be many to many') end it 'get_through_target should work' do @@ -263,7 +263,7 @@ module Utils expect do described_class.get_through_origin(collection_book, 'myBookPersons') - end.to raise_error(Exceptions::UnprocessableError, 'Relation must be many to many') + end.to raise_error(ForestException, 'Relation must be many to many') end it 'get_through_origin should work' do diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/record_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/record_spec.rb index f707da24d..2aaa3b18c 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/record_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/record_spec.rb @@ -32,7 +32,7 @@ module Utils expect do described_class.primary_keys(collection, { 'notId' => 'foo' }) - end.to raise_error(Exceptions::BadRequestError, 'Missing primary key: id') + end.to raise_error(ForestException, 'Missing primary key: id') end end diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/schema_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/schema_spec.rb index e2c905004..482f562f1 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/schema_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/utils/schema_spec.rb @@ -71,13 +71,13 @@ module Utils describe 'get_to_many_relation' do it 'raise an error when relation do not exist' do expect { described_class.get_to_many_relation(collection, 'foo') }.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::NotFoundError, 'Relation foo not found' + ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Relation foo not found' ) end it 'raise an error when the relation is not a to_many relation' do expect { described_class.get_to_many_relation(collection, 'author') }.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, + ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Relation author has invalid type should be one of OneToMany or ManyToMany.' ) end diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/chart_validator_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/chart_validator_spec.rb index e5f12d15f..6f4535f38 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/chart_validator_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/chart_validator_spec.rb @@ -7,7 +7,7 @@ module Validations it 'raises an exception' do expect do described_class.validate?(true, { key1: 1, key2: 2 }, 'key1,key2') - end.to raise_error(ForestAdminDatasourceToolkit::Exceptions::BadRequestError, + end.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, "The result columns must be named 'key1,key2' instead of 'key1,key2'") end end diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/condition_tree_validator/condition_tree_validator_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/condition_tree_validator/condition_tree_validator_spec.rb index 54323e13a..7159a9569 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/condition_tree_validator/condition_tree_validator_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/condition_tree_validator/condition_tree_validator_spec.rb @@ -60,7 +60,7 @@ module Validations expect do described_class.validate(condition_tree, collection) end.to raise_error( - Exceptions::NotFoundError, + Exceptions::ForestException, 'Column not found collection.fieldDoesNotExistInSchema' ) end @@ -121,7 +121,7 @@ module Validations expect do described_class.validate(condition_tree, collection) end.to raise_error( - Exceptions::NotFoundError, + Exceptions::ForestException, 'Column not found collection.fieldDoesNotExistInSchema' ) end diff --git a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/record_validator_spec.rb b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/record_validator_spec.rb index 1d22ebe09..eeedfa9f8 100644 --- a/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/record_validator_spec.rb +++ b/packages/forest_admin_datasource_toolkit/spec/lib/forest_admin_datasource_toolkit/validations/record_validator_spec.rb @@ -4,7 +4,7 @@ module ForestAdminDatasourceToolkit module Validations include ForestAdminDatasourceToolkit::Schema - include ForestAdminAgent::Http::Exceptions + include ForestAdminDatasourceToolkit::Exceptions describe RecordValidator do let(:datasource) { Datasource.new } @@ -25,7 +25,7 @@ module Validations it 'throws an error' do expect do described_class.validate(@collection, { 'unknownField' => 'this field is not defined in the collection' }) - end.to raise_error(Exceptions::NotFoundError, 'Unknown field unknownField') + end.to raise_error(ForestException, 'Unknown field unknownField') end end @@ -41,7 +41,7 @@ module Validations it 'throws an error' do expect do described_class.validate(@collection, { 'name' => [100] }) - end.to raise_error(Exceptions::BadRequestError, "The given value has a wrong type for 'name': 100.\n Expects [\"String\", nil]") + end.to raise_error(ForestException, "The given value has a wrong type for 'name': 100.\n Expects [\"String\", nil]") end end end @@ -88,19 +88,19 @@ module Validations it 'throws an error when the record data doest not match the collection' do expect do described_class.validate(datasource.get_collection('book'), { 'relation' => { 'fieldNotExist' => 'a name' } }) - end.to raise_error(Exceptions::NotFoundError, 'Unknown field fieldNotExist') + end.to raise_error(ForestException, 'Unknown field fieldNotExist') end it 'throws an error when the relation is an empty object' do expect do described_class.validate(datasource.get_collection('book'), { 'relation' => {} }) - end.to raise_error(Exceptions::BadRequestError, 'The record data is empty') + end.to raise_error(ForestException, 'The record data is empty') end it 'throws an error when the relation is nil' do expect do described_class.validate(datasource.get_collection('book'), { 'relation' => nil }) - end.to raise_error(Exceptions::BadRequestError, 'The record data is empty') + end.to raise_error(ForestException, 'The record data is empty') end end @@ -141,7 +141,7 @@ module Validations it 'throws an error' do expect do described_class.validate(datasource.get_collection('book'), { 'relation' => { 'name' => 'a name' } }) - end.to raise_error(Exceptions::UnprocessableError, "Unexpected schema type 'OneToMany' while traversing record") + end.to raise_error(ForestException, "Unexpected schema type 'OneToMany' while traversing record") end end @@ -202,7 +202,7 @@ module Validations it 'throws an error' do expect do described_class.validate(@collection, { 'id' => '1' }) - end.to raise_error(Exceptions::UnprocessableError) + end.to raise_error(ForestException) end end end From ac38dfbec1a6fb413344615b805d2139bc8de9d4 Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Fri, 24 Oct 2025 14:40:41 +0200 Subject: [PATCH 17/21] fix: tests --- .../routes/charts/charts.rb | 2 +- .../services/smart_action_checker.rb | 5 +++-- .../routes/capabilities/collections_spec.rb | 2 +- .../routes/charts/charts_spec.rb | 4 ++-- .../routes/resources/list_spec.rb | 2 +- .../routes/resources/native_query_spec.rb | 2 +- .../collection_customizer_spec.rb | 2 +- .../action_collection_decorator_spec.rb | 8 +++---- .../decorators/action/dynamic_field_spec.rb | 4 ++-- .../decorators/action/form_factory_spec.rb | 10 ++++----- .../binary_collection_decorator_spec.rb | 6 ++--- .../chart/chart_collection_decorator_spec.rb | 2 +- .../decorators/chart/chart_context_spec.rb | 2 +- .../chart/chart_datasource_decorator_spec.rb | 6 ++--- .../compute_collection_decorator_spec.rb | 4 ++-- ...ators_emulate_collection_decorator_spec.rb | 10 ++++----- .../publication_collection_decorator_spec.rb | 8 +++---- .../publication_datasource_decorator_spec.rb | 8 +++---- .../relation_collection_decorator_spec.rb | 22 +++++++++---------- ...me_collection_datasource_decorator_spec.rb | 12 +++++----- .../rename_field_collection_decorator_spec.rb | 8 +++---- .../segment_collection_decorator_spec.rb | 2 +- .../sort/sort_collection_decorator_spec.rb | 2 +- .../validation_collection_decorator_spec.rb | 14 ++++++------ .../write_replace/collection_basics_spec.rb | 4 ++-- .../collection_simple_creation_spec.rb | 10 ++++----- .../collection_simple_update_spec.rb | 10 ++++----- .../plugins/add_external_spec.rb | 2 +- 28 files changed, 87 insertions(+), 86 deletions(-) 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 9c13f6d73..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 @@ -197,7 +197,7 @@ def make_leaderboard return LeaderboardChart.new(result).serialize end - raise ForestAdminAgent::Http::Exceptions::UnprocessableError, + 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/services/smart_action_checker.rb b/packages/forest_admin_agent/lib/forest_admin_agent/services/smart_action_checker.rb index abb7ad889..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 @@ -88,8 +88,9 @@ def match_conditions(condition_name) "Action: #{attributes[:smart_action_id]}" ) - raise UnprocessableError, "Collection '#{collection.name}' has no primary keys. " \ - 'Actions with conditional permissions require a primary key to identify records.' + raise ForestAdminAgent::Http::Exceptions::UnprocessableError, + "Collection '#{collection.name}' has no primary keys. " \ + 'Actions with conditional permissions require a primary key to identify records.' end pk = pks[0] diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/capabilities/collections_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/capabilities/collections_spec.rb index e60b0af20..f39ec8c86 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/capabilities/collections_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/routes/capabilities/collections_spec.rb @@ -123,7 +123,7 @@ module Capabilities 'timezone' => 'Europe/Paris' } } - expect { capabilities_collections.handle_request(args) }.to raise_error(ForestAdminAgent::Http::Exceptions::NotFoundError, 'Collection unknown not found.') + expect { capabilities_collections.handle_request(args) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::ForestException, 'Collection unknown not found.') end end 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 019224d10..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::BadRequestError, 'Invalid Chart type unknown_type' + ForestAdminAgent::Http::Exceptions::BadRequestError, 'Invalid Chart type unknown_type' ) end @@ -500,7 +500,7 @@ module Charts expect do chart.handle_request(args) end.to raise_error( - ForestAdminDatasourceToolkit::Exceptions::BadRequestError, + 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 199dc7f66..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(ForestAdminAgent::Http::Exceptions::UnprocessableError, "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 4649349b8..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( - BadRequestError, 'Invalid Chart type unknown_type' + ForestAdminAgent::Http::Exceptions::BadRequestError, 'Invalid Chart type unknown_type' ) 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 7904feb25..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::BadRequestError, '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/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 2abd72c83..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::ConflictError, "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::ConflictError, "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::BadRequestError, "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::BadRequestError, "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 f486f5380..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::BadRequestError, "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::BadRequestError, "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 5a42b74b6..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::UnprocessableError) + 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::UnprocessableError) + 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::UnprocessableError) + 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::BadRequestError, "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::BadRequestError, "'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 114d744b2..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::BadRequestError) + 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::BadRequestError) + 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::BadRequestError) + 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 0b48f6ea5..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,7 +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::ConflictError, '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 1cc85655e..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::ConflictError, '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::ConflictError, '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::ConflictError, '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 7c0948d44..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::BadRequestError, "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,7 +118,7 @@ module Computed values: proc { |records| records } ) ) - end.to raise_error(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, '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 54dd6e299..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::UnprocessableError, '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::UnprocessableError, "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::UnprocessableError, '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 da811eff8..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(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, "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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, '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 a46dbe00f..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(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, 'Collection unknown not found.') - expect { @datasource_decorator.keep_collections_matching(nil, ['unknown']) }.to raise_error(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, '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( - ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, + 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(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, "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 e24bba67d..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(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, "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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, "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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, "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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, "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(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, '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(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, "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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, "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(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, '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(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, "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(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, "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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, "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 968b4607e..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::BadRequestError) + 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,13 +105,13 @@ module RenameCollection @datasource.rename_collection('book', 'book2') expect do @datasource.rename_collection('book2', 'book3') - end.to raise_error(Exceptions::UnprocessableError, '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::NotFoundError, '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 @@ -161,7 +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::BadRequestError, '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 @@ -290,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 ee99a0bde..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::NotFoundError, "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::NotFoundError, "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 23df0cfd2..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::NotFoundError, '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 f6c8e8e7b..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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, '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 14bacbe09..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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, '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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, '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 ace23689c..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(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, "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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, '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 16705fe74..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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, '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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, '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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, '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(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, "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(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, "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 450b1354c..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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, '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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, '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(ForestAdminDatasourceToolkit::Exceptions::UnprocessableError, '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(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, "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(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, "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 9bc13b16a..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::BadRequestError, '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 From 0574392d4792296a4df13e52495e63a8c3d7af29 Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Fri, 24 Oct 2025 14:51:15 +0200 Subject: [PATCH 18/21] fix: tests --- .../forest_admin_datasource_rpc/utils/rpc_client_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From 5c4f967333b201aedf5281ab047716839d7786f3 Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Fri, 24 Oct 2025 15:28:29 +0200 Subject: [PATCH 19/21] fix: tests --- .../lib/forest_admin_datasource_mongoid/datasource_spec.rb | 6 +++--- .../utils/pipeline/lookup_generator_spec.rb | 2 +- .../utils/pipeline/virtual_field_generator_spec.rb | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) 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 f3dc1a496..ca02bcfc4 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(ForestAdminDatasourceToolkit::Exceptions::ConflictError, 'Collection Post already defined in datasource') + .to raise_error(ForestAdminAgent::Http::Exceptions::ConflictError, '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(ForestAdminDatasourceToolkit::Exceptions::NotFoundError, "Collection' not found.") + expect { described_class.new }.to raise_error(ForestAdminAgent::Http::Exceptions::NotFoundError, "Collection' 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 396e64e4b..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::UnprocessableError, "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 afc4bfdbd..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::UnprocessableError, '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 From ee032a077db162482bbe779b37180ac4b8d139e4 Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Fri, 24 Oct 2025 15:40:09 +0200 Subject: [PATCH 20/21] fix: test --- .../lib/forest_admin_datasource_mongoid/datasource_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ca02bcfc4..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 @@ -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(ForestAdminAgent::Http::Exceptions::ConflictError, '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(ForestAdminAgent::Http::Exceptions::NotFoundError, "Collection' not found.") + expect { described_class.new }.to raise_error(ForestAdminAgent::Http::Exceptions::NotFoundError, "Collection 'Author' not found.") end end end From 0f5192b940254d664d0e767fad41dabc1ef58646 Mon Sep 17 00:00:00 2001 From: Enki Pontvianne Date: Fri, 24 Oct 2025 16:00:27 +0200 Subject: [PATCH 21/21] fix: agent not starting --- .../lib/forest_admin_rails/engine.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 0b6451009..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,13 +33,13 @@ class Engine < ::Rails::Engine end config.after_initialize do - Rails.error.handle(ForestAdminDatasourceToolkit::Exceptions::ForestException, - ForestAdminAgent::Http::Exceptions::BusinessError) 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