Skip to content

Commit

Permalink
Enhance parse_response Method to Improve Error Handling and Logging (
Browse files Browse the repository at this point in the history
  • Loading branch information
armando-rodriguez-cko authored Nov 19, 2024
1 parent 7ca3887 commit 7141ca3
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 94 deletions.
109 changes: 59 additions & 50 deletions lib/checkout_sdk/api_client.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# frozen_string_literal: true

require 'csv'

module CheckoutSdk
class ApiClient
attr_accessor :client, :multipart_client, :log

# @param [CheckoutConfiguration] configuration
# @param [String] uri
def initialize(configuration, uri)
@client = configuration.http_client.clone
@client.url_prefix = uri
Expand All @@ -14,42 +14,27 @@ def initialize(configuration, uri)
@log = configuration.logger
end

# @param [String] path
# @param [SdkAuthorization] authorization
# @param [Object] params
def invoke_get(path,
authorization,
params = nil)
def invoke_get(path, authorization, params = nil)
invoke(:get, path, authorization, params: params)
end

def invoke_post(path,
authorization,
request = nil,
idempotency_key = nil)
def invoke_post(path, authorization, request = nil, idempotency_key = nil)
invoke(:post, path, authorization, request, idempotency_key)
end

def invoke_put(path,
authorization,
request)
def invoke_put(path, authorization, request)
invoke(:put, path, authorization, request)
end

def invoke_patch(path,
authorization,
request = nil)
def invoke_patch(path, authorization, request = nil)
invoke(:patch, path, authorization, request)
end

def invoke_delete(path,
authorization)
def invoke_delete(path, authorization)
invoke(:delete, path, authorization)
end

def submit_file(path,
authorization,
request)
def submit_file(path, authorization, request)
upload(path, authorization, request)
end

Expand All @@ -58,7 +43,7 @@ def submit_file(path,
def invoke(method, path, authorization, body = nil, idempotency_key = nil, params: nil)
path = append_params(path, params) unless params.nil?

headers = get_default_headers authorization
headers = default_headers(authorization)
headers[:'Content-Type'] = 'application/json'
headers[:'Cko-Idempotency-Key'] = idempotency_key unless idempotency_key.nil?

Expand All @@ -71,26 +56,23 @@ def invoke(method, path, authorization, body = nil, idempotency_key = nil, param
raise CheckoutApiException, e.response
end

parse_response response
parse_response(response)
end

def get_default_headers(authorization)
{
'User-Agent': "checkout-sdk-ruby/#{VERSION}",
Accept: 'application/json',
Authorization: authorization.authorization_header
}
def default_headers(authorization)
{ 'User-Agent': "checkout-sdk-ruby/#{VERSION}", Accept: 'application/json',
Authorization: authorization.authorization_header }
end

def append_params(path, input_params)
raise CheckoutArgumentException, 'Query parameters were not provided' if input_params.nil?

if input_params.is_a? String
params = input_params
else
hash = CheckoutSdk::JsonSerializer.to_custom_hash(input_params)
params = URI.encode_www_form(hash)
end
params = if input_params.is_a? String
input_params
else
hash = CheckoutSdk::JsonSerializer.to_custom_hash(input_params)
URI.encode_www_form(hash)
end

"#{path}?#{params}"
end
Expand All @@ -103,16 +85,16 @@ def build_multipart_request(file_request, file)
MIME::Types.type_for(file_request.file).first,
File.basename(file_request.file)
),
:purpose => file_request.purpose
purpose: file_request.purpose
}
end

def upload(path, authorization, file_request)
headers = get_default_headers authorization
headers = default_headers(authorization)

file = File.open(file_request.file)

form = build_multipart_request file_request, file
form = build_multipart_request(file_request, file)

begin
@log.info "post: /#{path}"
Expand All @@ -123,28 +105,55 @@ def upload(path, authorization, file_request)
file.close
end

parse_response response
parse_response(response)
end

def parse_response(response)
raise CheckoutApiException, response if response.status < 200 || response.status >= 400
raise CheckoutApiException, response if response.status < 200 || response.status >= 300

metadata = CheckoutUtils.map_to_http_metadata(response)
body = parse_json_or_contents(response)
body = OpenStruct.new if body.nil?
body = OpenStruct.new(items: body) if body.is_a? Array
body.http_metadata = metadata if body.is_a? OpenStruct
body = parse_body(response)

if body.is_a?(Array)
body = OpenStruct.new(items: body)
elsif !body.is_a?(OpenStruct)
body = OpenStruct.new(contents: body)
end

body.http_metadata = metadata if body.is_a?(OpenStruct)

body
rescue JSON::ParserError => e
raise CheckoutApiException.new(response, "Error parsing JSON: #{e.message}")
rescue StandardError => e
@log&.error("Unexpected error occurred: #{e.message}")
raise
end

def parse_json_or_contents(response)
return if response.body.nil? || response.body == ''
def parse_body(response)
content_type = response.headers['Content-Type']
return OpenStruct.new if response.body.nil? || response.body.empty?

if response.body.start_with?('{', '[')
JSON.parse(response.body, object_class: OpenStruct)
if content_type&.include?('application/json')
parsed_value = JSON.parse(response.body)
deep_convert_to_ostruct(parsed_value)
elsif content_type&.include?('text/csv')
csv_data = CSV.parse(response.body, headers: true)
OpenStruct.new(csv: csv_data)
else
OpenStruct.new(contents: response.body)
end
end

def deep_convert_to_ostruct(obj)
case obj
when Hash
OpenStruct.new(obj.transform_values { |value| deep_convert_to_ostruct(value) })
when Array
obj.map { |item| deep_convert_to_ostruct(item) }
else
obj
end
end
end
end
5 changes: 2 additions & 3 deletions lib/checkout_sdk/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ class Client
attr_reader :api_client,
:authorization_type,
:configuration
protected :api_client, :authorization_type, :configuration

protected

# @param [CheckoutSdk::ApiClient] api_client
# @param [CheckoutConfiguration] configuration
Expand All @@ -22,8 +23,6 @@ def initialize(api_client, configuration, authorization_type)
@configuration = configuration
end

protected

# @param [Array] args
def build_path(*args)
args.join('/')
Expand Down
32 changes: 24 additions & 8 deletions lib/checkout_sdk/error.rb
Original file line number Diff line number Diff line change
@@ -1,30 +1,46 @@
# frozen_string_literal: true

module CheckoutSdk
class CheckoutException < StandardError
end
class CheckoutException < StandardError; end

class CheckoutArgumentException < CheckoutException; end

class CheckoutAuthorizationException < CheckoutException
def self.invalid_authorization(authorization_type)
CheckoutAuthorizationException.new("Operation requires #{authorization_type} authorization type")
new("Operation requires #{authorization_type} authorization type.")
end

def self.invalid_key(key_type)
CheckoutAuthorizationException.new("#{key_type} is required for this operation.")
new("#{key_type} is required for this operation.")
end
end

class CheckoutApiException < CheckoutException
attr_reader :http_metadata, :error_details

def initialize(response)
def initialize(response, message = nil)
@http_metadata = CheckoutUtils.map_to_http_metadata(response)
if !http_metadata.body.nil? && http_metadata.body != ''
@error_details = JSON.parse(http_metadata.body, object_class: OpenStruct)
@error_details = parse_error_details(http_metadata.body)
super(message || build_error_message)
end

private

def parse_error_details(body)
return if body.nil? || body.empty?

JSON.parse(body, object_class: OpenStruct)
rescue JSON::ParserError
nil
end

def build_error_message
message = "The API response status code (#{http_metadata.status_code}) does not indicate success."
if @error_details && !@error_details.to_h.empty?
details = @error_details.to_h.map { |key, value| "#{key}: #{value}" }.join(', ')
message += " Details: #{details}."
end
super("The API response status code (#{http_metadata.status_code}) does not indicate success.")
message
end
end
end
Loading

0 comments on commit 7141ca3

Please sign in to comment.