Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions lib/serpapi/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -228,23 +228,23 @@ 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
raise SerpApiError, "not supported decoder: #{decoder}, available: :json, :html"
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
Expand All @@ -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
Expand Down
87 changes: 87 additions & 0 deletions spec/serpapi/client/search_archive_error_handling_spec.rb
Original file line number Diff line number Diff line change
@@ -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