diff --git a/app/controllers/external_protocols_controller.rb b/app/controllers/external_protocols_controller.rb index 3f1936f87..d6fa2816a 100644 --- a/app/controllers/external_protocols_controller.rb +++ b/app/controllers/external_protocols_controller.rb @@ -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], diff --git a/app/services/protocol_importers/build_protocol_from_client_service.rb b/app/services/protocol_importers/build_protocol_from_client_service.rb index 434a1cfe6..cf135aa3e 100644 --- a/app/services/protocol_importers/build_protocol_from_client_service.rb +++ b/app/services/protocol_importers/build_protocol_from_client_service.rb @@ -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 diff --git a/app/services/protocol_importers/search_protocols_service.rb b/app/services/protocol_importers/search_protocols_service.rb index abd5a620e..202ec9f1c 100644 --- a/app/services/protocol_importers/search_protocols_service.rb +++ b/app/services/protocol_importers/search_protocols_service.rb @@ -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 diff --git a/app/utilities/protocol_importers/protocols_io/v3/api_client.rb b/app/utilities/protocol_importers/protocols_io/v3/api_client.rb index a4b907a93..c52d2a027 100644 --- a/app/utilities/protocol_importers/protocols_io/v3/api_client.rb +++ b/app/utilities/protocol_importers/protocols_io/v3/api_client.rb @@ -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 diff --git a/app/utilities/protocol_importers/protocols_io/v3/api_errors/error.rb b/app/utilities/protocol_importers/protocols_io/v3/api_errors/error.rb new file mode 100644 index 000000000..822b7bd4c --- /dev/null +++ b/app/utilities/protocol_importers/protocols_io/v3/api_errors/error.rb @@ -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 diff --git a/app/utilities/protocol_importers/protocols_io/v3/api_errors/invalid_token_error.rb b/app/utilities/protocol_importers/protocols_io/v3/api_errors/invalid_token_error.rb new file mode 100644 index 000000000..b1e8c738c --- /dev/null +++ b/app/utilities/protocol_importers/protocols_io/v3/api_errors/invalid_token_error.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module ProtocolImporters + module ProtocolsIO + module V3 + module ApiErrors + class InvalidTokenError < Error + end + end + end + end +end diff --git a/app/utilities/protocol_importers/protocols_io/v3/api_errors/missing_or_empty_parameters_error.rb b/app/utilities/protocol_importers/protocols_io/v3/api_errors/missing_or_empty_parameters_error.rb new file mode 100644 index 000000000..a228c6b9b --- /dev/null +++ b/app/utilities/protocol_importers/protocols_io/v3/api_errors/missing_or_empty_parameters_error.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module ProtocolImporters + module ProtocolsIO + module V3 + module ApiErrors + class MissingOrEmptyParametersError < Error + end + end + end + end +end diff --git a/app/utilities/protocol_importers/protocols_io/v3/api_errors/token_expired_error.rb b/app/utilities/protocol_importers/protocols_io/v3/api_errors/token_expired_error.rb new file mode 100644 index 000000000..e0c533b14 --- /dev/null +++ b/app/utilities/protocol_importers/protocols_io/v3/api_errors/token_expired_error.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module ProtocolImporters + module ProtocolsIO + module V3 + module ApiErrors + class TokenExpiredError < Error + end + end + end + end +end diff --git a/app/utilities/protocol_importers/protocols_io/v3/normalizer_error.rb b/app/utilities/protocol_importers/protocols_io/v3/normalizer_error.rb new file mode 100644 index 000000000..1c33b81d4 --- /dev/null +++ b/app/utilities/protocol_importers/protocols_io/v3/normalizer_error.rb @@ -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 diff --git a/app/utilities/protocol_importers/protocols_io/v3/protocol_normalizer.rb b/app/utilities/protocol_importers/protocols_io/v3/protocol_normalizer.rb index a517e3bcc..af1df5e4e 100644 --- a/app/utilities/protocol_importers/protocols_io/v3/protocol_normalizer.rb +++ b/app/utilities/protocol_importers/protocols_io/v3/protocol_normalizer.rb @@ -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| { diff --git a/spec/controllers/external_protocols_controller_spec.rb b/spec/controllers/external_protocols_controller_spec.rb index 94e0cc1c6..9abf14098 100644 --- a/spec/controllers/external_protocols_controller_spec.rb +++ b/spec/controllers/external_protocols_controller_spec.rb @@ -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) diff --git a/spec/services/protocol_importers/build_protocol_from_client_service_spec.rb b/spec/services/protocol_importers/build_protocol_from_client_service_spec.rb index 84356e2dd..e1af19c0a 100644 --- a/spec/services/protocol_importers/build_protocol_from_client_service_spec.rb +++ b/spec/services/protocol_importers/build_protocol_from_client_service_spec.rb @@ -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') diff --git a/spec/services/protocol_importers/search_protocols_service_spec.rb b/spec/services/protocol_importers/search_protocols_service_spec.rb index bc9f0e1cf..9e85cd6dc 100644 --- a/spec/services/protocol_importers/search_protocols_service_spec.rb +++ b/spec/services/protocol_importers/search_protocols_service_spec.rb @@ -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')