diff --git a/lib/serpapi/client.rb b/lib/serpapi/client.rb index 0a8f081..120284c 100644 --- a/lib/serpapi/client.rb +++ b/lib/serpapi/client.rb @@ -151,7 +151,7 @@ def location(params = {}) def search_archive(search_id, format = :json) raise SerpApiError, 'format must be json or html' unless [:json, :html].include?(format) - get("/searches/#{search_id}.#{format}", format) + get("/searches/#{search_id}.#{format}", format, {}, allow_error_field: true) end # Get account information using Account API @@ -214,9 +214,9 @@ def persistent? # @param [Symbol] decoder type :json or :html # @param [Hash] params custom search inputs # @return [String|Hash] raw HTML or decoded response as JSON / Hash - def get(endpoint, decoder = :json, params = {}) + def get(endpoint, decoder = :json, params = {}, allow_error_field: false) response = execute_request(endpoint, params) - handle_response(response, decoder, endpoint, params) + handle_response(response, decoder, endpoint, params, allow_error_field: allow_error_field) end def execute_request(endpoint, params) @@ -228,10 +228,10 @@ def execute_request(endpoint, params) end end - def handle_response(response, decoder, endpoint, params) + def handle_response(response, decoder, endpoint, params, allow_error_field: false) case decoder when :json - process_json_response(response, endpoint, params) + process_json_response(response, endpoint, params, allow_error_field: allow_error_field) when :html process_html_response(response, endpoint, params) else @@ -239,12 +239,12 @@ def handle_response(response, decoder, endpoint, params) end end - def process_json_response(response, endpoint, params) + def process_json_response(response, endpoint, params, allow_error_field: false) symbolize = params.fetch(:symbolize_names, true) begin data = JSON.parse(response.body, symbolize_names: symbolize) - validate_json_content!(data, response, endpoint, params) + validate_json_content!(data, response, endpoint, params, allow_error_field: allow_error_field) rescue JSON::ParserError raise_parser_error(response, endpoint, params) end @@ -258,7 +258,10 @@ def process_html_response(response, endpoint, params) response.body end - def validate_json_content!(data, response, endpoint, params) + def validate_json_content!(data, response, endpoint, params, allow_error_field: false) + # Successful archive responses can contain an error field for the archived search itself. + return if allow_error_field && response.status == 200 + if data.is_a?(Hash) && data.key?(:error) raise_http_error(response, data, endpoint, params, explicit_error: data[:error]) elsif response.status != 200 diff --git a/spec/serpapi/client/search_archive_error_handling_spec.rb b/spec/serpapi/client/search_archive_error_handling_spec.rb new file mode 100644 index 0000000..34f20d4 --- /dev/null +++ b/spec/serpapi/client/search_archive_error_handling_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe 'SerpApi Search Archive API error handling' do + def client_with_response(body, status) + response = double('response', body: body, status: status) + client = SerpApi::Client.new(api_key: '', engine: 'google', persistent: false) + + allow(client).to receive(:execute_request).and_return(response) + client + end + + def client_with_json(payload, status) + client_with_response(JSON.dump(payload), status) + end + + it 'returns archived search data when a successful response contains an error field' do + payload = { + error: "Google hasn't returned any results for this query.", + search_metadata: { + id: '6935c0b9a0cb1015d74ef919' + } + } + client = client_with_json(payload, 200) + + expect(client.search_archive(payload[:search_metadata][:id])).to eq(payload) + end + + it 'keeps raising for regular search responses with an error field' do + payload = { + error: 'Missing query `q` parameter.', + search_metadata: { + id: 'regular-search-error' + } + } + client = client_with_json(payload, 200) + + expect { + client.search(q: 'Coffee') + }.to raise_error(SerpApi::SerpApiError) { |error| + expect(error.response_status).to eq(200) + expect(error.serpapi_error).to eq(payload[:error]) + } + end + + it 'raises for unsuccessful archive responses with an error field' do + payload = { + error: 'Search not found.', + search_metadata: { + id: 'missing-archive-search' + } + } + client = client_with_json(payload, 404) + + expect { + client.search_archive(payload[:search_metadata][:id]) + }.to raise_error(SerpApi::SerpApiError) { |error| + expect(error.response_status).to eq(404) + expect(error.serpapi_error).to eq(payload[:error]) + expect(error.search_id).to eq(payload[:search_metadata][:id]) + } + end + + it 'raises for unsuccessful archive responses without an error field' do + payload = { + search_metadata: { + id: 'server-error-archive-search' + } + } + client = client_with_json(payload, 500) + + expect { + client.search_archive(payload[:search_metadata][:id]) + }.to raise_error(SerpApi::SerpApiError) { |error| + expect(error.response_status).to eq(500) + expect(error.serpapi_error).to be_nil + expect(error.search_id).to eq(payload[:search_metadata][:id]) + } + end + + it 'still raises parser errors for malformed archive JSON responses' do + client = client_with_response('{"error":', 200) + + expect { + client.search_archive('malformed-json') + }.to raise_error(SerpApi::SerpApiError, /JSON parse error/) + end +end