diff --git a/CHANGELOG.md b/CHANGELOG.md index 662f2525..decded5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +### [Unreleased] +* Added access to response headers + ### [6.6.0] * Added support for `single_level` query parameter in Folders API for Microsoft accounts * Added support for `include_hidden_folders` query parameter in folders list endpoint for Microsoft accounts to control whether hidden folders are included in the response diff --git a/lib/nylas/handler/http_client.rb b/lib/nylas/handler/http_client.rb index d373140c..0eb14ec5 100644 --- a/lib/nylas/handler/http_client.rb +++ b/lib/nylas/handler/http_client.rb @@ -41,7 +41,8 @@ def execute(method:, path:, timeout:, headers: {}, query: {}, payload: nil, api_ content_type = response.headers["content-type"].downcase end - parsed_response = parse_json_evaluate_error(result.code.to_i, response.body, path, content_type) + parsed_response = parse_json_evaluate_error(result.code.to_i, response.body, path, content_type, + response.headers) # Include headers in the response parsed_response[:headers] = response.headers unless parsed_response.nil? parsed_response @@ -311,37 +312,34 @@ def handle_response(http, get_request, path, &block) end # Parses the response from the Nylas API and evaluates for errors. - def parse_json_evaluate_error(http_code, response, path, content_type = nil) + def parse_json_evaluate_error(http_code, response, path, content_type = nil, headers = nil) begin response = parse_response(response) if content_type == "application/json" rescue Nylas::JsonParseError - handle_failed_response(http_code, response, path) + handle_failed_response(http_code, response, path, headers) raise end - handle_failed_response(http_code, response, path) + handle_failed_response(http_code, response, path, headers) response end # Handles failed responses from the Nylas API. - def handle_failed_response(http_code, response, path) + def handle_failed_response(http_code, response, path, headers = nil) return if HTTP_SUCCESS_CODES.include?(http_code) case response when Hash - raise error_hash_to_exception(response, http_code, path) + raise error_hash_to_exception(response, http_code, path, headers) else raise NylasApiError.parse_error_response(response, http_code) end end # Converts error hashes to exceptions. - def error_hash_to_exception(response, status_code, path) + def error_hash_to_exception(response, status_code, path, headers = nil) return if !response || !response.key?(:error) - # Safely get headers without risking KeyError - headers = response.key?(:headers) ? response[:headers] : nil - if %W[#{api_uri}/v3/connect/token #{api_uri}/v3/connect/revoke].include?(path) NylasOAuthError.new(response[:error], response[:error_description], response[:error_uri], response[:error_code], status_code) diff --git a/spec/nylas/handler/http_client_spec.rb b/spec/nylas/handler/http_client_spec.rb index 34057eda..980f8c54 100644 --- a/spec/nylas/handler/http_client_spec.rb +++ b/spec/nylas/handler/http_client_spec.rb @@ -318,6 +318,35 @@ class TestHttpClient expect(response).to eq(response_json.merge(headers: mock_headers)) end + it "returns an error with headers" do + response_json = { + foo: "bar", + error: { + type: "api_error", + message: "An unexpected error occurred", + provider_error: "This is the provider error" + } + } + request_params = { method: :get, path: "https://test.api.nylas.com/foo", timeout: 30 } + mock_headers = { + "content-type" => "application/json", + "x-request-id" => "123", + "some-header" => "value" + } + mock_response = instance_double("HTTParty::Response", + body: response_json.to_json, + headers: mock_headers, + code: 429) + + allow(HTTParty).to receive(:get).and_return(mock_response) + + expect do + http_client.send(:execute, **request_params) + end.to raise_error(Nylas::NylasApiError) { |error| + expect(error.headers).to eq(mock_headers) + } + end + it "raises a timeout error" do request_params = { method: :get, path: "https://test.api.nylas.com/foo", timeout: 30 } allow(HTTParty).to receive(:get).and_raise(Net::OpenTimeout) @@ -462,22 +491,23 @@ class TestHttpClient type: "api_error", message: "An unexpected error occurred", provider_error: "This is the provider error" - }, - headers: { - "x-request-id": "request-id-from-headers", - "x-ratelimit-limit": "100", - "x-ratelimit-remaining": "99" } } + headers = { + "x-request-id": "request-id-from-headers", + "x-ratelimit-limit": "100", + "x-ratelimit-remaining": "99" + } - err_obj = http_client.send(:error_hash_to_exception, response, 400, "https://test.api.nylas.com/foo") + err_obj = http_client.send(:error_hash_to_exception, response, 400, "https://test.api.nylas.com/foo", + headers) expect(err_obj).to be_a(Nylas::NylasApiError) expect(err_obj.message).to eq("An unexpected error occurred") expect(err_obj.request_id).to eq("request-id") expect(err_obj.provider_error).to eq("This is the provider error") expect(err_obj.type).to eq("api_error") - expect(err_obj.headers).to eq(response[:headers]) + expect(err_obj.headers).to eq(headers) end end @@ -555,11 +585,17 @@ class TestHttpClient provider_error: "This is the provider error" } } + headers = { + "x-request-id": "request-id-from-headers", + "x-ratelimit-limit": "100" + } expect do http_client.send(:parse_json_evaluate_error, 400, response.to_json, - "https://test.api.nylas.com/foo", "application/json") - end.to raise_error(Nylas::NylasApiError) + "https://test.api.nylas.com/foo", "application/json", headers) + end.to raise_error(Nylas::NylasApiError) { |error| + expect(error.headers).to eq(headers) + } end it "raises a NylasApiError for a non-JSON response" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0388c74e..799ee325 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,12 +1,17 @@ # frozen_string_literal: true require "simplecov" +require "simplecov-cobertura" + SimpleCov.start do add_filter "/spec/" -end -require "simplecov-cobertura" -SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter + # Use multiple formatters to ensure coverage data is available + formatter SimpleCov::Formatter::MultiFormatter.new([ + SimpleCov::Formatter::HTMLFormatter, + SimpleCov::Formatter::CoberturaFormatter + ]) +end require "nylas" require "support/nylas_helpers"