txgh-queue 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/lib/txgh-queue/application.rb +35 -0
  3. data/lib/txgh-queue/backends/null.rb +19 -0
  4. data/lib/txgh-queue/backends/sqs/config.rb +29 -0
  5. data/lib/txgh-queue/backends/sqs/consumer.rb +30 -0
  6. data/lib/txgh-queue/backends/sqs/history_sequence.rb +62 -0
  7. data/lib/txgh-queue/backends/sqs/job.rb +115 -0
  8. data/lib/txgh-queue/backends/sqs/message_attributes.rb +33 -0
  9. data/lib/txgh-queue/backends/sqs/producer.rb +33 -0
  10. data/lib/txgh-queue/backends/sqs/queue.rb +45 -0
  11. data/lib/txgh-queue/backends/sqs/retry_logic.rb +118 -0
  12. data/lib/txgh-queue/backends/sqs.rb +36 -0
  13. data/lib/txgh-queue/backends.rb +22 -0
  14. data/lib/txgh-queue/config.rb +48 -0
  15. data/lib/txgh-queue/error_handlers/github.rb +47 -0
  16. data/lib/txgh-queue/error_handlers/server_response.rb +22 -0
  17. data/lib/txgh-queue/error_handlers/standard_errors.rb +15 -0
  18. data/lib/txgh-queue/error_handlers/transifex.rb +23 -0
  19. data/lib/txgh-queue/error_handlers/txgh_errors.rb +27 -0
  20. data/lib/txgh-queue/error_handlers.rb +9 -0
  21. data/lib/txgh-queue/job.rb +77 -0
  22. data/lib/txgh-queue/result.rb +26 -0
  23. data/lib/txgh-queue/status.rb +51 -0
  24. data/lib/txgh-queue/supervisor.rb +76 -0
  25. data/lib/txgh-queue/version.rb +3 -0
  26. data/lib/txgh-queue/webhooks/github/request_handler.rb +34 -0
  27. data/lib/txgh-queue/webhooks/github.rb +7 -0
  28. data/lib/txgh-queue/webhooks/transifex/request_handler.rb +21 -0
  29. data/lib/txgh-queue/webhooks/transifex.rb +7 -0
  30. data/lib/txgh-queue/webhooks.rb +6 -0
  31. data/lib/txgh-queue.rb +14 -0
  32. data/spec/application_spec.rb +97 -0
  33. data/spec/backends/sqs/config_spec.rb +30 -0
  34. data/spec/backends/sqs/consumer_spec.rb +34 -0
  35. data/spec/backends/sqs/history_sequence_spec.rb +75 -0
  36. data/spec/backends/sqs/job_spec.rb +189 -0
  37. data/spec/backends/sqs/message_attributes_spec.rb +64 -0
  38. data/spec/backends/sqs/producer_spec.rb +32 -0
  39. data/spec/backends/sqs/queue_spec.rb +65 -0
  40. data/spec/backends/sqs/retry_logic_spec.rb +157 -0
  41. data/spec/backends/sqs_spec.rb +57 -0
  42. data/spec/backends_spec.rb +31 -0
  43. data/spec/config_spec.rb +15 -0
  44. data/spec/error_handlers/github_spec.rb +30 -0
  45. data/spec/error_handlers/server_response_spec.rb +36 -0
  46. data/spec/error_handlers/standard_errors_spec.rb +22 -0
  47. data/spec/error_handlers/transifex_spec.rb +43 -0
  48. data/spec/error_handlers/txgh_errors_spec.rb +25 -0
  49. data/spec/helpers/env_helpers.rb +13 -0
  50. data/spec/helpers/nil_logger.rb +10 -0
  51. data/spec/helpers/sqs/sqs_test_message.rb +47 -0
  52. data/spec/helpers/test_backend.rb +54 -0
  53. data/spec/job_spec.rb +111 -0
  54. data/spec/result_spec.rb +63 -0
  55. data/spec/spec_helper.rb +66 -0
  56. data/spec/status_spec.rb +68 -0
  57. data/spec/supervisor_spec.rb +40 -0
  58. data/spec/webhooks/github/request_handler_spec.rb +145 -0
  59. data/spec/webhooks/transifex/request_handler_spec.rb +87 -0
  60. data/txgh-queue.gemspec +24 -0
  61. metadata +172 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ff7b50a82408cdac666a7921a2f9f011bc8d5226
