diff --git a/lib/octokit/error.rb b/lib/octokit/error.rb index cf53245ca..121f96b12 100644 --- a/lib/octokit/error.rb +++ b/lib/octokit/error.rb @@ -1,7 +1,7 @@ module Octokit # Custom error class for rescuing from all GitHub errors class Error < StandardError - + attr_reader :context # Returns the appropriate Octokit::Error subclass based # on status and response message # @@ -34,9 +34,16 @@ def self.from_response(response) end end + def build_error_context + if RATE_LIMITED_ERRORS.include?(self.class) + @context = Octokit::RateLimit.from_response(@response) + end + end + def initialize(response=nil) @response = response super(build_error_message) + build_error_context end # Documentation URL returned by the API for some errors @@ -315,4 +322,5 @@ class ApplicationCredentialsRequired < StandardError; end # Raised when a repository is created with an invalid format class InvalidRepository < ArgumentError; end + RATE_LIMITED_ERRORS = [Octokit::TooManyRequests, Octokit::AbuseDetected] end diff --git a/lib/octokit/rate_limit.rb b/lib/octokit/rate_limit.rb index b6ff0fb37..201bc674c 100644 --- a/lib/octokit/rate_limit.rb +++ b/lib/octokit/rate_limit.rb @@ -20,7 +20,7 @@ class RateLimit < Struct.new(:limit, :remaining, :resets_at, :resets_in) # @return [RateLimit] def self.from_response(response) info = new - if response && !response.headers.nil? + if response && response.respond_to?(:headers) && !response.headers.nil? info.limit = (response.headers['X-RateLimit-Limit'] || 1).to_i info.remaining = (response.headers['X-RateLimit-Remaining'] || 1).to_i info.resets_at = Time.at((response.headers['X-RateLimit-Reset'] || Time.now).to_i) diff --git a/spec/octokit/client_spec.rb b/spec/octokit/client_spec.rb index de4a5234c..7aadb4d93 100644 --- a/spec/octokit/client_spec.rb +++ b/spec/octokit/client_spec.rb @@ -1055,6 +1055,53 @@ end end + it "returns empty context when non rate limit error occurs" do + stub_get('/user').to_return \ + :status => 509, + :headers => { + :content_type => "application/json", + }, + :body => {:message => "Bandwidth exceeded"}.to_json + begin + Octokit.get('/user') + rescue Octokit::ServerError => server_error + expect(server_error.context).to be_nil + end + end + + it "returns context with default data when rate limit error occurs but headers are missing" do + stub_get('/user').to_return \ + :status => 403, + :headers => { + :content_type => "application/json", + }, + :body => {:message => "rate limit exceeded"}.to_json + begin + expect_any_instance_of(Faraday::Env).to receive(:headers).at_least(:once).and_return({}) + Octokit.get('/user') + rescue Octokit::TooManyRequests => rate_limit_error + expect(rate_limit_error.context).to be_an_instance_of(Octokit::RateLimit) + end + end + + it "returns context when non rate limit error occurs but rate limit headers are present" do + stub_get('/user').to_return \ + :status => 403, + :headers => { + 'content_type' => 'application/json' + }, + :body => {:message => "rate limit exceeded"}.to_json + begin + rate_limit_headers = {'X-RateLimit-Limit' => 60, 'X-RateLimit-Remaining' => 42, 'X-RateLimit-Reset' => (Time.now + 60).to_i} + expect_any_instance_of(Faraday::Env).to receive(:headers).at_least(:once).and_return(rate_limit_headers) + Octokit.get('/user') + rescue Octokit::TooManyRequests => rate_limit_error + expect(rate_limit_error.context).to be_an_instance_of(Octokit::RateLimit) + expect(rate_limit_error.context.limit).to eql 60 + expect(rate_limit_error.context.remaining).to eql 42 + end + end + describe "module call shortcut" do before do Octokit.reset!