-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6 from telebugs/notifier
Implement notice sending
- Loading branch information
Showing
14 changed files
with
394 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,30 @@ | ||
# frozen_string_literal: true | ||
|
||
require "concurrent" | ||
require "net/https" | ||
require "json" | ||
|
||
require_relative "telebugs/version" | ||
require_relative "telebugs/config" | ||
require_relative "telebugs/promise" | ||
require_relative "telebugs/notifier" | ||
require_relative "telebugs/sender" | ||
require_relative "telebugs/wrapped_error" | ||
require_relative "telebugs/notice" | ||
|
||
module Telebugs | ||
# The general error that this library uses when it wants to raise. | ||
Error = Class.new(StandardError) | ||
|
||
HTTPError = Class.new(Error) | ||
|
||
class << self | ||
def configure | ||
yield Telebugs::Config.instance | ||
yield Config.instance | ||
end | ||
|
||
def notify(error:) | ||
Telebugs::Promise.new(error) do | ||
end | ||
Notifier.instance.notify(error) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
# frozen_string_literal: true | ||
|
||
module Telebugs | ||
# Represents a piece of information that will be sent to Telebugs API to | ||
# report an error. | ||
class Notice | ||
# The list of possible exceptions that might be raised when an object is | ||
# converted to JSON | ||
JSON_EXCEPTIONS = [ | ||
IOError, | ||
NotImplementedError, | ||
JSON::GeneratorError, | ||
Encoding::UndefinedConversionError | ||
].freeze | ||
|
||
# On Ruby 3.1+, the error highlighting gem can produce messages that can | ||
# span over multiple lines. We don't want to display multiline error titles. | ||
# Therefore, we want to strip out the higlighting part so that the errors | ||
# look consistent. | ||
RUBY_31_ERROR_HIGHLIGHTING_DIVIDER = "\n\n" | ||
|
||
# The options for +String#encode+ | ||
ENCODING_OPTIONS = {invalid: :replace, undef: :replace}.freeze | ||
|
||
# The maxium size of the JSON payload in bytes | ||
MAX_NOTICE_SIZE = 64000 | ||
|
||
def initialize(error) | ||
@payload = { | ||
errors: errors_as_json(error) | ||
} | ||
end | ||
|
||
# Converts the notice to JSON. Calls +to_json+ on each object inside | ||
# notice's payload. Truncates notices, JSON representation of which is | ||
# bigger than {MAX_NOTICE_SIZE}. | ||
def to_json(*_args) | ||
loop do | ||
begin | ||
json = @payload.to_json | ||
rescue *JSON_EXCEPTIONS | ||
# TODO | ||
else | ||
return json if json && json.bytesize <= MAX_NOTICE_SIZE | ||
end | ||
|
||
break if truncate == 0 | ||
end | ||
end | ||
|
||
private | ||
|
||
def errors_as_json(error) | ||
WrappedError.new(error).unwrap.map do |e| | ||
{ | ||
type: e.class.name, | ||
message: message(e) | ||
} | ||
end | ||
end | ||
|
||
def message(error) | ||
return unless (msg = error.message) | ||
|
||
msg.encode(Encoding::UTF_8, **ENCODING_OPTIONS) | ||
.split(RUBY_31_ERROR_HIGHLIGHTING_DIVIDER) | ||
.first | ||
end | ||
|
||
def truncate | ||
0 | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# frozen_string_literal: true | ||
|
||
module Telebugs | ||
# Notifier is reponsible for sending notices to Telebugs. | ||
class Notifier | ||
class << self | ||
attr_writer :instance | ||
|
||
def instance | ||
@instance ||= new | ||
end | ||
end | ||
|
||
def initialize | ||
@sender = Sender.new | ||
end | ||
|
||
def notify(error) | ||
Telebugs::Promise.new(error) do | ||
@sender.send(error) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# frozen_string_literal: true | ||
|
||
module Telebugs | ||
# Responsible for sending HTTP requests to Telebugs. | ||
class Sender | ||
CONTENT_TYPE = "application/json" | ||
|
||
USER_AGENT = "telebugs-ruby/#{Telebugs::VERSION} (#{RUBY_ENGINE}/#{RUBY_VERSION})" | ||
|
||
def initialize | ||
@config = Config.instance | ||
@authorization = "Bearer #{@config.api_key}" | ||
end | ||
|
||
def send(data) | ||
req = build_request(@config.api_url, data) | ||
|
||
resp = build_https(@config.api_url).request(req) | ||
if resp.code_type == Net::HTTPCreated | ||
return JSON.parse(resp.body) | ||
end | ||
|
||
raise HTTPError, "#{resp.code_type} (#{resp.code}): #{JSON.parse(resp.body)}" | ||
end | ||
|
||
private | ||
|
||
def build_request(uri, data) | ||
Net::HTTP::Post.new(uri.request_uri).tap do |req| | ||
req["Authorization"] = @authorization | ||
req["Content-Type"] = CONTENT_TYPE | ||
req["User-Agent"] = USER_AGENT | ||
|
||
req.body = data.to_json | ||
end | ||
end | ||
|
||
def build_https(uri) | ||
Net::HTTP.new(uri.host, uri.port).tap do |https| | ||
https.use_ssl = uri.is_a?(URI::HTTPS) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# frozen_string_literal: true | ||
|
||
require "test_helper" | ||
|
||
class TestConfig < Minitest::Test | ||
def teardown | ||
Telebugs::Config.instance.reset | ||
end | ||
|
||
def test_api_key | ||
Telebugs.configure { |c| c.api_key = "12345:abcdef" } | ||
|
||
assert_equal "12345:abcdef", Telebugs::Config.instance.api_key | ||
end | ||
|
||
def test_error_api_url | ||
Telebugs.configure { |c| c.api_url = "example.com" } | ||
|
||
assert_equal URI("example.com"), Telebugs::Config.instance.api_url | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,3 +4,4 @@ | |
require "telebugs" | ||
|
||
require "minitest/autorun" | ||
require "webmock/minitest" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
# frozen_string_literal: true | ||
|
||
require "test_helper" | ||
|
||
class TestNotice < Minitest::Test | ||
def test_to_json_with_nested_errors | ||
begin | ||
raise StandardError.new("error 1") | ||
rescue => _ | ||
begin | ||
raise StandardError.new("error 2") | ||
rescue => e2 | ||
n = Telebugs::Notice.new(e2) | ||
end | ||
end | ||
|
||
assert_equal( | ||
{ | ||
"errors" => [ | ||
{ | ||
"type" => "StandardError", | ||
"message" => "error 2" | ||
}, | ||
{ | ||
"type" => "StandardError", | ||
"message" => "error 1" | ||
} | ||
] | ||
}, | ||
JSON.parse(n.to_json) | ||
) | ||
end | ||
|
||
def test_to_json_with_error_highlighting_in_messages | ||
begin | ||
raise "undefined method `[]' for nil:NilClass\n\n " \ | ||
"data[:result].first[:first_name]\n ^^^^^^^^^^^^^" | ||
rescue => e | ||
end | ||
|
||
n = Telebugs::Notice.new(e) | ||
|
||
assert_equal( | ||
{ | ||
"errors" => [ | ||
{ | ||
"type" => "RuntimeError", | ||
"message" => "undefined method `[]' for nil:NilClass" | ||
} | ||
] | ||
}, | ||
JSON.parse(n.to_json) | ||
) | ||
end | ||
|
||
def test_to_json_when_error_message_contains_invalid_characters | ||
begin | ||
JSON.parse(Marshal.dump(Time.now)) | ||
rescue JSON::ParserError => e | ||
end | ||
|
||
n = Telebugs::Notice.new(e) | ||
json = JSON.parse(n.to_json) | ||
|
||
assert_equal "JSON::ParserError", json["errors"].first["type"] | ||
assert_match(/unexpected token at/, json["errors"].first["message"]) | ||
end | ||
|
||
def test_to_json_when_error_message_is_nil | ||
error = Class.new(StandardError) { | ||
def message | ||
end | ||
}.new | ||
|
||
n = Telebugs::Notice.new(error) | ||
|
||
assert_equal( | ||
{ | ||
"errors" => [ | ||
{ | ||
"type" => nil, | ||
"message" => nil | ||
} | ||
] | ||
}, | ||
JSON.parse(n.to_json) | ||
) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# frozen_string_literal: true | ||
|
||
require "test_helper" | ||
|
||
class TestNotifier < Minitest::Test | ||
def teardown | ||
WebMock.reset! | ||
end | ||
|
||
def test_notify_returns_a_promise_that_resolves_to_a_hash | ||
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) | ||
end | ||
end |
Oops, something went wrong.