4
+ data.tar.gz: dbe3735109b47773c7bb8175544273f2edffd86c
5
+ SHA512:
6
+ metadata.gz: 8c0b6b954cf71c340094acf5de1f34d33796b3eb92bcd6ac4b13ec5758f3c0f38b97f2a35617e1d224e2fd93881cd554d5cb3ce8bb26e3326b22f47480bd5af8
7
+ data.tar.gz: d3113158ce321050b04ebe1c6abdb8260039a8cfe00ac6764d45f46d7d4cd2c1da7be04b101674aa9bc4bd234b96465f5e59c5a2c78e096b661784d4fadaed6d
@@ -0,0 +1,35 @@
1
+ require 'sinatra'
2
+ require 'sinatra/json'
3
+
4
+ module TxghQueue
5
+ module RespondWith
6
+ def respond_with(resp)
7
+ env['txgh.response'] = resp
8
+ status resp.status
9
+ json resp.body
10
+ end
11
+ end
12
+
13
+ class WebhookEndpoints < Sinatra::Base
14
+ include TxghQueue::Webhooks
15
+ helpers RespondWith
16
+
17
+ configure do
18
+ set :logging, nil
19
+ logger = Txgh::TxLogger.logger
20
+ set :logger, logger
21
+ end
22
+
23
+ post '/transifex/enqueue' do
24
+ respond_with(
25
+ Transifex::RequestHandler.handle_request(request, settings.logger)
26
+ )
27
+ end
28
+
29
+ post '/github/enqueue' do
30
+ respond_with(
31
+ Github::RequestHandler.handle_request(request, settings.logger)
32
+ )
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,19 @@
1
+ require 'txgh'
2
+
3
+ module TxghQueue
4
+ module Backends
5
+ module Null
6
+
7
+ class << self
8
+ def producer_for(*args)
9
+ raise BackendNotConfiguredError, 'No queue backend has been configured'
10
+ end
11
+
12
+ def consumer_for(*args)
13
+ raise BackendNotConfiguredError, 'No queue backend has been configured'
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ module TxghQueue
2
+ module Backends
3
+ module Sqs
4
+ class Config
5
+ class << self
6
+ def queues
7
+ @queues ||= TxghQueue::Config.options[:queues].map do |queue_options|
8
+ Queue.new(queue_options)
9
+ end
10
+ end
11
+
12
+ def failure_queue
13
+ @failure_queue ||= Queue.new(
14
+ TxghQueue::Config.options[:failure_queue]
15
+ )
16
+ end
17
+
18
+ def get_queue(queue_name)
19
+ queues.find { |q| q.name == queue_name }
20
+ end
21
+
22
+ def reset!
23
+ @queues = nil
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ require 'json'
2
+ require 'txgh'
3
+
4
+ module TxghQueue
5
+ module Backends
6
+ module Sqs
7
+ class Consumer
8
+ RECEIVE_PARAMS = { message_attribute_names: %w(history_sequence) }
9
+
10
+ attr_reader :queues, :logger
11
+
12
+ def initialize(queues, logger)
13
+ @queues = queues
14
+ @logger = logger
15
+ end
16
+
17
+ def work
18
+ queues.each do |queue|
19
+ queue.receive_message(RECEIVE_PARAMS).messages.each do |message|
20
+ logger.info("Received message from #{queue.name}, id: #{message.message_id}")
21
+ Job.new(message, queue, logger).complete
22
+ end
23
+ end
24
+ rescue => e
25
+ Txgh.events.publish_error!(e)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,62 @@
1
+ require 'json'
2
+ require 'txgh'
3
+
4
+ module TxghQueue
5
+ module Backends
6
+ module Sqs
7
+ class HistorySequence
8
+ extend Forwardable
9
+ include Enumerable
10
+
11
+ class << self
12
+ def from_message(message)
13
+ if attribute = message.message_attributes['history_sequence']
14
+ new(JSON.parse(attribute.string_value))
15
+ else
16
+ new([])
17
+ end
18
+ end
19
+
20
+ def from_h(hash)
21
+ new(
22
+ JSON.parse(
23
+ Txgh::Utils.deep_symbolize_keys(hash)
24
+ .fetch(:history_sequence, {})
25
+ .fetch(:string_value, nil)
26
+ )
27
+ )
28
+ end
29
+ end
30
+
31
+ attr_reader :sequence
32
+
33
+ def_delegators :sequence, :[], :<<, :first, :last, :size, :length
34
+
35
+ def initialize(sequence)
36
+ @sequence = Txgh::Utils.deep_symbolize_keys(sequence)
37
+ end
38
+
39
+ def add(obj)
40
+ sequence << obj
41
+ end
42
+
43
+ def to_h
44
+ { string_value: sequence.to_json, data_type: 'String' }
45
+ end
46
+
47
+ def dup
48
+ # use marshal serialization to deep copy the sequence
49
+ self.class.new(Marshal.load(Marshal.dump(sequence)))
50
+ end
51
+
52
+ def current
53
+ sequence.last
54
+ end
55
+
56
+ def each(&block)
57
+ sequence.each(&block)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,115 @@
1
+ require 'json'
2
+
3
+ module TxghQueue
4
+ module Backends
5
+ module Sqs
6
+ class Job < TxghQueue::Job
7
+ attr_reader :message, :message_attributes, :queue, :logger
8
+
9
+ def initialize(message, queue, logger)
10
+ @message = message
11
+ @queue = queue
12
+ @message_attributes = MessageAttributes.from_message(message)
13
+
14
+ # add empty retry attributes hash to sequence - will be populated when
15
+ # the complete method is called
16
+ message_attributes.history_sequence.add({})
17
+
18
+ super(logger)
19
+ end
20
+
21
+ def complete
22
+ result = process(payload)
23
+ logger.info("Finished processing #{message.message_id}, result: #{result.status}")
24
+
25
+ message_attributes.history_sequence.current.merge!(attributes_for(result))
26
+
27
+ return do_ok(result) if result.status.ok?
28
+ return do_retry(result) if result.status.retry?
29
+ return do_fail(result) if result.status.fail?
30
+ end
31
+
32
+ private
33
+
34
+ def attributes_for(result)
35
+ { status: result.status.to_s }.merge(
36
+ if result.has_error?
37
+ error_attributes_for(result)
38
+ else
39
+ response_attributes_for(result)
40
+ end
41
+ )
42
+ end
43
+
44
+ def error_attributes_for(result)
45
+ {
46
+ response_type: 'error',
47
+ class: result.error.class,
48
+ message: result.error.message,
49
+ backtrace: (result.error.backtrace || []).first,
50
+ error_tracking: publish_error_for(result)
51
+ }
52
+ end
53
+
54
+ def response_attributes_for(result)
55
+ {
56
+ response_type: 'response',
57
+ code: result.response.status,
58
+ body: result.response.body
59
+ }
60
+ end
61
+
62
+ def payload
63
+ @payload ||= begin
64
+ logger.info("Processing #{message.message_id}")
65
+
66
+ JSON.parse(message.body).tap do |payload|
67
+ logger.info("Payload: #{payload.inspect}")
68
+ end
69
+ end
70
+ end
71
+
72
+ def do_ok(result)
73
+ delete(message)
74
+ logger.info("Deleted #{message.message_id}")
75
+ end
76
+
77
+ def do_retry(result)
78
+ retry_logic = RetryLogic.new(message_attributes, result.status)
79
+
80
+ if retry_logic.retry?
81
+ logger.info("Retrying #{message.message_id} with #{retry_logic.next_delay_seconds} second delay")
82
+ new_message = queue.send_message(message.body, retry_logic.sqs_retry_params)
83
+ logger.info("Re-enqueued as #{new_message.message_id}")
84
+ delete(message)
85
+ logger.info("Deleted original #{message.message_id}")
86
+ elsif retry_logic.retries_exceeded?
87
+ logger.info("Message #{message.message_id} has exceeded allowed retries.")
88
+ do_fail(result)
89
+ end
90
+ end
91
+
92
+ def do_fail(result)
93
+ # send to failure queue
94
+ new_message = Config.failure_queue.send_message(
95
+ message.body, message_attributes: message_attributes.to_h
96
+ )
97
+
98
+ delete(message)
99
+ end
100
+
101
+ def publish_error_for(result)
102
+ Txgh.events.publish_error(result.error, {
103
+ payload: payload,
104
+ message_id: message.message_id,
105
+ queue: queue.name
106
+ })
107
+ end
108
+
109
+ def delete(message)
110
+ queue.delete_message(message.receipt_handle)
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,33 @@
1
+ module TxghQueue
2
+ module Backends
3
+ module Sqs
4
+ class MessageAttributes
5
+ class << self
6
+ def from_message(message)
7
+ history_sequence = HistorySequence.from_message(message)
8
+ new(history_sequence)
9
+ end
10
+
11
+ def from_h(hash)
12
+ history_sequence = HistorySequence.from_h(hash)
13
+ new(history_sequence)
14
+ end
15
+ end
16
+
17
+ attr_reader :history_sequence
18
+
19
+ def initialize(history_sequence)
20
+ @history_sequence = history_sequence
21
+ end
22
+
23
+ def to_h
24
+ { history_sequence: history_sequence.to_h }
25
+ end
26
+
27
+ def dup
28
+ self.class.new(history_sequence.dup)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ require 'json'
2
+
3
+ module TxghQueue
4
+ module Backends
5
+ module Sqs
6
+ class Producer
7
+ attr_reader :queues, :logger
8
+
9
+ def initialize(queues, logger)
10
+ @queues = queues
11
+ @logger = logger
12
+ end
13
+
14
+ def enqueue(payload, options = {})
15
+ payload_json = payload.to_json
16
+
17
+ message_ids = queues.map do |queue|
18
+ new_message = queue.send_message(payload_json, options)
19
+
20
+ logger.info(
21
+ "Enqueued new message with id #{new_message.message_id} and params "\
22
+ "#{payload_json}"
23
+ )
24
+
25
+ new_message.message_id
26
+ end
27
+
28
+ { message_ids: message_ids }
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,45 @@
1
+ require 'aws-sdk'
2
+
3
+ module TxghQueue
4
+ module Backends
5
+ module Sqs
6
+ class Queue
7
+ attr_reader :name, :region, :events
8
+
9
+ def initialize(options = {})
10
+ @name = options.fetch(:name)
11
+ @region = options.fetch(:region)
12
+ @events = options.fetch(:events, [])
13
+ end
14
+
15
+ def client
16
+ @client ||= Aws::SQS::Client.new(region: region)
17
+ end
18
+
19
+ def url
20
+ @url ||= client.get_queue_url(queue_name: name).queue_url
21
+ end
22
+
23
+ def receive_message(options = {})
24
+ client.receive_message(options.merge(queue_url: url))
25
+ end
26
+
27
+ def send_message(body, options = {})
28
+ params = options.merge(message_body: body, queue_url: url)
29
+ client.send_message(params)
30
+ end
31
+
32
+ def delete_message(receipt_handle)
33
+ params = { queue_url: url, receipt_handle: receipt_handle }
34
+ client.delete_message(params)
35
+ end
36
+
37
+ private
38
+
39
+ def sqs_attributes
40
+ @sqs_attributes ||= client.get_queue_attributes(queue_url: url)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,118 @@
1
+ require 'json'
2
+
3
+ module TxghQueue
4
+ module Backends
5
+ module Sqs
6
+ class RetriesExceededError < StandardError; end
7
+
8
+ class RetryLogic
9
+ SEQUENTIAL_MAX_RETRIES = 5
10
+ OVERALL_MAX_RETRIES = 15
11
+
12
+ # SQS max is 15 minutes, or 900 seconds
13
+ DELAY_INTERVALS = [16, 32, 64, 128, 256, 512, 900]
14
+
15
+ attr_reader :message_attributes, :current_status
16
+
17
+ def initialize(message_attributes, current_status)
18
+ @message_attributes = message_attributes.dup
19
+ @current_status = current_status
20
+ end
21
+
22
+ def retry?
23
+ retry_with_delay? || retry_without_delay?
24
+ end
25
+
26
+ def retries_exceeded?
27
+ max_overall_retries_exceeded? ||
28
+ max_sequential_retries_exceeded? ||
29
+ max_sequential_delays_exceeded?
30
+ end
31
+
32
+ def next_delay_seconds
33
+ raise RetriesExceededError unless retry?
34
+ return 0 unless retry_with_delay?
35
+ DELAY_INTERVALS[[current_sequence.size - 1, 0].max]
36
+ end
37
+
38
+ def sqs_retry_params
39
+ if retry_without_delay?
40
+ sqs_params_without_delay
41
+ elsif retry_with_delay?
42
+ sqs_params_with_delay
43
+ else
44
+ raise RetriesExceededError
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def retry_with_delay?
51
+ return false if max_overall_retries_exceeded?
52
+ return false unless current_status.retry_with_delay?
53
+ !max_sequential_delays_exceeded?
54
+ end
55
+
56
+ def retry_without_delay?
57
+ return false if max_overall_retries_exceeded?
58
+ return false unless current_status.retry_without_delay?
59
+ !max_sequential_retries_exceeded?
60
+ end
61
+
62
+ def sqs_params_without_delay
63
+ { message_attributes: message_attributes.to_h }
64
+ end
65
+
66
+ def sqs_params_with_delay
67
+ sqs_params_without_delay.merge(delay_seconds: next_delay_seconds)
68
+ end
69
+
70
+ def max_overall_retries_exceeded?
71
+ history_sequence.size >= OVERALL_MAX_RETRIES
72
+ end
73
+
74
+ def max_sequential_delays_exceeded?
75
+ delay_sequence_will_continue? && current_sequence.size >= DELAY_INTERVALS.size
76
+ end
77
+
78
+ def max_sequential_retries_exceeded?
79
+ retry_sequence_will_continue? && current_sequence.size >= SEQUENTIAL_MAX_RETRIES
80
+ end
81
+
82
+ def retry_sequence_will_continue?
83
+ current_status.status == last_status &&
84
+ current_status.retry_without_delay?
85
+ end
86
+
87
+ def delay_sequence_will_continue?
88
+ current_status.status == last_status &&
89
+ current_status.retry_with_delay?
90
+ end
91
+
92
+ def last_status
93
+ rt = current_sequence.last
94
+ rt.to_sym if rt
95
+ end
96
+
97
+ def current_sequence
98
+ return [] unless sequence = chunked_history_sequence.last
99
+ return [] unless last_elem = sequence.last
100
+ return [] unless current_status.status.to_s == last_elem
101
+ sequence
102
+ end
103
+
104
+ def chunked_history_sequence
105
+ @chunked_history_sequence ||= history_sequence.lazy
106
+ .map { |elem| elem[:status] }
107
+ .chunk(&:itself)
108
+ .map(&:last)
109
+ .to_a
110
+ end
111
+
112
+ def history_sequence
113
+ message_attributes.history_sequence
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,36 @@
1
+ require 'txgh'
2
+
3
+ module TxghQueue
4
+ module Backends
5
+ module Sqs
6
+ autoload :Config, 'txgh-queue/backends/sqs/config'
7
+ autoload :Consumer, 'txgh-queue/backends/sqs/consumer'
8
+ autoload :HistorySequence, 'txgh-queue/backends/sqs/history_sequence'
9
+ autoload :Job, 'txgh-queue/backends/sqs/job'
10
+ autoload :MessageAttributes, 'txgh-queue/backends/sqs/message_attributes'
11
+ autoload :Producer, 'txgh-queue/backends/sqs/producer'
12
+ autoload :Queue, 'txgh-queue/backends/sqs/queue'
13
+ autoload :RetryLogic, 'txgh-queue/backends/sqs/retry_logic'
14
+
15
+ class << self
16
+ def producer_for(events, logger = Txgh::TxLogger.logger)
17
+ Producer.new(find_queues_for(Array(events)), logger)
18
+ end
19
+
20
+ def consumer_for(events, logger = Txgh::TxLogger.logger)
21
+ Consumer.new(find_queues_for(Array(events)), logger)
22
+ end
23
+
24
+ private
25
+
26
+ def find_queues_for(events)
27
+ queues = events.flat_map do |event|
28
+ Config.queues.select { |queue| queue.events.include?(event) }
29
+ end
30
+
31
+ queues.uniq
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,22 @@
1
+ module TxghQueue
2
+ module Backends
3
+ autoload :Null, 'txgh-queue/backends/null'
4
+ autoload :Sqs, 'txgh-queue/backends/sqs'
5
+
6
+ class BackendNotConfiguredError < StandardError; end
7
+
8
+ class << self
9
+ def register(name, klass)
10
+ all[name] = klass
11
+ end
12
+
13
+ def get(name)
14
+ all[name]
15
+ end
16
+
17
+ def all
18
+ @all ||= {}
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,48 @@
1
+ require 'yaml'
2
+
3
+ module TxghQueue
4
+ class Config
5
+ DEFAULT_BACKEND = 'null'
6
+
7
+ class << self
8
+ def backend
9
+ TxghQueue::Backends.get(
10
+ raw_config.fetch(:backend, DEFAULT_BACKEND)
11
+ )
12
+ end
13
+
14
+ def processing_enabled?
15
+ raw_config.fetch(:processing_enabled, true)
16
+ end
17
+
18
+ def options
19
+ raw_config.fetch(:options, {})
20
+ end
21
+
22
+ def reset!
23
+ @raw_config = nil
24
+ end
25
+
26
+ private
27
+
28
+ def raw_config
29
+ @raw_config ||= begin
30
+ if ENV['TXGH_QUEUE_CONFIG']
31
+ scheme, payload = ENV['TXGH_QUEUE_CONFIG'].split('://')
32
+ send(:"load_#{scheme}", payload)
33
+ else
34
+ {}
35
+ end
36
+ end
37
+ end
38
+
39
+ def load_file(payload)
40
+ Txgh::Utils.deep_symbolize_keys(YAML.load_file(payload))
41
+ end
42
+
43
+ def load_raw(payload)
44
+ Txgh::Utils.deep_symbolize_keys(YAML.load(payload))
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,47 @@
1
+ require 'octokit'
2
+
3
+ module TxghQueue
4
+ module ErrorHandlers
5
+ class Github
6
+ ERROR_CLASSES = {
7
+ Octokit::AbuseDetected => Status.fail,
8
+ Octokit::BadGateway => Status.retry_without_delay,
9
+ Octokit::BadRequest => Status.fail,
10
+ Octokit::ClientError => Status.fail,
11
+ Octokit::Conflict => Status.fail,
12
+ Octokit::Forbidden => Status.fail,
13
+ Octokit::InternalServerError => Status.retry_with_delay,
14
+ Octokit::MethodNotAllowed => Status.fail,
15
+ Octokit::NotAcceptable => Status.fail,
16
+ Octokit::NotFound => Status.fail,
17
+ Octokit::NotImplemented => Status.fail,
18
+ Octokit::OneTimePasswordRequired => Status.fail,
19
+ Octokit::RepositoryUnavailable => Status.retry_with_delay,
20
+ Octokit::ServerError => Status.retry_with_delay,
21
+ Octokit::ServiceUnavailable => Status.retry_with_delay,
22
+ Octokit::TooManyLoginAttempts => Status.retry_with_delay,
23
+ Octokit::TooManyRequests => Status.retry_with_delay,
24
+ Octokit::Unauthorized => Status.fail,
25
+ Octokit::UnprocessableEntity => Status.fail,
26
+ Octokit::UnsupportedMediaType => Status.fail,
27
+ Octokit::UnverifiedEmail => Status.fail
28
+ }
29
+
30
+ class << self
31
+ def can_handle?(error_or_response)
32
+ error_or_response.is_a?(Octokit::Error)
33
+ end
34
+
35
+ def status_for(error)
36
+ ERROR_CLASSES.fetch(error.class) { handle_other(error) }
37
+ end
38
+
39
+ private
40
+
41
+ def handle_other(error)
42
+ Status.fail
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end