./lib/lux/error/error.rb in lux-fw-0.5.37 vs ./lib/lux/error/error.rb in lux-fw-0.6.2

- old
+ new

@@ -8,186 +8,241 @@ # 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 -class Lux::Error < StandardError - class AutoRaise < Lux::Error - end + # https://httpstatuses.com/ + CODE_LIST ||= { + # 1×× Informational + 100 => { name: 'Continue' }, + 101 => { name: 'Switching Protocols' }, + 102 => { name: 'Processing' }, - # 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' }, - # 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' }, - # 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' }, - # 4×× Client Error - 400 => { name: 'Bad Request', code: :bad_request }, - 401 => { name: 'Unauthorized', code: :unauthorized }, - 402 => { name: 'Payment Required', code: :payment_required }, - 403 => { name: 'Forbidden', code: :forbidden }, - 404 => { name: 'Document Not Found', code: :not_found }, - 405 => { name: 'Method Not Allowed', code: :method_not_allowed }, - 406 => { name: 'Not Acceptable', code: :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' }, + } - # 5×× Server Error - 500 => { name: 'Internal Server Error', code: :internal_server_error }, - 501 => { name: 'Not Implemented', code: :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 - # e = Lux::Error.not_found('foo') - CODE_LIST.each do |status, data| - if data[:code] - define_singleton_method(data[:code]) do |message=nil| - error = new status, message - raise error if Lux::Error::AutoRaise === error - error + 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 - end - class << self - # template to show full error page - def render text, status=500 - Lux.current.response.status status - Lux.current.response.body Lux.config.server_error_template.call(text) - throw :done - end + class << self + # template to show full error page + def render error + error = StandardError.new(error) if error.is_a?(String) - # render error inline or break in production - def inline name, error=nil - error ||= $! + code = error.respond_to?(:code) ? error.code : 500 + Lux.current.response.status code - unless Lux.config(:dump_errors) - key = log error - render "Lux inline error: %s\n\nkey: %s" % [error.message, key] + 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 &mdash; <a href="https://httpstatuses.com/#{code}" target="http_error">#{code}</a>] + n.push %[ &dash; #{error.name}] if error.respond_to?(:name) + end + n.push inline error + end + end + ) end - name ||= 'Undefined name' - msg = error.message.to_s.gsub('","',%[",\n "]).gsub('<','&lt;') + # render error inline + def inline object, msg = nil + error, message = if object.is_a?(String) + [nil, object] + else + [object, object.message] + end - dmp = split_backtrace error + error_key = error ? log(error) : nil + message = message.to_s.gsub('","',%[",\n "]).gsub('<','&lt;') - dmp[0] = dmp[0].map { |_| _ = _.split(':', 3); '<b>%s</b> - %s - %s' % _ } + 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 - log error + if error && Lux.env.show_errors? + n.hr + n.push mark_backtrace(error, html: true).join("\n") + end + end + end - <<~TEXT - <pre style="color:red; background:#eee; padding:10px; font-family:'Lucida Console'; line-height:15pt; font-size:11pt;"> - <b style="font-size:110%;">#{name}</b> + # 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 - <b>#{error}: #{msg}</b> + # prepare backtrace for better render + def split_backtrace error + # split app log rest of the log + dmp = [[error.class, error.message], [], []] - #{dmp[0].join("\n")} + root = Lux.root.to_s - #{dmp[1].join("\n")} - </pre> - TEXT - end + (error.backtrace || caller).each do |line| + line = line.sub(root, '.') + dmp[line[0,1] == '.' ? 1 : 2].push line + end - def report code, msg=nil - e = Integer === code ? Lux::Error.new(code) : Lux::Error.send(code) - e.message = msg if msg - raise e - end + dmp + end - def log error - Lux.config.error_logger.call error - end + def mark_backtrace error, html: false + return ['no backtrace present'] unless error && error.backtrace - def split_backtrace error - # split app log rest of the log - dmp = [[], []] + root = Lux.root.to_s - root = Lux.root.to_s + error + .backtrace + .map do |line| + path = line.split(':in').first + path = path.sub(/^\.\//, root+'/') - error.backtrace.each do |line| - line = line.sub(root, '.') - dmp[line[0,1] == '.' ? 0 : 1].push line + edit = html ? %[ &bull; <a href="subl://open?url=file:/#{path}">edit</a>] : '' + line = line.sub(root, '.') + (line[0,1] != '/' ? (html ? line.tag(:b) : line) : line) + edit + end end - dmp + # 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 - end - ### + ### - attr_accessor :message + attr_accessor :message - def initialize code_num, message=nil - self.code = code_num - @message = message || CODE_LIST[code_num][:name] - end + def initialize *args + if args.first.is_a?(Integer) + self.code = args.shift + end - def code - # 400 is a default - @code || 400 - end + self.message = args.shift || name + end - def code= num - @code = num.to_i + def code + # 400 is a default + @code || 400 + end - raise 'Status code %s not found' % @code unless CODE_LIST[@code] - end + def name + CODE_LIST[code][:name] + end - def render - self.class.render message, code + def code= num + @code = num.to_i + + raise 'Status code %s not found' % @code unless CODE_LIST[@code] + end end end -