diffend 0.2.15

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'ldiff' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("diff-lcs", "ldiff")
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rake", "rake")
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rspec' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rspec-core", "rspec")
@@ -0,0 +1,25 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIEODCCAqCgAwIBAgIBATANBgkqhkiG9w0BAQsFADAjMSEwHwYDVQQDDBhtYWNp
3
+ ZWovREM9bWVuc2ZlbGQvREM9cGwwHhcNMTkwNzMwMTQ1NDU0WhcNMjAwNzI5MTQ1
4
+ NDU0WjAjMSEwHwYDVQQDDBhtYWNpZWovREM9bWVuc2ZlbGQvREM9cGwwggGiMA0G
5
+ CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQC9fCwtaHZG2SyyNXiH8r0QbJQx/xxl
6
+ dkvwWz9QGJO+O8rEx20FB1Ab+MVkfOscwIv5jWpmk1U9whzDPl1uFtIbgu+sk+Zb
7
+ uQlZyK/DPN6c+/BbBL+RryTBRyvkPLoCVwm7uxc/JZ1n4AI6eF4cCZ2ieZ9QgQbU
8
+ MQs2QPqs9hT50Ez/40GnOdadVfiDDGz+NME2C4ms0BriXwZ1tcRTfJIHe2xjIbbb
9
+ y5qRGfsLKcgMzvLQR24olixyX1MR0s4+Wveq3QL/gBhL4veUcv+UABJA8IJR0kyB
10
+ seHHutusiwZ1v3SjjjW1xLLrc2ARV0mgCb0WaK2T4iA3oFTGLh6Ydz8LNl31KQFv
11
+ 94nRd8IhmJxrhQ6dQ/WT9IXoa5S9lfT5lPJeINemH4/6QPABzf9W2IZlCdI9wCdB
12
+ TBaw57MKneGAYZiKjw6OALSy2ltQUCl3RqFl3VP7n8uFy1U987Q5VIIQ3O1UUsQD
13
+ Oe/h+r7GUU4RSPKgPlrwvW9bD/UQ+zF51v8CAwEAAaN3MHUwCQYDVR0TBAIwADAL
14
+ BgNVHQ8EBAMCBLAwHQYDVR0OBBYEFJNIBHdfEUD7TqHqIer2YhWaWhwcMB0GA1Ud
15
+ EQQWMBSBEm1hY2llakBtZW5zZmVsZC5wbDAdBgNVHRIEFjAUgRJtYWNpZWpAbWVu
16
+ c2ZlbGQucGwwDQYJKoZIhvcNAQELBQADggGBAKA4eqko6BTNhlysip6rfBkVTGri
17
+ ZXsL+kRb2hLvsQJS/kLyM21oMlu+LN0aPj3qEFR8mE/YeDD8rLAfruBRTltPNbR7
18
+ xA5eE1gkxY5LfExUtK3b2wPqfmo7mZgfcsMwfYg/tUXw1WpBCnrhAJodpGH6SXmp
19
+ A40qFUZst0vjiOoO+aTblIHPmMJXoZ3K42dTlNKlEiDKUWMRKSgpjjYGEYalFNWI
20
+ hHfCz2r8L2t+dYdMZg1JGbEkq4ADGsAA8ioZIpJd7V4hI17u5TCdi7X5wh/0gN0E
21
+ CgP+nLox3D+l2q0QuQEkayr+auFYkzTCkF+BmEk1D0Ru4mcf3F4CJvEmW4Pzbjqt
22
+ i1tsCWPtJ4E/UUKnKaWKqGbjrjHJ0MuShYzHkodox5IOiCXIQg+1+YSzfXUV6WEK
23
+ KJG/fhg1JV5vVDdVy6x+tv5SQ5ctU0feCsVfESi3rE3zRd+nvzE9HcZ5aXeL1UtJ
24
+ nT5Xrioegu2w1jPyVEgyZgTZC5rvD0nNS5sFNQ==
25
+ -----END CERTIFICATE-----
@@ -0,0 +1,25 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIERDCCAqygAwIBAgIBATANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDDBt0b21l
3
+ ay9EQz1wb2xpc2hnZWVrcy9EQz1jb20wHhcNMjAwNzA3MTY0NjU0WhcNMjEwNzA3
4
+ MTY0NjU0WjAmMSQwIgYDVQQDDBt0b21lay9EQz1wb2xpc2hnZWVrcy9EQz1jb20w
5
+ ggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDPRTvPuofdtL/wFEPOwPUr
6
+ vR0XHzM/ADb2GBuzu6fzgmoxaYXBe8A++0BbgFvK47T04i8bsbXnfkxrkz/nupQ5
7
+ SK2DPgS4HWnADuyBuyBY7LT4O1wwlytdlHtJgQV6NIcbprcOs/ZQKnimZpW9uByu
8
+ FoN3i94pAEQhuzK0S+wWPvSm22+6XGtCuOzyFGdnCJjGUOkCRno5Nx34MWz0NpJ3
9
+ 9Ekkyy8g2cLvBcUdfeSrY7WsJ5cPCNrBs5cMuV426s1dDrhuvsW+sacwwY/4/LBw
10
+ JzEX4/zS+lsVIX+iOoIFGJdeGnpEWqKgWoaskxqseFi661td1n9UaMXxgoaYh/oX
11
+ 3fJOy2jsZFboZ/eJ5rfciXLiCqSERGkEA+QcA2/jC/d77YJ1FfJW9uwJs3kptf4D
12
+ p6h8wuA3T6rN4QrxkGBYzOfUJ2zSQy1cFu0rTZiYdKo9X6BunnxhmUExNng7advu
13
+ qo8IDinyRlqA5+sOLXd4W3AS/RfF2nrayZNa3khTmmUCAwEAAaN9MHswCQYDVR0T
14
+ BAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFHRFOZPwpgOd2m8FIOodOii+OiID
15
+ MCAGA1UdEQQZMBeBFXRvbWVrQHBvbGlzaGdlZWtzLmNvbTAgBgNVHRIEGTAXgRV0
16
+ b21la0Bwb2xpc2hnZWVrcy5jb20wDQYJKoZIhvcNAQELBQADggGBAKWFwYTGZVoy
17
+ Bj3L9lvGOXpz8VWNoptFNHdncpaw1MMhS8UHcPQOUEiExX5ZH7MARy1fBjMXzIh9
18
+ 41ZpCjR+S6uCEpzUcg5Z/kEWa/wOW6tqrX+zfyxFATDI20pYaQWOLepjbDxePFMZ
19
+ GAlIX5UNsze04A+wArXAttZB4oPt6loS1ao0GNdMb+syYMLzZUTW/sY2rm8zP4Mz
20
+ Kt+zjoqMxQ1Jf+EwH+0uq8Tj5BJcmG6mWYM+ljvRbxBwfimoUBUCQe6KIDouF0Og
21
+ uwLMY7X3jSERta4SxyY+iY7qNLsmG370GIGYbHuIiCwubFXt8jiPJZEdPE1xuzVF
22
+ CLsYItzC28UQEWrVe6sJ0Fuqv5VHM6t8jNClkXDwzf95efFlGSCFN4t+/dywVIK8
23
+ 9MmF6uCQa1EjK2p8tYT0MnbHrFkoehxdX4VO9y99GAkhZyJNKPYPtyAUFV27sT2V
24
+ LfCJRk4ifKIN/FUCwDSn8Cz0m6oH265q0p6wdzI6qrWOjP8tGOMBTA==
25
+ -----END CERTIFICATE-----
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'diffend'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'diffend'
9
+ spec.version = Diffend::VERSION
10
+ spec.authors = ['Tomasz Pajor']
11
+ spec.email = ['contact@diffend.io']
12
+
13
+ spec.summary = 'OSS supply chain security and management platform'
14
+ spec.summary = 'OSS supply chain security and management platform.'
15
+ spec.homepage = Diffend::HOMEPAGE
16
+ spec.license = 'Prosperity Public License'
17
+
18
+ if $PROGRAM_NAME.end_with?('gem')
19
+ spec.signing_key = File.expand_path('~/.ssh/gem-private_key.pem')
20
+ end
21
+
22
+ spec.cert_chain = %w[certs/tomaszpajor.pem]
23
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec)/}) }
24
+ spec.require_paths = %w[lib]
25
+
26
+ spec.add_development_dependency 'bundler'
27
+ spec.add_development_dependency 'rake'
28
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ %w[
4
+ bundler
5
+ ].each(&method(:require))
6
+
7
+ %w[
8
+ config/fetcher
9
+ config/file_finder
10
+ commands
11
+ errors
12
+ voting
13
+ ].each { |file| require "diffend/#{file}" }
14
+
15
+ %w[
16
+ request
17
+ versions/local
18
+ versions/remote
19
+ ].each { |file| require "diffend/voting/#{file}" }
20
+
21
+ # Diffend main namespace
22
+ module Diffend
23
+ # Current plugin version
24
+ VERSION = '0.2.15'
25
+ # Diffend homepage
26
+ HOMEPAGE = 'https://diffend.io'
27
+
28
+ class << self
29
+ # Registers the plugin and add before install all hook
30
+ def register
31
+ Bundler::Plugin.add_hook('before-install-all') do |_|
32
+ Diffend::Voting.call(
33
+ command,
34
+ build_definition(command)
35
+ )
36
+ end
37
+ end
38
+
39
+ # Build clean instance of bundler definition, as we don't want to pollute the main one
40
+ #
41
+ # @param command [String] bundler command that we are executing
42
+ #
43
+ # @return [Bundler::Definition]
44
+ def build_definition(command)
45
+ unlock = command == 'update' ? true : nil
46
+
47
+ Bundler.configure
48
+
49
+ Bundler::Definition.build(
50
+ Bundler.default_gemfile,
51
+ Bundler.default_lockfile,
52
+ unlock
53
+ )
54
+ end
55
+
56
+ # Command that was run with bundle
57
+ #
58
+ # @return [String]
59
+ def command
60
+ ARGV.first || Diffend::Commands::INSTALL
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Diffend
4
+ # Modules grouping supported bundler commands
5
+ module Commands
6
+ # Install bundler command
7
+ INSTALL = 'install'
8
+ # Update bundler command
9
+ UPDATE = 'update'
10
+ end
11
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module Diffend
6
+ # Module for all the components related to setting up the config
7
+ module Config
8
+ # Class responsible for fetching the config from .diffend.yml
9
+ module Fetcher
10
+ class << self
11
+ # @param build_path [String] path of the current build
12
+ # @return [OpenStruct] open struct with config details
13
+ # @example
14
+ # details = Fetcher.new.call('./')
15
+ # details.build_path #=> './'
16
+ def call(build_path)
17
+ OpenStruct.new(
18
+ YAML.safe_load(
19
+ ERB.new(
20
+ File.read(
21
+ FileFinder.call(build_path)
22
+ )
23
+ ).result
24
+ ).merge(build_path: build_path)
25
+ )
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Diffend
4
+ module Config
5
+ # Class used to figure out the file from which we should load the settings
6
+ module FileFinder
7
+ # Names of the files or paths where we will look for the settings
8
+ #
9
+ # @note We do the double dot trick, to look outside of the current dir because when
10
+ # executed from a docker container, we copy the local uncommitted settings into the
11
+ # dir above the app location not to pollute the reset state of the git repo
12
+ #
13
+ # @note Order is important, as for local env we should load from
14
+ # local file (if present first)
15
+ FILE_NAMES = %w[
16
+ .diffend.yml
17
+ ].map { |name| ["../#{name}", name] }.tap(&:flatten!).freeze
18
+
19
+ private_constant :FILE_NAMES
20
+
21
+ class << self
22
+ # Looks for Diffend settings file for a given env
23
+ #
24
+ # @param build_path [String] path of the current build
25
+ #
26
+ # @return [String] path to the file from which we should load all the settings
27
+ def call(build_path)
28
+ FILE_NAMES
29
+ .map { |name| File.join(build_path, name) }
30
+ .map { |name| Dir[name] }
31
+ .find { |selection| !selection.empty? }
32
+ .tap { |path| path || raise(Errors::MissingConfigurationFile) }
33
+ .first
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Diffend
4
+ # Build runner app errors
5
+ module Errors
6
+ # Base error class from which all the errors should inherit
7
+ BaseError = Class.new(StandardError)
8
+ # Raised when we couldn't find a valid configuration file
9
+ MissingConfigurationFile = Class.new(BaseError)
10
+ # When unsupported response returned from the endpoint
11
+ UnsupportedResponse = Class.new(BaseError)
12
+ # When unsupported action returned from the endpoint
13
+ UnsupportedAction = Class.new(BaseError)
14
+ end
15
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Diffend
4
+ # Verifies voting verdicts for gems
5
+ module Voting
6
+ class << self
7
+ # Build verdict
8
+ #
9
+ # @param command [String] either install or update
10
+ # @param definition [Bundler::Definition] definition for your source
11
+ def call(command, definition)
12
+ Versions::Remote
13
+ .call(command, definition)
14
+ .tap { |response| build_message(command, response) }
15
+ end
16
+
17
+ def build_message(command, response)
18
+ if response.key?('error')
19
+ build_error(response)
20
+ elsif response.key?('action')
21
+ build_verdict(command, response)
22
+ else
23
+ raise UnsupportedResponse, response['action']
24
+ end
25
+ end
26
+
27
+ def build_error(response)
28
+ build_error_message(response)
29
+ .tap(&Bundler.ui.method(:error))
30
+
31
+ exit 1
32
+ end
33
+
34
+ def build_verdict(command, response)
35
+ case response['action']
36
+ when 'allow'
37
+ build_allow_message(command, response)
38
+ .tap(&Bundler.ui.method(:confirm))
39
+ when 'deny'
40
+ build_deny_message(command, response)
41
+ .tap(&Bundler.ui.method(:error))
42
+
43
+ exit 1
44
+ else
45
+ raise UnsupportedAction, response['action']
46
+ end
47
+ end
48
+
49
+ def build_error_message(response)
50
+ <<~MSG
51
+ \nDiffend returned an error for your request.\n
52
+ #{response['error']}\n
53
+ MSG
54
+ end
55
+
56
+ def build_allow_message(command, response)
57
+ <<~MSG
58
+ \nDiffend reported an allow verdict for #{command} command for this project.\n
59
+ All of our #{response['allows_count'] + response['denies_count']} checks succeeded.\n
60
+ #{response['review_url']}\n
61
+ MSG
62
+ end
63
+
64
+ def build_deny_message(command, response)
65
+ <<~MSG
66
+ \nDiffend reported a deny verdict for #{command} command for this project.\n
67
+ #{response['denies_count']} out of our #{response['allows_count'] + response['denies_count']} checks failed. Please go to the url below and review the issues.\n
68
+ #{response['review_url']}\n
69
+ MSG
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'openssl'
5
+ require 'json'
6
+ require 'securerandom'
7
+
8
+ module Diffend
9
+ module Voting
10
+ # Module responsible for doing request to Diffend
11
+ module Request
12
+ # List of connection exceptions
13
+ CONNECTION_EXCEPTIONS = [
14
+ Errno::ECONNRESET,
15
+ Errno::ENETUNREACH,
16
+ Errno::EHOSTUNREACH,
17
+ Errno::ECONNREFUSED
18
+ ].freeze
19
+ # List of timeout exceptions
20
+ TIMEOUT_EXCEPTIONS = [
21
+ Net::OpenTimeout,
22
+ Net::ReadTimeout
23
+ ].freeze
24
+ # Request headers
25
+ HEADERS = { 'Content-Type': 'application/json' }.freeze
26
+
27
+ private_constant :HEADERS
28
+
29
+ class << self
30
+ # Execute request to Diffend
31
+ #
32
+ # @param command [String] either install or update
33
+ # @param payload [Hash] with versions to check
34
+ # @param config [OpenStruct] Diffend config
35
+ #
36
+ # @return [Net::HTTPResponse] response from Diffend
37
+ def call(command, payload, config)
38
+ retry_count ||= 0
39
+ build_http(command, config.project_id) do |http, uri|
40
+ http.request(build_request(uri, config, payload))
41
+ end
42
+ rescue *CONNECTION_EXCEPTIONS => e
43
+ Bundler.ui.error('We experienced a connection issue, retrying...')
44
+ sleep(exponential_backoff(retry_count))
45
+ retry_count += 1
46
+
47
+ retry if retry_count < 3
48
+
49
+ puts prepare_report(payload, e)
50
+ Bundler.ui.error('^^^ Above is the dump of your request ^^^')
51
+ Bundler.ui.error(build_request_error_message)
52
+
53
+ exit 1
54
+ rescue *TIMEOUT_EXCEPTIONS => e
55
+ Bundler.ui.error('We experienced a timeout issue, retrying...')
56
+ sleep(exponential_backoff(retry_count))
57
+ retry_count += 1
58
+
59
+ retry if retry_count < 3
60
+
61
+ puts prepare_report(payload, e)
62
+ Bundler.ui.error('^^^ Above is the dump of your request ^^^')
63
+ Bundler.ui.error(build_request_error_message)
64
+
65
+ exit 1
66
+ rescue StandardError => e
67
+ puts prepare_report(payload, e)
68
+ Bundler.ui.error('^^^ Above is the dump of your request ^^^')
69
+ Bundler.ui.error(build_unhandled_exception_message)
70
+
71
+ exit 1
72
+ end
73
+
74
+ # Builds http connection object
75
+ #
76
+ # @param command [String] either install or update
77
+ # @param project_id [String] diffend project_id
78
+ def build_http(command, project_id)
79
+ uri = URI(endpoint_url(command, project_id))
80
+
81
+ Net::HTTP.start(
82
+ uri.host,
83
+ uri.port,
84
+ use_ssl: uri.scheme == 'https',
85
+ verify_mode: OpenSSL::SSL::VERIFY_NONE,
86
+ open_timeout: 5,
87
+ read_timeout: 5
88
+ ) { |http| yield(http, uri) }
89
+ end
90
+
91
+ # Build http post request and assigns headers and payload
92
+ #
93
+ # @param uri [URI::HTTPS]
94
+ # @param config [OpenStruct] Diffend config
95
+ # @param payload [Hash] with versions to check
96
+ #
97
+ # @return [Net::HTTP::Post]
98
+ def build_request(uri, config, payload)
99
+ Net::HTTP::Post
100
+ .new(uri.request_uri, HEADERS)
101
+ .tap { |request| assign_auth(request, config) }
102
+ .tap { |request| assign_payload(request, payload) }
103
+ end
104
+
105
+ # Assigns basic authorization if provided in the config
106
+ #
107
+ # @param request [Net::HTTP::Post] prepared http post
108
+ # @param config [OpenStruct] Diffend config
109
+ def assign_auth(request, config)
110
+ return unless config
111
+ return unless config.shareable_id
112
+ return unless config.shareable_key
113
+
114
+ request.basic_auth(config.shareable_id, config.shareable_key)
115
+ end
116
+
117
+ # Assigns payload as json
118
+ #
119
+ # @param request [Net::HTTP::Post] prepared http post
120
+ # @param payload [Hash] with versions to check
121
+ def assign_payload(request, payload)
122
+ request.body = JSON.dump(payload: payload)
123
+ end
124
+
125
+ # Provides diffend endpoint url
126
+ #
127
+ # @param command [String] either install or update
128
+ # @param project_id [String] diffend project_id
129
+ #
130
+ # @return [String] diffend endpoint
131
+ def endpoint_url(command, project_id)
132
+ return ENV['DIFFEND_URL'] if ENV.key?('DIFFEND_URL')
133
+
134
+ "https://my.diffend.io/api/projects/#{project_id}/bundle/#{command}"
135
+ end
136
+
137
+ def exponential_backoff(retry_count)
138
+ 2**(retry_count + 1)
139
+ end
140
+
141
+ def build_request_error_message
142
+ <<~MSG
143
+ \nWe were unable to process your request at this time. We recorded this incident in our system and will review it.\n
144
+ If you think that this is a bug, don't hesitate.\n
145
+ Create an issue on https://github.com/diffend-io/diffend-ruby/issues\n
146
+ MSG
147
+ end
148
+
149
+ def build_unhandled_exception_message
150
+ <<~MSG
151
+ \nSomething went really wrong. We recorded this incident in our system and will review it.\n
152
+ This is a bug, don't hesitate.\n
153
+ Create an issue on https://github.com/diffend-io/diffend-ruby/issues\n
154
+ MSG
155
+ end
156
+
157
+ def prepare_report(payload, exception)
158
+ {
159
+ request_id: SecureRandom.uuid,
160
+ payload: payload,
161
+ exception: {
162
+ class: exception.class,
163
+ message: exception.message,
164
+ backtrace: exception.backtrace
165
+ }
166
+ }.to_json
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end