diffend 0.2.15

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.
@@ -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