card/lib/card/error.rb
# -*- encoding : utf-8 -*-
class StandardError
MAX_BACKTRACE_LINES = 20
def report
Rails.logger.info "exception = #{self.class}: #{message}"
Rails.logger.debug backtrace[0..MAX_BACKTRACE_LINES].join("\n")
end
end
class Card
# exceptions and errors.
# (arguably most of these should be Card::Exception)
class Error < StandardError
cattr_accessor :current
class_attribute :status_code, :view
attr_writer :backtrace
self.view = :errors
self.status_code = 422
attr_accessor :card
def initialize message=nil
if message.is_a? Card
self.card = message
message = message_from_card
end
super message
end
def message_from_card
::I18n.t :lib_exception_for_card, cardname: card.name, message: card_message_text
end
def backtrace
@backtrace || super
end
def card_message_text
card.errors.first&.message
end
# error attributable to code (as opposed to card configuration)
class ServerError < Error
def self.view
debugger_on? ? :debug_server_error : :server_error
end
def self.status_code
# Errors with status code 900 are displayed as modal instead of inside
# the "card-notice" div``
debugger_on? ? 900 : 500
end
def self.debugger_on?
Card::Codename[:debugger] && Card[:debugger]&.content =~ /on/
end
def report
super
card&.notable_exception_raised
end
end
# error whose message can be shown to any user
class UserError < Error
cattr_accessor :user_error_classes
self.user_error_classes = [self,
ActionController::BadRequest,
ActionController::MissingFile,
ActiveRecord::RecordNotFound,
ActiveRecord::RecordInvalid]
end
# error in CQL query
class BadQuery < UserError
end
class BadAddress < UserError
self.status_code = 404
self.view = :bad_address
end
# card not found
class NotFound < UserError
self.status_code = 404
self.view = :not_found
end
class CodenameNotFound < NotFound
end
# two editors altering the same card at once
class EditConflict < UserError
self.status_code = 409
self.view = :conflict
end
# permission errors
class PermissionDenied < UserError
self.status_code = 403
self.view = :denial
def card_message_text
card.errors[:permission_denied]
end
end
# exception class for aborting card actions
class Abort < StandardError
attr_reader :status
def report
Rails.logger.debug "aborting: #{message}"
end
def initialize status, msg=""
@status = status
super msg
end
end
# associating views with exceptions
class << self
KEY_MAP = { permission_denied: PermissionDenied,
conflict: EditConflict }.freeze
def report exception, card
e = cardify_exception exception, card
self.current = e
e.report
e
end
def cardify_exception exception, card
card_exception =
if exception.is_a? Card::Error
exception
else
card_error_class(exception, card).new exception.message
end
card_exception.card ||= card
card_exception.backtrace ||= exception.backtrace
add_card_errors card, card_exception if card.errors.empty?
card_exception
end
def add_card_errors card, exception
label = exception.class.to_s.split("::").last
card.errors.add label, exception.message
end
def card_error_class exception, card
# "simple" error messages are visible to end users and are generally not
# treated as software bugs (though they may be "shark" bugs)
case exception
when ActiveRecord::RecordInvalid
invalid_card_error_class card
when ActiveRecord::RecordNotFound, ActionController::MissingFile
Card::Error::NotFound
else
Card::Error::ServerError
end
end
def invalid_card_error_class card
KEY_MAP.each do |key, klass|
return klass if card.errors.key? key
end
Card::Error
end
end
end
end