Implement protocols.io error handling for api client and normalizer

This commit is contained in:
Mojca Lorber 2019-06-18 14:01:57 +02:00
parent c691ad80b5
commit ebcaa4b014
13 changed files with 235 additions and 8 deletions

View file

@ -27,7 +27,15 @@ class ExternalProtocolsController < ApplicationController
endpoint_name = Constants::PROTOCOLS_ENDPOINTS.dig(*show_params[:protocol_source]
.split('/').map(&:to_sym))
api_client = "ProtocolImporters::#{endpoint_name}::ApiClient".constantize.new
html_preview = api_client.protocol_html_preview(show_params[:protocol_id])
begin
html_preview = api_client.protocol_html_preview(show_params[:protocol_id])
rescue SocketError, HTTParty::Error => e
render json: {
errors: [network: e.message]
}
return
end
render json: {
protocol_source: show_params[:protocol_source],

View file

@ -17,9 +17,24 @@ module ProtocolImporters
def call
return self unless valid?
# TODO: check for errors
api_response = api_client.single_protocol(@id)
normalized_hash = normalizer.normalize_protocol(api_response)
# Call api client
begin
api_response = api_client.single_protocol(@id)
rescue api_errors => e
@errors[e.class.to_s.downcase.to_sym] = e.error_message
return self
rescue SocketError, HTTParty::Error => e
@errors[e.class.to_s.downcase.to_sym] = e.message
return self
end
# Normalize protocol
begin
normalized_hash = normalizer.normalize_protocol(api_response)
rescue normalizer_errors => e
@errors[e.class.to_s.downcase.to_sym] = e.error_message
return self
end
pio = ProtocolImporters::ProtocolIntermediateObject.new(normalized_json: normalized_hash,
user: @user,
@ -63,5 +78,13 @@ module ProtocolImporters
def normalizer
"ProtocolImporters::#{endpoint_name}::ProtocolNormalizer".constantize.new
end
def api_errors
"ProtocolImporters::#{endpoint_name}::ApiErrors::Error".constantize
end
def normalizer_errors
"ProtocolImporters::#{endpoint_name}::NormalizerError".constantize
end
end
end

View file

@ -17,9 +17,24 @@ module ProtocolImporters
def call
return self unless valid?
api_response = api_client.protocol_list(@query_params)
# Call api client
begin
api_response = api_client.protocol_list(@query_params)
rescue api_errors => e
@errors[e.class.to_s.downcase.to_sym] = e.error_message
return self
rescue SocketError, HTTParty::Error => e
@errors[e.class.to_s.downcase.to_sym] = e.message
return self
end
@protocols_list = normalizer.normalize_list(api_response)
# Normalize protocols list
begin
@protocols_list = normalizer.normalize_list(api_response)
rescue normalizer_errors => e
@errors[e.class.to_s.downcase.to_sym] = e.error_message
return self
end
self
end
@ -66,5 +81,13 @@ module ProtocolImporters
def normalizer
"ProtocolImporters::#{endpoint_name}::ProtocolNormalizer".constantize.new
end
def api_errors
"ProtocolImporters::#{endpoint_name}::ApiErrors::Error".constantize
end
def normalizer_errors
"ProtocolImporters::#{endpoint_name}::NormalizerError".constantize
end
end
end

View file

@ -12,9 +12,12 @@ module ProtocolImporters
default_timeout CONSTANTS[:default_timeout]
logger Rails.logger, CONSTANTS[:debug_level]
attr_reader :errors
def initialize(token = nil)
# Currently we support public tokens only (no token needed for public data)
@auth = { token: token }
@errors = {}
# Set default headers
self.class.headers('Authorization': "Bearer #{@auth[:token]}") if @auth[:token].present?
@ -45,12 +48,12 @@ module ProtocolImporters
query = CONSTANTS.dig(:endpoints, :protocols, :default_query_params)
.merge(query_params)
self.class.get('/protocols', query: query)
check_for_api_errors(self.class.get('/protocols', query: query))
end
# Returns full representation of given protocol ID
def single_protocol(id)
self.class.get("/protocols/#{id}")
check_for_api_errors(self.class.get("/protocols/#{id}"))
end
# Returns html preview for given protocol
@ -59,6 +62,22 @@ module ProtocolImporters
def protocol_html_preview(uri)
self.class.get("https://www.protocols.io/view/#{uri}.html", headers: {})
end
private
def check_for_api_errors(response)
if response['status_code'] == 0
return response
elsif response['status_code'] == 1
raise ApiErrors::MissingOrEmptyParametersError.new(response['status_code'], response['error_message'])
elsif response['status_code'] == 1218
raise ApiErrors::InvalidTokenError.new(response['status_code'], response['error_message'])
elsif response['status_code'] == 1219
raise ApiErrors::TokenExpiredError.new(response['status_code'], response['error_message'])
else
raise ApiErrors::Error.new(response['status_code'], response['error_message'])
end
end
end
end
end

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
module ProtocolImporters
module ProtocolsIO
module V3
module ApiErrors
class Error < StandardError
attr_reader :status_code, :error_message
def initialize(status_code, error_message)
@status_code = status_code
@error_message = error_message
end
end
end
end
end
end

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
module ProtocolImporters
module ProtocolsIO
module V3
module ApiErrors
class InvalidTokenError < Error
end
end
end
end
end

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
module ProtocolImporters
module ProtocolsIO
module V3
module ApiErrors
class MissingOrEmptyParametersError < Error
end
end
end
end
end

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
module ProtocolImporters
module ProtocolsIO
module V3
module ApiErrors
class TokenExpiredError < Error
end
end
end
end
end

View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
module ProtocolImporters
module ProtocolsIO
module V3
class NormalizerError < StandardError
attr_reader :error_type, :error_message
def initialize(error_type, error_message)
@error_type = error_type
@error_message = error_message
end
end
end
end
end

View file

@ -7,6 +7,9 @@ module ProtocolImporters
def normalize_protocol(client_data)
# client_data is HttpParty ApiReponse object
protocol_hash = client_data.parsed_response.with_indifferent_access[:protocol]
unless protocol_hash.present?
raise NormalizerError.new(:nil_protocol, 'Protocol not present in hash.')
end
normalized_data = {
uri: client_data.request.last_uri.to_s,
@ -54,6 +57,9 @@ module ProtocolImporters
def normalize_list(client_data)
# client_data is HttpParty ApiReponse object
protocols_hash = client_data.parsed_response.with_indifferent_access[:items]
unless protocols_hash.present?
raise NormalizerError.new(:nil_protocol_items, 'Protocol items not present in hash.')
end
normalized_data = {}
normalized_data[:protocols] = protocols_hash.map do |e|
{

View file

@ -78,6 +78,16 @@ describe ExternalProtocolsController, type: :controller do
expect(JSON.parse(response.body)['html']).to eq(html_preview)
end
it 'return network errors when calling api client' do
allow_any_instance_of(ProtocolImporters::ProtocolsIO::V3::ApiClient)
.to(receive(:protocol_html_preview)
.and_raise(SocketError))
# Call action
action
expect(JSON.parse(response.body)).to have_key('errors')
end
it 'returns error JSON and 400 response when something went wrong' do
allow_any_instance_of(ProtocolImporters::ProtocolsIO::V3::ApiClient)
.to(receive(:protocol_html_preview)).and_raise(StandardError)

View file

@ -22,6 +22,40 @@ describe ProtocolImporters::BuildProtocolFromClientService do
end
end
context 'when raise api client error' do
it 'return network errors' do
allow_any_instance_of(ProtocolImporters::ProtocolsIO::V3::ApiClient)
.to(receive(:single_protocol)
.and_raise(SocketError))
expect(service_call.errors).to have_key(:socketerror)
end
it 'return api errors' do
allow_any_instance_of(ProtocolImporters::ProtocolsIO::V3::ApiClient)
.to(receive(:single_protocol)
.and_raise(ProtocolImporters::ProtocolsIO::V3::ApiErrors::MissingOrEmptyParametersError.new('1', 'Missing Or Empty Parameters Error')))
expect(service_call.errors).to have_key(:"protocolimporters::protocolsio::v3::apierrors::missingoremptyparameterserror")
end
end
context 'when normalize protocol fails' do
it 'return normalizer errors' do
client_data = double('api_response')
allow_any_instance_of(ProtocolImporters::ProtocolsIO::V3::ApiClient)
.to(receive(:single_protocol)
.and_return(client_data))
allow_any_instance_of(ProtocolImporters::ProtocolsIO::V3::ProtocolNormalizer)
.to(receive(:normalize_protocol).with(client_data)
.and_raise(ProtocolImporters::ProtocolsIO::V3::NormalizerError.new('nil_protocol', 'Nil Protocol')))
expect(service_call.errors).to have_key(:"protocolimporters::protocolsio::v3::normalizererror")
end
end
context 'when have valid arguments' do
before do
client_data = double('api_response')

View file

@ -32,6 +32,40 @@ describe ProtocolImporters::SearchProtocolsService do
end
end
context 'when raise api client error' do
it 'return network errors' do
allow_any_instance_of(ProtocolImporters::ProtocolsIO::V3::ApiClient)
.to(receive(:protocol_list)
.and_raise(SocketError))
expect(service_call.errors).to have_key(:socketerror)
end
it 'return api errors' do
allow_any_instance_of(ProtocolImporters::ProtocolsIO::V3::ApiClient)
.to(receive(:protocol_list)
.and_raise(ProtocolImporters::ProtocolsIO::V3::ApiErrors::MissingOrEmptyParametersError.new('1', 'Missing Or Empty Parameters Error')))
expect(service_call.errors).to have_key(:"protocolimporters::protocolsio::v3::apierrors::missingoremptyparameterserror")
end
end
context 'when normalize protocol fails' do
it 'return normalizer errors' do
client_data = double('api_response')
allow_any_instance_of(ProtocolImporters::ProtocolsIO::V3::ApiClient)
.to(receive(:protocol_list)
.and_return(client_data))
allow_any_instance_of(ProtocolImporters::ProtocolsIO::V3::ProtocolNormalizer)
.to(receive(:normalize_list).with(client_data)
.and_raise(ProtocolImporters::ProtocolsIO::V3::NormalizerError.new('nil_items', 'Nil Items')))
expect(service_call.errors).to have_key(:"protocolimporters::protocolsio::v3::normalizererror")
end
end
context 'when have valid attributes' do
before do
client_data = double('api_response')