From 369aadcbbc72b7214d829d9f74460de1732c9f17 Mon Sep 17 00:00:00 2001 From: Kyrylo Silin Date: Mon, 10 Jun 2024 16:12:14 +0300 Subject: [PATCH] Add support for middlewares --- lib/telebugs.rb | 2 ++ lib/telebugs/config.rb | 5 ++- lib/telebugs/middleware.rb | 17 ++++++++++ lib/telebugs/middleware_stack.rb | 30 +++++++++++++++++ lib/telebugs/notifier.rb | 8 ++++- lib/telebugs/report.rb | 2 ++ lib/telebugs/sender.rb | 8 ++++- test/test_config.rb | 10 ++++++ test/test_middleware_stack.rb | 57 ++++++++++++++++++++++++++++++++ test/test_notifier.rb | 35 +++++++++++++++++++- test/test_report.rb | 8 +++++ 11 files changed, 178 insertions(+), 4 deletions(-) create mode 100644 lib/telebugs/middleware.rb create mode 100644 lib/telebugs/middleware_stack.rb create mode 100644 test/test_middleware_stack.rb 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_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