# frozen_string_literal: true # https://en.wikipedia.org/wiki/List_of_HTTP_status_codes # default error handler for lux # e = Lux::Error.new 404 # e.code => 404 # e.message => 'Not Found' # # e = Lux::Error.not_found('foo') # e.code => 404 # e.message => foo module Lux class Error < StandardError class AutoRaise < Lux::Error end # https://httpstatuses.com/ CODE_LIST ||= { # 1×× Informational 100 => { name: 'Continue' }, 101 => { name: 'Switching Protocols' }, 102 => { name: 'Processing' }, # 2×× Success 200 => { name: 'OK' }, 201 => { name: 'Created' }, 202 => { name: 'Accepted' }, 203 => { name: 'Non-authoritative Information' }, 204 => { name: 'No Content' }, 205 => { name: 'Reset Content' }, 206 => { name: 'Partial Content' }, 207 => { name: 'Multi-Status' }, 208 => { name: 'Already Reported' }, 226 => { name: 'IM Used' }, # 3×× Redirection 300 => { name: 'Multiple Choices' }, 301 => { name: 'Moved Permanently' }, 302 => { name: 'Found' }, 303 => { name: 'See Other' }, 304 => { name: 'Not Modified' }, 305 => { name: 'Use Proxy' }, 307 => { name: 'Temporary Redirect' }, 308 => { name: 'Permanent Redirect' }, # 4×× Client Error 400 => { name: 'Bad Request', short: :bad_request }, 401 => { name: 'Unauthorized', short: :unauthorized }, 402 => { name: 'Payment Required', short: :payment_required }, 403 => { name: 'Forbidden', short: :forbidden }, 404 => { name: 'Document Not Found', short: :not_found }, 405 => { name: 'Method Not Allowed', short: :method_not_allowed }, 406 => { name: 'Not Acceptable', short: :not_acceptable }, 407 => { name: 'Proxy Authentication Required' }, 408 => { name: 'Request Timeout' }, 409 => { name: 'Conflict' }, 410 => { name: 'Gone' }, 411 => { name: 'Length Required' }, 412 => { name: 'Precondition Failed' }, 413 => { name: 'Payload Too Large' }, 414 => { name: 'Request-URI Too Long' }, 415 => { name: 'Unsupported Media Type' }, 416 => { name: 'Requested Range Not Satisfiable' }, 417 => { name: 'Expectation Failed' }, 418 => { name: 'I\'m a teapot' }, 421 => { name: 'Misdirected Request' }, 422 => { name: 'Unprocessable Entity' }, 423 => { name: 'Locked' }, 424 => { name: 'Failed Dependency' }, 426 => { name: 'Upgrade Required' }, 428 => { name: 'Precondition Required' }, 429 => { name: 'Too Many Requests' }, 431 => { name: 'Request Header Fields Too Large' }, 444 => { name: 'Connection Closed Without Response' }, 451 => { name: 'Unavailable For Legal Reasons' }, 499 => { name: 'Client Closed Request' }, # 5×× Server Error 500 => { name: 'Internal Server Error', short: :internal_server_error }, 501 => { name: 'Not Implemented', short: :not_implemented }, 502 => { name: 'Bad Gateway' }, 503 => { name: 'Service Unavailable' }, 504 => { name: 'Gateway Timeout' }, 505 => { name: 'HTTP Version Not Supported' }, 506 => { name: 'Variant Also Negotiates' }, 507 => { name: 'Insufficient Storage' }, 508 => { name: 'Loop Detected' }, 510 => { name: 'Not Extended' }, 511 => { name: 'Network Authentication Required' }, 599 => { name: 'Network Connect Timeout Error' }, } # e = Lux::Error.not_found('foo') CODE_LIST.each do |status, data| if data[:short] define_singleton_method(data[:short]) do |message=nil| error = Lux::Error.new status, message if self == Lux::Error::AutoRaise Lux.current.response.status status Lux.log " error.#{data[:code]} in #{Lux.app_caller}" raise error end error end end end class << self # template to show full error page def render error error = StandardError.new(error) if error.is_a?(String) code = error.respond_to?(:code) ? error.code : 500 Lux.current.response.status code Lux.current.response.body( HtmlTag.html do |n| n.head do |n| n.title 'Lux error' end n.body style: "margin: 20px 20px 20px 140px; background-color:#fdd; font-size: 14pt; font-family: sans-serif;" do |n| n.img src: "https://i.imgur.com/Zy7DLXU.png", style: "width: 100px; position: absolute; margin-left: -120px;" n.h4 do |n| n.push %[HTTP Error — #{code}] n.push %[ ‐ #{error.name}] if error.respond_to?(:name) end n.push inline error end end ) end # render error inline def inline object, msg = nil error, message = if object.is_a?(String) [nil, object] else [object, object.message] end error_key = error ? log(error) : nil message = message.to_s.gsub('","',%[",\n "]).gsub('<','<') HtmlTag.pre(class: 'lux-inline-error', style: 'background: #fff; margin-top: 10px; padding: 10px; font-size: 14px; border: 2px solid #600; line-height: 20px;') do |n| n.h3 '%s : %s' % [error.class, message] n.p msg if msg n.p 'Key: %s' % error_key if error_key n.p 'Description: %s' % error.description if error && error.respond_to?(:description) && error.description if error && Lux.env.show_errors? n.hr n.push mark_backtrace(error, html: true).join("\n") end end end # clear osx screen :) def clear_screen time = 0 last = (Thread.current[:_lux_clear_screen] ||= 1.day.ago) if last < Time.now - time Thread.current[:_lux_clear_screen] = Time.now print "\e[H\e[2J\e[3J" end end # prepare backtrace for better render def split_backtrace error # split app log rest of the log dmp = [[error.class, error.message], [], []] root = Lux.root.to_s (error.backtrace || caller).each do |line| line = line.sub(root, '.') dmp[line[0,1] == '.' ? 1 : 2].push line end dmp end def mark_backtrace error, html: false return ['no backtrace present'] unless error && error.backtrace root = Lux.root.to_s error .backtrace .map do |line| path = line.split(':in').first path = path.sub(/^\.\//, root+'/') edit = html ? %[ • edit] : '' line = line.sub(root, '.') (line[0,1] != '/' ? (html ? line.tag(:b) : line) : line) + edit end end # show in stdout def screen error return unless Lux.env.show_errors? data = split_backtrace(error) if error.class == Lux::Error Lux.info "Lux error: #{error.message} (#{error.code}) - #{data[1][0]}" else data[2] = data[2][0,5] ap data end end def log err if Lux.config.error_logger Lux.config.error_logger.call err end end end ### attr_accessor :message def initialize *args if args.first.is_a?(Integer) self.code = args.shift end self.message = args.shift || name end def code # 400 is a default @code || 400 end def name CODE_LIST[code][:name] end def code= num @code = num.to_i raise 'Status code %s not found' % @code unless CODE_LIST[@code] end end end