concise_errors 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 746b9cdaa164daed16b8bc70adba8cb822ef52c9039a441ab1fc29d92c53e48f
4
+ data.tar.gz: 6af78b52ef0ce3cae853b72a379e9c744b55c06d6324acfd66d776d7870dc6c1
5
+ SHA512:
6
+ metadata.gz: 4695c72ba6c43566bc957f975fb1c190f1bcbd66572d847554f8ea725fec6686be2990e8862c9b040f7367f3a169a1699a501b083965885c173ac69574410471
7
+ data.tar.gz: 4097761cb0f2a54d2d2f3c40eab9870c842c00f7a4efbbb82c4d31baa5316077e8160656ce891e96d76f9cd4134343a9cab61b33d69fb3f40320b049897b70be
data/.rubocop.yml ADDED
@@ -0,0 +1,10 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.0
3
+ NewCops: enable
4
+ SuggestExtensions: false
5
+
6
+ Style/StringLiterals:
7
+ EnforcedStyle: double_quotes
8
+
9
+ Style/StringLiteralsInInterpolation:
10
+ EnforcedStyle: double_quotes
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-10-22
4
+
5
+ - Initial release
@@ -0,0 +1,21 @@
1
+ # Code of Conduct
2
+
3
+ This guideline exists to support a safe, respectful, and productive place for anyone contributing to the ConciseErrors project.
4
+
5
+ ## Scope
6
+
7
+ This Code of Conduct applies to all collaborative spaces, including mailing lists, issues, patches/pull requests, commit comments, chats, and events.
8
+
9
+ ## Expected Behavior
10
+
11
+ - Be tolerant of opposing views.
12
+ - Assume good intentions when interpreting others’ words and actions.
13
+ - Keep language and actions free of personal attacks or disparaging remarks.
14
+
15
+ ## Unacceptable Behavior
16
+
17
+ - Harassment, or behavior that could reasonably be considered harassment.
18
+
19
+ ## Reporting
20
+
21
+ Report concerns to the maintainers at obiefernandez@gmail.com. Reports will be reviewed promptly and fairly, and the privacy of reporters will be respected.
data/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # ConciseErrors
2
+
3
+ ConciseErrors swaps Rails’ `ActionDispatch::DebugExceptions` middleware with a compact error page that focuses on the class, message, and a truncated backtrace. It was designed to give human developers and AI assistants the minimum viable context needed to diagnose a failure without the noise of the default HTML-heavy debugger.
4
+
5
+ ## Features
6
+
7
+ - Tiny plain-text payloads by default (no inline assets, no copious markup).
8
+ - Optional single-file HTML view optimised for dark/light mode without external CSS.
9
+ - Configurable backtrace depth with an omission indicator.
10
+ - Automatic fallback to `text/plain` when the request is `xhr?` or clients negotiate non-HTML `Accept` headers.
11
+
12
+ ## Installation
13
+
14
+ Add the gem to your Gemfile:
15
+
16
+ ```ruby
17
+ gem "concise_errors"
18
+ ```
19
+
20
+ Run `bundle install` and restart your Rails server. ConciseErrors automatically swaps the middleware when the gem loads.
21
+
22
+ ## Configuration
23
+
24
+ ConciseErrors ships with opinionated defaults — HTML output, no CSS, and Web Console middleware is automaticallyremoved in development — so simply installing the gem is enough. Override anything you need from `config/application.rb` or an environment-specific config:
25
+
26
+ ```ruby
27
+ # config/environments/development.rb
28
+ Rails.application.configure do
29
+ config.concise_errors.tap do |cfg|
30
+ cfg.stack_trace_lines = 5 # default: 10
31
+ cfg.format = :html # available: :text (default) or :html
32
+ cfg.enabled = true # flip to false to restore the stock debug page
33
+ cfg.application_root = Rails.root.to_s # optional: trim this prefix from traces
34
+ cfg.logger = Rails.logger # optional: reuse your preferred logger
35
+ end
36
+ end
37
+ ```
38
+
39
+ You can also steer the default format via `ENV["CONCISE_ERRORS_FORMAT"]` (`text` or `html`).
40
+
41
+ ConciseErrors only affects the debug middleware (the screen you see when `config.consider_all_requests_local` is true). Production 500 pages continue to use whatever `ActionDispatch::ShowExceptions` is configured to serve.
42
+
43
+ ## Sample Output
44
+
45
+ Plain text format (default):
46
+
47
+ ```
48
+ RuntimeError: bang
49
+ HTTP 500 (Internal Server Error)
50
+
51
+ app/controllers/widgets_controller.rb:12:in `show'
52
+ app/controllers/widgets_controller.rb:12:in `show'
53
+ ...
54
+ ```
55
+
56
+ HTML format renders the same content inside a single `<pre>` block with minimal inline styling suitable for AI agents that prefer HTML responses.
57
+
58
+ ## Development
59
+
60
+ After checking out the repo run:
61
+
62
+ ```bash
63
+ bin/setup
64
+ bundle exec rake test
65
+ ```
66
+
67
+ `bin/console` opens an IRB session with the gem loaded. To try the middleware in a real app, add a `path:` entry to a Rails application's Gemfile pointing at your local clone.
68
+
69
+ Before cutting a release:
70
+
71
+ 1. Update `ConciseErrors::VERSION` and `CHANGELOG.md`.
72
+ 2. Run `bundle exec rake release` to tag, build, and push the gem.
73
+
74
+ ## Contributing
75
+
76
+ Bug reports and pull requests are welcome on GitHub at https://github.com/obie/concise_errors. All contributions are expected to follow the [code of conduct](https://github.com/obie/concise_errors/blob/main/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ require "rubocop/rake_task"
7
+
8
+ RuboCop::RakeTask.new
9
+
10
+ Rake::TestTask.new(:test) do |t|
11
+ t.libs << "test"
12
+ t.test_files = FileList["test/**/*_test.rb"]
13
+ end
14
+
15
+ task default: %i[test rubocop]
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConciseErrors
4
+ # Stores global configuration for the middleware.
5
+ class Configuration
6
+ DEFAULT_STACK_LINES = 10
7
+ DEFAULT_FORMAT = :text
8
+
9
+ attr_accessor :stack_trace_lines, :enabled, :logger, :application_root, :cleaner
10
+
11
+ def initialize
12
+ reset!
13
+ end
14
+
15
+ def format
16
+ (@format || DEFAULT_FORMAT).to_sym
17
+ end
18
+
19
+ def enabled?
20
+ enabled != false
21
+ end
22
+
23
+ def format=(value)
24
+ @format = value&.to_sym
25
+ end
26
+
27
+ def reset!
28
+ @format = :text
29
+ @stack_trace_lines = DEFAULT_STACK_LINES
30
+ @enabled = true
31
+ @logger = nil
32
+ @application_root = nil
33
+ @cleaner = nil
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_dispatch/http/content_security_policy"
4
+ require "action_dispatch/http/mime_type"
5
+ require "action_dispatch/http/parameters"
6
+ require "action_dispatch/http/request"
7
+ require "action_dispatch/middleware/debug_exceptions"
8
+
9
+ module ConciseErrors
10
+ # Replacement middleware that keeps the existing API while producing concise output.
11
+ class DebugExceptions < ::ActionDispatch::DebugExceptions
12
+ def call(env)
13
+ env["action_dispatch.backtrace_cleaner"] ||= ConciseErrors.configuration.cleaner
14
+ super
15
+ end
16
+
17
+ private
18
+
19
+ def render_for_browser_request(request, wrapper)
20
+ formatter = Formatter.new(wrapper, request, ConciseErrors.configuration)
21
+ render(wrapper.status_code, formatter.body, formatter.content_type)
22
+ end
23
+
24
+ def logger(request)
25
+ ConciseErrors.logger || super
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+ require "rack/utils"
5
+
6
+ module ConciseErrors
7
+ # Builds the compact error payload for browser responses.
8
+ class Formatter
9
+ OMITTED_SUFFIX = "... %<count>d more lines omitted"
10
+
11
+ def initialize(wrapper, request, configuration)
12
+ @wrapper = wrapper
13
+ @request = request
14
+ @configuration = configuration
15
+ end
16
+
17
+ def body
18
+ response_format == :html ? html_payload : text_payload
19
+ end
20
+
21
+ def content_type
22
+ response_format == :html ? "text/html" : "text/plain"
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :wrapper, :request, :configuration
28
+
29
+ def response_format
30
+ return :text if prefers_plain_text?
31
+
32
+ configuration.format
33
+ end
34
+
35
+ def text_payload
36
+ [heading_line, status_line, "", *truncated_trace].compact.join("\n")
37
+ end
38
+
39
+ def html_payload
40
+ <<~HTML
41
+ <!DOCTYPE html>
42
+ <html lang="en">
43
+ <head>
44
+ <meta charset="utf-8" />
45
+ <title>#{html_escape(heading_line)}</title>
46
+ </head>
47
+ <body>
48
+ <h1>#{html_escape(heading_line)}</h1>
49
+ <p>#{html_escape(status_line)}</p>
50
+ <pre>#{html_escape(truncated_trace.join("\n"))}</pre>
51
+ </body>
52
+ </html>
53
+ HTML
54
+ end
55
+
56
+ def heading_line
57
+ "#{wrapper.exception_class_name}: #{wrapper.message}".strip
58
+ end
59
+
60
+ def status_line
61
+ status = wrapper.status_code
62
+ message = Rack::Utils::HTTP_STATUS_CODES[status] || "Internal Server Error"
63
+ "HTTP #{status} (#{message})"
64
+ end
65
+
66
+ def truncated_trace
67
+ full_trace = Array(wrapper.exception_trace).map { |line| sanitize_trace(line) }
68
+ limit = configuration.stack_trace_lines
69
+
70
+ return full_trace if limit.nil? || limit <= 0 || full_trace.size <= limit
71
+
72
+ full_trace.first(limit) + [Kernel.format(OMITTED_SUFFIX, count: full_trace.size - limit)]
73
+ end
74
+
75
+ def html_escape(string)
76
+ ERB::Util.html_escape(string)
77
+ end
78
+
79
+ def prefers_plain_text?
80
+ return true if request.xhr?
81
+
82
+ accept = request.get_header("HTTP_ACCEPT").to_s
83
+ return false if accept.empty?
84
+
85
+ !accept.downcase.include?("html")
86
+ end
87
+
88
+ def sanitize_trace(line)
89
+ root = configuration.application_root.to_s
90
+ return line if root.empty?
91
+
92
+ line.sub(%r{\A#{Regexp.escape(root)}/?}, "./")
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/railtie"
4
+
5
+ module ConciseErrors
6
+ # Integrates the middleware with a host Rails application.
7
+ class Railtie < Rails::Railtie
8
+ config.concise_errors = ConciseErrors.configuration
9
+
10
+ initializer "concise_errors.configure_defaults" do |app|
11
+ ConciseErrors.configure do |config|
12
+ apply_framework_defaults(config, app)
13
+ end
14
+ end
15
+
16
+ initializer "concise_errors.swap_middleware" do |app|
17
+ next unless ConciseErrors.configuration.enabled?
18
+
19
+ stack = app.config.middleware
20
+ disable_web_console(stack)
21
+
22
+ begin
23
+ stack.swap ::ActionDispatch::DebugExceptions, ConciseErrors::DebugExceptions
24
+ rescue RuntimeError
25
+ stack.delete ::ActionDispatch::DebugExceptions
26
+ stack.use ConciseErrors::DebugExceptions
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def apply_framework_defaults(config, app)
33
+ config.logger ||= detect_logger
34
+ config.application_root ||= detect_root(app)
35
+ config.format = detect_format if config.format == Configuration::DEFAULT_FORMAT
36
+ config.stack_trace_lines ||= Configuration::DEFAULT_STACK_LINES
37
+ config.cleaner ||= detect_cleaner
38
+ end
39
+
40
+ def detect_logger
41
+ return Rails.logger if defined?(Rails) && Rails.respond_to?(:logger)
42
+
43
+ require "logger"
44
+ Logger.new($stdout)
45
+ end
46
+
47
+ def detect_root(app)
48
+ return Rails.root.to_s if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
49
+ return unless app.respond_to?(:root) && app.root
50
+
51
+ app.root.to_s
52
+ end
53
+
54
+ def detect_cleaner
55
+ Rails.backtrace_cleaner if defined?(Rails) && Rails.respond_to?(:backtrace_cleaner)
56
+ end
57
+
58
+ def detect_format
59
+ configured = ENV.fetch("CONCISE_ERRORS_FORMAT", nil)&.strip
60
+ return configured.downcase.to_sym if configured && %w[text html].include?(configured.downcase)
61
+
62
+ :html
63
+ end
64
+
65
+ def disable_web_console(stack)
66
+ return unless defined?(WebConsole::Middleware)
67
+
68
+ stack.delete(WebConsole::Middleware)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConciseErrors
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+ require "active_support/core_ext/module/attribute_accessors"
5
+
6
+ require_relative "concise_errors/version"
7
+ require_relative "concise_errors/configuration"
8
+ require_relative "concise_errors/formatter"
9
+ require_relative "concise_errors/debug_exceptions"
10
+
11
+ # ConciseErrors exposes configuration helpers and loads the custom error middleware.
12
+ module ConciseErrors
13
+ class << self
14
+ def configuration
15
+ @configuration ||= Configuration.new
16
+ end
17
+
18
+ def configure
19
+ yield(configuration)
20
+ end
21
+
22
+ def reset_configuration!
23
+ @configuration = Configuration.new
24
+ end
25
+
26
+ def logger
27
+ configuration.logger
28
+ end
29
+
30
+ def logger=(logger)
31
+ configuration.logger = logger
32
+ end
33
+
34
+ def application_root
35
+ configuration.application_root
36
+ end
37
+
38
+ def application_root=(path)
39
+ configuration.application_root = path
40
+ end
41
+
42
+ def cleaner
43
+ configuration.cleaner
44
+ end
45
+
46
+ def cleaner=(cleaner)
47
+ configuration.cleaner = cleaner
48
+ end
49
+ end
50
+ end
51
+
52
+ require_relative "concise_errors/railtie" if defined?(Rails::Railtie)
@@ -0,0 +1,35 @@
1
+ module ConciseErrors
2
+ VERSION: String
3
+
4
+ class Configuration
5
+ DEFAULT_FORMAT: Symbol
6
+ @format: Symbol
7
+ @stack_trace_lines: Integer?
8
+ @enabled: bool
9
+ @logger: untyped
10
+ @application_root: String?
11
+ @cleaner: untyped
12
+
13
+ attr_accessor stack_trace_lines: Integer?
14
+ attr_accessor enabled: bool
15
+ attr_accessor logger: untyped
16
+ attr_accessor application_root: String?
17
+ attr_accessor cleaner: untyped
18
+
19
+ def initialize: () -> void
20
+ def format: () -> Symbol
21
+ def format=:(Symbol | String | nil) -> void
22
+ def enabled?: () -> bool
23
+ def reset!: () -> void
24
+ end
25
+
26
+ def self.configuration: () -> Configuration
27
+ def self.configure: () { (Configuration) -> void } -> void
28
+ def self.reset_configuration!: () -> void
29
+ def self.logger: () -> untyped
30
+ def self.logger=:(untyped) -> void
31
+ def self.application_root: () -> String?
32
+ def self.application_root=:(String?) -> void
33
+ def self.cleaner: () -> untyped
34
+ def self.cleaner=:(untyped) -> void
35
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: concise_errors
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Obie Fernandez
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-10-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: actionpack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.1'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '9.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '6.1'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '9.0'
33
+ description: 'ConciseErrors replaces ActionDispatch::DebugExceptions with a compact
34
+ error page that highlights the exception and a truncated backtrace, making Rails
35
+ crashes easier for humans and AI helpers alike.
36
+
37
+ '
38
+ email:
39
+ - obiefernandez@gmail.com
40
+ executables: []
41
+ extensions: []
42
+ extra_rdoc_files: []
43
+ files:
44
+ - ".rubocop.yml"
45
+ - CHANGELOG.md
46
+ - CODE_OF_CONDUCT.md
47
+ - README.md
48
+ - Rakefile
49
+ - lib/concise_errors.rb
50
+ - lib/concise_errors/configuration.rb
51
+ - lib/concise_errors/debug_exceptions.rb
52
+ - lib/concise_errors/formatter.rb
53
+ - lib/concise_errors/railtie.rb
54
+ - lib/concise_errors/version.rb
55
+ - sig/concise_errors.rbs
56
+ homepage: https://github.com/obie/concise_errors
57
+ licenses: []
58
+ metadata:
59
+ homepage_uri: https://github.com/obie/concise_errors
60
+ source_code_uri: https://github.com/obie/concise_errors
61
+ changelog_uri: https://github.com/obie/concise_errors/blob/main/CHANGELOG.md
62
+ rubygems_mfa_required: 'true'
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 3.0.0
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 3.5.11
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: Minimal Rails error pages tuned for AI agents and compact debugging.
82
+ test_files: []