diff --git a/lib/telebugs.rb b/lib/telebugs.rb
index 31e561b..d75e14e 100644
--- a/lib/telebugs.rb
+++ b/lib/telebugs.rb
@@ -15,6 +15,8 @@
require_relative "telebugs/backtrace"
require_relative "telebugs/file_cache"
require_relative "telebugs/code_hunk"
+require_relative "telebugs/middleware"
+require_relative "telebugs/middleware_stack"
module Telebugs
# The general error that this library uses when it wants to raise.
diff --git a/lib/telebugs/config.rb b/lib/telebugs/config.rb
index a0c6274..792c380 100644
--- a/lib/telebugs/config.rb
+++ b/lib/telebugs/config.rb
@@ -7,7 +7,8 @@ class Config
ERROR_API_URL = "https://api.telebugs.com/2024-03-28/errors"
attr_accessor :api_key,
- :root_directory
+ :root_directory,
+ :middleware
attr_reader :api_url
@@ -34,6 +35,8 @@ def reset
(defined?(Bundler) && Bundler.root) ||
Dir.pwd
)
+
+ @middleware = MiddlewareStack.new
end
end
end
diff --git a/lib/telebugs/middleware.rb b/lib/telebugs/middleware.rb
new file mode 100644
index 0000000..cc1438a
--- /dev/null
+++ b/lib/telebugs/middleware.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Telebugs
+ # Represents a middleware that can be used to filter out errors.
+ # You must inherit from this class and implement the #call method.
+ class Middleware
+ DEFAULT_WEIGHT = 0
+
+ def weight
+ DEFAULT_WEIGHT
+ end
+
+ def call(_report)
+ raise NotImplementedError, "You must implement the #call method"
+ end
+ end
+end
diff --git a/lib/telebugs/middleware_stack.rb b/lib/telebugs/middleware_stack.rb
new file mode 100644
index 0000000..e7ee4ef
--- /dev/null
+++ b/lib/telebugs/middleware_stack.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Telebugs
+ # MiddlewareStack represents an ordered array of middleware.
+ #
+ # A middleware is an object that responds to #call (typically a Proc or a
+ # class that implements the call method). The #call method must accept
+ # exactly one argument: the report object.
+ #
+ # When you add a new middleware to the stack, it gets inserted according to its
+ # weight. Smaller weight means the middleware will be somewhere in the
+ # beginning of the array. Larger - in the end.
+ class MiddlewareStack
+ attr_reader :middlewares
+
+ def initialize
+ @middlewares = []
+ end
+
+ def use(new_middleware)
+ @middlewares = (@middlewares << new_middleware).sort_by(&:weight).reverse
+ end
+
+ def call(report)
+ @middlewares.each do |middleware|
+ middleware.call(report)
+ end
+ end
+ end
+end
diff --git a/lib/telebugs/notifier.rb b/lib/telebugs/notifier.rb
index c2cab9e..f605530 100644
--- a/lib/telebugs/notifier.rb
+++ b/lib/telebugs/notifier.rb
@@ -13,11 +13,17 @@ def instance
def initialize
@sender = Sender.new
+ @middleware = Config.instance.middleware
end
def notify(error)
Telebugs::Promise.new(error) do
- @sender.send(error)
+ report = Report.new(error)
+
+ @middleware.call(report)
+ next if report.ignored
+
+ @sender.send(report)
end
end
end
diff --git a/lib/telebugs/report.rb b/lib/telebugs/report.rb
index 456f224..d942eb7 100644
--- a/lib/telebugs/report.rb
+++ b/lib/telebugs/report.rb
@@ -17,8 +17,10 @@ class Report
MAX_REPORT_SIZE = 64000
attr_reader :data
+ attr_accessor :ignored
def initialize(error)
+ @ignored = false
@data = {
errors: errors_as_json(error)
}
diff --git a/lib/telebugs/sender.rb b/lib/telebugs/sender.rb
index 14c4bf8..91f46f1 100644
--- a/lib/telebugs/sender.rb
+++ b/lib/telebugs/sender.rb
@@ -20,7 +20,13 @@ def send(data)
return JSON.parse(resp.body)
end
- raise HTTPError, "#{resp.code_type} (#{resp.code}): #{JSON.parse(resp.body)}"
+ begin
+ reason = JSON.parse(resp.body)
+ rescue JSON::ParserError
+ nil
+ end
+
+ raise HTTPError, "#{resp.code_type} (#{resp.code}): #{reason}"
end
private
diff --git a/test/test_config.rb b/test/test_config.rb
index 4d4e7e4..310e0fb 100644
--- a/test/test_config.rb
+++ b/test/test_config.rb
@@ -24,4 +24,14 @@ def test_root_directory
assert_equal "/tmp", Telebugs::Config.instance.root_directory
end
+
+ def test_middleware
+ middleware_class = Class.new(Telebugs::Middleware)
+
+ Telebugs.configure do |c|
+ c.middleware.use middleware_class.new
+ end
+
+ assert_equal 1, Telebugs::Config.instance.middleware.middlewares.size
+ end
end
diff --git a/test/test_middleware.rb b/test/test_middleware.rb
new file mode 100644
index 0000000..4617a0f
--- /dev/null
+++ b/test/test_middleware.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class TestMiddleware < Minitest::Test
+ def test_call
+ assert_raises(NotImplementedError) do
+ Telebugs::Middleware.new.call(nil)
+ end
+ end
+
+ def test_weight
+ assert_equal 0, Telebugs::Middleware.new.weight
+ end
+end
diff --git a/test/test_middleware_stack.rb b/test/test_middleware_stack.rb
new file mode 100644
index 0000000..2c10b0f
--- /dev/null
+++ b/test/test_middleware_stack.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class TestFilteringMiddleware < Telebugs::Middleware
+ def call(report)
+ report.data[:errors][0][:message] = "[FILTERED]"
+ end
+
+ def weight
+ 1
+ end
+end
+
+class TestStartLineMiddleware < Telebugs::Middleware
+ def call(report)
+ report.data[:errors][0][:backtrace] = {123 => 456}
+ end
+
+ def weight
+ 10
+ end
+end
+
+class TestMiddlewareStack < Minitest::Test
+ def test_call
+ stack = Telebugs::MiddlewareStack.new
+ stack.use TestFilteringMiddleware.new
+ stack.use TestStartLineMiddleware.new
+
+ report = Telebugs::Report.new(StandardError.new("error message"))
+ stack.call(report)
+
+ assert_equal "[FILTERED]", report.data[:errors][0][:message]
+ assert_equal({123 => 456}, report.data[:errors][0][:backtrace])
+ end
+
+ def test_weight
+ stack = Telebugs::MiddlewareStack.new
+ stack.use TestFilteringMiddleware.new
+ stack.use TestStartLineMiddleware.new
+
+ assert_equal(
+ [TestStartLineMiddleware, TestFilteringMiddleware],
+ stack.middlewares.map(&:class)
+ )
+
+ stack = Telebugs::MiddlewareStack.new
+ stack.use TestStartLineMiddleware.new
+ stack.use TestFilteringMiddleware.new
+
+ assert_equal(
+ [TestStartLineMiddleware, TestFilteringMiddleware],
+ stack.middlewares.map(&:class)
+ )
+ end
+end
diff --git a/test/test_notifier.rb b/test/test_notifier.rb
index f68fced..b0d4962 100644
--- a/test/test_notifier.rb
+++ b/test/test_notifier.rb
@@ -2,17 +2,50 @@
require "test_helper"
+class TestIgnoreMiddleware < Telebugs::Middleware
+ def call(report)
+ report.ignored = true
+ end
+end
+
class TestNotifier < Minitest::Test
def teardown
WebMock.reset!
+ Telebugs::Config.instance.reset
end
def test_notify_returns_a_promise_that_resolves_to_a_hash
- stub_request(:post, Telebugs::Config.instance.api_url)
+ stub = stub_request(:post, Telebugs::Config.instance.api_url)
.to_return(status: 201, body: {id: "123"}.to_json)
p = Telebugs::Notifier.new.notify(StandardError.new)
assert_equal({"id" => "123"}, p.value)
+ assert_requested stub
+ end
+
+ def test_notify_returns_a_promise_that_rejects_on_http_error
+ stub = stub_request(:post, Telebugs::Config.instance.api_url)
+ .to_return(status: 500)
+
+ p = Telebugs::Notifier.new.notify(StandardError.new)
+
+ assert_nil p.value
+ assert_instance_of(Telebugs::HTTPError, p.reason)
+ assert_requested stub
+ end
+
+ def test_notify_does_not_send_ignored_errors
+ stub = stub_request(:post, Telebugs::Config.instance.api_url)
+ .to_return(status: 201, body: {id: "123"}.to_json)
+
+ Telebugs.configure do |c|
+ c.middleware.use TestIgnoreMiddleware.new
+ end
+
+ p = Telebugs::Notifier.new.notify(StandardError.new)
+ p.wait
+
+ refute_requested stub
end
end
diff --git a/test/test_report.rb b/test/test_report.rb
index 45508a8..d94f931 100644
--- a/test/test_report.rb
+++ b/test/test_report.rb
@@ -80,4 +80,12 @@ def test_as_json_code
assert_nil backtrace[1][:code]
assert_nil backtrace[2][:code]
end
+
+ def test_ignore
+ r = Telebugs::Report.new(StandardError.new)
+ refute r.ignored
+
+ r.ignored = true
+ assert r.ignored
+ end
end