module JSONAPI module Exceptions class Error < RuntimeError attr :error_object_overrides def initialize(error_object_overrides = {}) @error_object_overrides = error_object_overrides end def create_error_object(error_defaults) JSONAPI::Error.new(error_defaults.merge(error_object_overrides)) end def errors # :nocov: raise NotImplementedError, "Subclass of Error must implement errors method" # :nocov: end end class InternalServerError < Error attr_accessor :exception def initialize(exception, error_object_overrides = {}) @exception = exception super(error_object_overrides) end def errors if JSONAPI.configuration.include_backtraces_in_errors meta = Hash.new meta[:exception] = exception.message meta[:backtrace] = exception.backtrace end [create_error_object(code: JSONAPI::INTERNAL_SERVER_ERROR, status: :internal_server_error, title: I18n.t('jsonapi-resources.exceptions.internal_server_error.title', default: 'Internal Server Error'), detail: I18n.t('jsonapi-resources.exceptions.internal_server_error.detail', default: 'Internal Server Error'), meta: meta)] end end class InvalidResource < Error attr_accessor :resource def initialize(resource, error_object_overrides = {}) @resource = resource super(error_object_overrides) end def errors [create_error_object(code: JSONAPI::INVALID_RESOURCE, status: :bad_request, title: I18n.t('jsonapi-resources.exceptions.invalid_resource.title', default: 'Invalid resource'), detail: I18n.t('jsonapi-resources.exceptions.invalid_resource.detail', default: "#{resource} is not a valid resource.", resource: resource))] end end class RecordNotFound < Error attr_accessor :id def initialize(id, error_object_overrides = {}) @id = id super(error_object_overrides) end def errors [create_error_object(code: JSONAPI::RECORD_NOT_FOUND, status: :not_found, title: I18n.translate('jsonapi-resources.exceptions.record_not_found.title', default: 'Record not found'), detail: I18n.translate('jsonapi-resources.exceptions.record_not_found.detail', default: "The record identified by #{id} could not be found.", id: id))] end end class UnsupportedMediaTypeError < Error attr_accessor :media_type def initialize(media_type, error_object_overrides = {}) @media_type = media_type super(error_object_overrides) end def errors [create_error_object(code: JSONAPI::UNSUPPORTED_MEDIA_TYPE, status: :unsupported_media_type, title: I18n.translate('jsonapi-resources.exceptions.unsupported_media_type.title', default: 'Unsupported media type'), detail: I18n.translate('jsonapi-resources.exceptions.unsupported_media_type.detail', default: "All requests that create or update must use the '#{JSONAPI::MEDIA_TYPE}' Content-Type. This request specified '#{media_type}'.", needed_media_type: JSONAPI::MEDIA_TYPE, media_type: media_type))] end end class NotAcceptableError < Error attr_accessor :media_type def initialize(media_type, error_object_overrides = {}) @media_type = media_type super(error_object_overrides) end def errors [create_error_object(code: JSONAPI::NOT_ACCEPTABLE, status: :not_acceptable, title: I18n.translate('jsonapi-resources.exceptions.not_acceptable.title', default: 'Not acceptable'), detail: I18n.translate('jsonapi-resources.exceptions.not_acceptable.detail', default: "All requests must use the '#{JSONAPI::MEDIA_TYPE}' Accept without media type parameters. This request specified '#{media_type}'.", needed_media_type: JSONAPI::MEDIA_TYPE, media_type: media_type))] end end class HasManyRelationExists < Error attr_accessor :id def initialize(id, error_object_overrides = {}) @id = id super(error_object_overrides) end def errors [create_error_object(code: JSONAPI::RELATION_EXISTS, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.has_many_relation.title', default: 'Relation exists'), detail: I18n.translate('jsonapi-resources.exceptions.has_many_relation.detail', default: "The relation to #{id} already exists.", id: id))] end end class BadRequest < Error def initialize(exception) @exception = exception end def errors [JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.bad_request.title', default: 'Bad Request'), detail: I18n.translate('jsonapi-resources.exceptions.bad_request.detail', default: @exception))] end end class InvalidRequestFormat < Error def errors [JSONAPI::Error.new(code: JSONAPI::BAD_REQUEST, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.invalid_request_format.title', default: 'Bad Request'), detail: I18n.translate('jsonapi-resources.exceptions.invalid_request_format.detail', default: 'Request must be a hash'))] end end class ToManySetReplacementForbidden < Error def errors [create_error_object(code: JSONAPI::FORBIDDEN, status: :forbidden, title: I18n.translate('jsonapi-resources.exceptions.to_many_set_replacement_forbidden.title', default: 'Complete replacement forbidden'), detail: I18n.translate('jsonapi-resources.exceptions.to_many_set_replacement_forbidden.detail', default: 'Complete replacement forbidden for this relationship'))] end end class InvalidFiltersSyntax < Error attr_accessor :filters def initialize(filters, error_object_overrides = {}) @filters = filters super(error_object_overrides) end def errors [create_error_object(code: JSONAPI::INVALID_FILTERS_SYNTAX, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.invalid_filter_syntax.title', default: 'Invalid filters syntax'), detail: I18n.translate('jsonapi-resources.exceptions.invalid_filter_syntax.detail', default: "#{filters} is not a valid syntax for filtering.", filters: filters))] end end class FilterNotAllowed < Error attr_accessor :filter def initialize(filter, error_object_overrides = {}) @filter = filter super(error_object_overrides) end def errors [create_error_object(code: JSONAPI::FILTER_NOT_ALLOWED, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.filter_not_allowed.title', default: 'Filter not allowed'), detail: I18n.translate('jsonapi-resources.exceptions.filter_not_allowed.detail', default: "#{filter} is not allowed.", filter: filter))] end end class InvalidFilterValue < Error attr_accessor :filter, :value def initialize(filter, value, error_object_overrides = {}) @filter = filter @value = value super(error_object_overrides) end def errors [create_error_object(code: JSONAPI::INVALID_FILTER_VALUE, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.invalid_filter_value.title', default: 'Invalid filter value'), detail: I18n.translate('jsonapi-resources.exceptions.invalid_filter_value.detail', default: "#{value} is not a valid value for #{filter}.", value: value, filter: filter))] end end class InvalidFieldValue < Error attr_accessor :field, :value def initialize(field, value, error_object_overrides = {}) @field = field @value = value super(error_object_overrides) end def errors [create_error_object(code: JSONAPI::INVALID_FIELD_VALUE, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.invalid_field_value.title', default: 'Invalid field value'), detail: I18n.translate('jsonapi-resources.exceptions.invalid_field_value.detail', default: "#{value} is not a valid value for #{field}.", value: value, field: field))] end end class InvalidFieldFormat < Error def errors [create_error_object(code: JSONAPI::INVALID_FIELD_FORMAT, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.invalid_field_format.title', default: 'Invalid field format'), detail: I18n.translate('jsonapi-resources.exceptions.invalid_field_format.detail', default: 'Fields must specify a type.'))] end end class InvalidDataFormat < Error def errors [create_error_object(code: JSONAPI::INVALID_DATA_FORMAT, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.invalid_data_format.title', default: 'Invalid data format'), detail: I18n.translate('jsonapi-resources.exceptions.invalid_data_format.detail', default: 'Data must be a hash.'))] end end class InvalidLinksObject < Error def errors [create_error_object(code: JSONAPI::INVALID_LINKS_OBJECT, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.invalid_links_object.title', default: 'Invalid Links Object'), detail: I18n.translate('jsonapi-resources.exceptions.invalid_links_object.detail', default: 'Data is not a valid Links Object.'))] end end class TypeMismatch < Error attr_accessor :type def initialize(type, error_object_overrides = {}) @type = type super(error_object_overrides) end def errors [create_error_object(code: JSONAPI::TYPE_MISMATCH, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.type_mismatch.title', default: 'Type Mismatch'), detail: I18n.translate('jsonapi-resources.exceptions.type_mismatch.detail', default: "#{type} is not a valid type for this operation.", type: type))] end end class InvalidField < Error attr_accessor :field, :type def initialize(type, field, error_object_overrides = {}) @field = field @type = type super(error_object_overrides) end def errors [create_error_object(code: JSONAPI::INVALID_FIELD, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.invalid_field.title', default: 'Invalid field'), detail: I18n.translate('jsonapi-resources.exceptions.invalid_field.detail', default: "#{field} is not a valid field for #{type}.", field: field, type: type))] end end class InvalidInclude < Error attr_accessor :relationship, :resource def initialize(resource, relationship, error_object_overrides = {}) @resource = resource @relationship = relationship super(error_object_overrides) end def errors [create_error_object(code: JSONAPI::INVALID_INCLUDE, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.invalid_include.title', default: 'Invalid field'), detail: I18n.translate('jsonapi-resources.exceptions.invalid_include.detail', default: "#{relationship} is not a valid relationship of #{resource}", relationship: relationship, resource: resource))] end end class InvalidSortCriteria < Error attr_accessor :sort_criteria, :resource def initialize(resource, sort_criteria, error_object_overrides = {}) @resource = resource @sort_criteria = sort_criteria super(error_object_overrides) end def errors [create_error_object(code: JSONAPI::INVALID_SORT_CRITERIA, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.invalid_sort_criteria.title', default: 'Invalid sort criteria'), detail: I18n.translate('jsonapi-resources.exceptions.invalid_sort_criteria.detail', default: "#{sort_criteria} is not a valid sort criteria for #{resource}", sort_criteria: sort_criteria, resource: resource))] end end class ParameterNotAllowed < Error attr_accessor :param def initialize(param, error_object_overrides = {}) @param = param super(error_object_overrides) end def errors [create_error_object(code: JSONAPI::PARAM_NOT_ALLOWED, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.parameter_not_allowed.title', default: 'Param not allowed'), detail: I18n.translate('jsonapi-resources.exceptions.parameter_not_allowed.detail', default: "#{param} is not allowed.", param: param))] end end class ParameterMissing < Error attr_accessor :param def initialize(param, error_object_overrides = {}) @param = param super(error_object_overrides) end def errors [create_error_object(code: JSONAPI::PARAM_MISSING, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.parameter_missing.title', default: 'Missing Parameter'), detail: I18n.translate('jsonapi-resources.exceptions.parameter_missing.detail', default: "The required parameter, #{param}, is missing.", param: param))] end end class KeyNotIncludedInURL < Error attr_accessor :key def initialize(key, error_object_overrides = {}) @key = key super(error_object_overrides) end def errors [create_error_object(code: JSONAPI::KEY_NOT_INCLUDED_IN_URL, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.key_not_included_in_url.title', default: 'Key is not included in URL'), detail: I18n.translate('jsonapi-resources.exceptions.key_not_included_in_url.detail', default: "The URL does not support the key #{key}", key: key))] end end class MissingKey < Error def errors [create_error_object(code: JSONAPI::KEY_ORDER_MISMATCH, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.missing_key.title', default: 'A key is required'), detail: I18n.translate('jsonapi-resources.exceptions.missing_key.detail', default: 'The resource object does not contain a key.'))] end end class RecordLocked < Error attr_accessor :message def initialize(message, error_object_overrides = {}) @message = message super(error_object_overrides) end def errors [create_error_object(code: JSONAPI::LOCKED, status: :locked, title: I18n.translate('jsonapi-resources.exceptions.record_locked.title', default: 'Locked resource'), detail: "#{message}")] end end class ValidationErrors < Error attr_reader :error_messages, :error_metadata, :resource_relationships def initialize(resource, error_object_overrides = {}) @error_messages = resource.model_error_messages @error_metadata = resource.validation_error_metadata @resource_relationships = resource.class._relationships.keys @key_formatter = JSONAPI.configuration.key_formatter super(error_object_overrides) end def format_key(key) @key_formatter.format(key) end def errors error_messages.flat_map do |attr_key, messages| messages.map { |message| json_api_error(attr_key, message) } end end private def json_api_error(attr_key, message) create_error_object(code: JSONAPI::VALIDATION_ERROR, status: :unprocessable_entity, title: message, detail: "#{format_key(attr_key)} - #{message}", source: { pointer: pointer(attr_key) }, meta: metadata_for(attr_key, message)) end def metadata_for(attr_key, message) return if error_metadata.nil? error_metadata[attr_key] ? error_metadata[attr_key][message] : nil end def pointer(attr_or_relationship_name) formatted_attr_or_relationship_name = format_key(attr_or_relationship_name) if resource_relationships.include?(attr_or_relationship_name) "/data/relationships/#{formatted_attr_or_relationship_name}" else "/data/attributes/#{formatted_attr_or_relationship_name}" end end end class SaveFailed < Error def errors [create_error_object(code: JSONAPI::SAVE_FAILED, status: :unprocessable_entity, title: I18n.translate('jsonapi-resources.exceptions.save_failed.title', default: 'Save failed or was cancelled'), detail: I18n.translate('jsonapi-resources.exceptions.save_failed.detail', default: 'Save failed or was cancelled'))] end end class InvalidPageObject < Error def errors [create_error_object(code: JSONAPI::INVALID_PAGE_OBJECT, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.invalid_page_object.title', default: 'Invalid Page Object'), detail: I18n.translate('jsonapi-resources.exceptions.invalid_page_object.detail', default: 'Invalid Page Object.'))] end end class PageParametersNotAllowed < Error attr_accessor :params def initialize(params, error_object_overrides = {}) @params = params super(error_object_overrides) end def errors params.collect do |param| create_error_object(code: JSONAPI::PARAM_NOT_ALLOWED, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.page_parameters_not_allowed.title', default: 'Page parameter not allowed'), detail: I18n.translate('jsonapi-resources.exceptions.page_parameters_not_allowed.detail', default: "#{param} is not an allowed page parameter.", param: param)) end end end class InvalidPageValue < Error attr_accessor :page, :value def initialize(page, value, error_object_overrides = {}) @page = page @value = value super(error_object_overrides) end def errors [create_error_object(code: JSONAPI::INVALID_PAGE_VALUE, status: :bad_request, title: I18n.translate('jsonapi-resources.exceptions.invalid_page_value.title', default: 'Invalid page value'), detail: I18n.translate('jsonapi-resources.exceptions.invalid_page_value.detail', default: "#{value} is not a valid value for #{page} page parameter.", value: value, page: page))] end end end end