serpapi 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/serpapi/client.rb +72 -35
- data/lib/serpapi/error.rb +75 -7
- data/lib/serpapi/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 256135fd8adcf51d34456850db13dc6110f8f9886d1c6ee162344b78b3ffacf1
|
|
4
|
+
data.tar.gz: 51b6b6482ac8ac3ca5bd882d8bfe7750a7de0a5b84bf85199ef80b06ae670968
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a42455d35414bd84de2e7e1f8c1185c9da913994b83738e7207d5622070202d839c185007d629e0aa19880aaebf64d146ad86fd038110239722032b695e19dab
|
|
7
|
+
data.tar.gz: fd987c93d188904abface957957483be583f8ae54ec68a2876d99354a8dd704c07df5d7c55f7c51451a128a0c5f3f457f7b5b0a2f0f4e157eb74a30ac38a8342
|
data/lib/serpapi/client.rb
CHANGED
|
@@ -181,6 +181,11 @@ module SerpApi
|
|
|
181
181
|
@socket.close if @socket
|
|
182
182
|
end
|
|
183
183
|
|
|
184
|
+
def inspect
|
|
185
|
+
masked_key = api_key && (api_key.length > 8 ? "#{api_key[..3]}****#{api_key[-4..]}" : '****')
|
|
186
|
+
"#<#{self.class} @engine=#{engine} @timeout=#{timeout} @persistent=#{persistent} api_key=#{masked_key}>"
|
|
187
|
+
end
|
|
188
|
+
|
|
184
189
|
private
|
|
185
190
|
|
|
186
191
|
# @param [Hash] params to merge with default parameters provided to the constructor.
|
|
@@ -210,47 +215,79 @@ module SerpApi
|
|
|
210
215
|
# @param [Hash] params custom search inputs
|
|
211
216
|
# @return [String|Hash] raw HTML or decoded response as JSON / Hash
|
|
212
217
|
def get(endpoint, decoder = :json, params = {})
|
|
213
|
-
|
|
214
|
-
response
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
response = execute_request(endpoint, params)
|
|
219
|
+
handle_response(response, decoder, endpoint, params)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def execute_request(endpoint, params)
|
|
223
|
+
if persistent?
|
|
224
|
+
@socket.get(endpoint, params: query(params))
|
|
225
|
+
else
|
|
226
|
+
url = "https://#{BACKEND}#{endpoint}"
|
|
227
|
+
HTTP.timeout(timeout).get(url, params: query(params))
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def handle_response(response, decoder, endpoint, params)
|
|
221
232
|
case decoder
|
|
222
233
|
when :json
|
|
223
|
-
|
|
224
|
-
begin
|
|
225
|
-
# user can turn on/off JSON keys to symbols
|
|
226
|
-
# this is more memory efficient, but not always needed
|
|
227
|
-
symbolize_names = params.key?(:symbolize_names) ? params[:symbolize_names] : true
|
|
228
|
-
|
|
229
|
-
# parse JSON response with Ruby standard library
|
|
230
|
-
data = JSON.parse(response.body, symbolize_names: symbolize_names)
|
|
231
|
-
if data.instance_of?(Hash) && data.key?(:error)
|
|
232
|
-
raise SerpApiError, "HTTP request failed with error: #{data[:error]} from url: https://#{BACKEND}#{endpoint}, params: #{params}, decoder: #{decoder}, response status: #{response.status} "
|
|
233
|
-
elsif response.status != 200
|
|
234
|
-
raise SerpApiError, "HTTP request failed with response status: #{response.status} reponse: #{data} on get url: https://#{BACKEND}#{endpoint}, params: #{params}, decoder: #{decoder}"
|
|
235
|
-
end
|
|
236
|
-
rescue JSON::ParserError
|
|
237
|
-
raise SerpApiError, "JSON parse error: #{response.body} on get url: https://#{BACKEND}#{endpoint}, params: #{params}, decoder: #{decoder}, response status: #{response.status}"
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
# discard response body
|
|
241
|
-
response.flush if persistent?
|
|
242
|
-
|
|
243
|
-
data
|
|
234
|
+
process_json_response(response, endpoint, params)
|
|
244
235
|
when :html
|
|
245
|
-
|
|
246
|
-
if response.status != 200
|
|
247
|
-
raise SerpApiError, "HTTP request failed with response status: #{response.status} reponse: #{data} on get url: https://#{BACKEND}#{endpoint}, params: #{params}, decoder: #{decoder}"
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
response.body
|
|
236
|
+
process_html_response(response, endpoint, params)
|
|
251
237
|
else
|
|
252
238
|
raise SerpApiError, "not supported decoder: #{decoder}, available: :json, :html"
|
|
253
239
|
end
|
|
254
240
|
end
|
|
241
|
+
|
|
242
|
+
def process_json_response(response, endpoint, params)
|
|
243
|
+
symbolize = params.fetch(:symbolize_names, true)
|
|
244
|
+
|
|
245
|
+
begin
|
|
246
|
+
data = JSON.parse(response.body, symbolize_names: symbolize)
|
|
247
|
+
validate_json_content!(data, response, endpoint, params)
|
|
248
|
+
rescue JSON::ParserError
|
|
249
|
+
raise_parser_error(response, endpoint, params)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
response.flush if persistent?
|
|
253
|
+
data
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def process_html_response(response, endpoint, params)
|
|
257
|
+
raise_http_error(response, nil, endpoint, params, decoder: :html) if response.status != 200
|
|
258
|
+
response.body
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def validate_json_content!(data, response, endpoint, params)
|
|
262
|
+
if data.is_a?(Hash) && data.key?(:error)
|
|
263
|
+
raise_http_error(response, data, endpoint, params, explicit_error: data[:error])
|
|
264
|
+
elsif response.status != 200
|
|
265
|
+
raise_http_error(response, data, endpoint, params)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Centralized error raising to clean up the logic methods
|
|
270
|
+
def raise_http_error(response, data, endpoint, params, explicit_error: nil, decoder: :json)
|
|
271
|
+
msg = "HTTP request failed with status: #{response.status}"
|
|
272
|
+
msg += " error: #{explicit_error}" if explicit_error
|
|
273
|
+
|
|
274
|
+
raise SerpApiError.new(
|
|
275
|
+
"#{msg} from url: https://#{BACKEND}#{endpoint}",
|
|
276
|
+
serpapi_error: explicit_error || (data.is_a?(Hash) ? data[:error] : nil),
|
|
277
|
+
search_params: params,
|
|
278
|
+
response_status: response.status,
|
|
279
|
+
search_id: data.is_a?(Hash) ? data&.dig(:search_metadata, :id) : nil,
|
|
280
|
+
decoder: decoder
|
|
281
|
+
)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def raise_parser_error(response, endpoint, params)
|
|
285
|
+
raise SerpApiError.new(
|
|
286
|
+
"JSON parse error: #{response.body} on get url: https://#{BACKEND}#{endpoint}",
|
|
287
|
+
search_params: params,
|
|
288
|
+
response_status: response.status,
|
|
289
|
+
decoder: :json
|
|
290
|
+
)
|
|
291
|
+
end
|
|
255
292
|
end
|
|
256
293
|
end
|
data/lib/serpapi/error.rb
CHANGED
|
@@ -2,13 +2,81 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
module SerpApi
|
|
5
|
-
#
|
|
5
|
+
# Custom error class for SerpApi-related errors.
|
|
6
|
+
# Inherits from StandardError.
|
|
7
|
+
# Includes optional attributes for detailed error context.
|
|
8
|
+
# Attributes:
|
|
9
|
+
# - serpapi_error: String error message from SerpApi (optional)
|
|
10
|
+
# - search_params: Hash of search parameters used (optional)
|
|
11
|
+
# - response_status: Integer HTTP or response status code (optional)
|
|
12
|
+
# - search_id: String id returned by the service for the search (optional)
|
|
13
|
+
# - decoder: Symbol representing the decoder/format used (optional) (e.g. :json)
|
|
6
14
|
class SerpApiError < StandardError
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
15
|
+
attr_reader :serpapi_error, :search_params, :response_status, :search_id, :decoder
|
|
16
|
+
|
|
17
|
+
# All attributes are optional keyword arguments.
|
|
18
|
+
#
|
|
19
|
+
# @param message [String, nil] an optional human message passed to StandardError
|
|
20
|
+
# @param serpapi_error [String, nil] optional error string coming from SerpAPI
|
|
21
|
+
# @param search_params [Hash, nil] optional hash of the search parameters used
|
|
22
|
+
# @param response_status [Integer, nil] optional HTTP or response status code
|
|
23
|
+
# @param search_id [String, nil] optional id returned by the service for the search
|
|
24
|
+
# @param decoder [Symbol, nil] optional decoder/format used (e.g. :json)
|
|
25
|
+
def initialize(message = nil,
|
|
26
|
+
serpapi_error: nil,
|
|
27
|
+
search_params: nil,
|
|
28
|
+
response_status: nil,
|
|
29
|
+
search_id: nil,
|
|
30
|
+
decoder: nil)
|
|
31
|
+
super(message)
|
|
32
|
+
|
|
33
|
+
@serpapi_error = validate_optional_string(serpapi_error, :serpapi_error)
|
|
34
|
+
@search_params = search_params.dup
|
|
35
|
+
@response_status = validate_optional_integer(response_status, :response_status)
|
|
36
|
+
@search_id = validate_optional_string(search_id, :search_id)
|
|
37
|
+
@decoder = validate_optional_symbol(decoder, :decoder)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Return a compact hash representation (omits nil values).
|
|
41
|
+
#
|
|
42
|
+
# @return [Hash]
|
|
43
|
+
def to_h
|
|
44
|
+
{
|
|
45
|
+
message: message,
|
|
46
|
+
serpapi_error: serpapi_error,
|
|
47
|
+
search_params: search_params.dup,
|
|
48
|
+
response_status: response_status,
|
|
49
|
+
search_id: search_id,
|
|
50
|
+
decoder: decoder
|
|
51
|
+
}.compact
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def validate_optional_string(value, name = nil)
|
|
57
|
+
return nil if value.nil?
|
|
58
|
+
raise TypeError, "expected #{name || 'value'} to be a String, got #{value.class}" unless value.is_a?(String)
|
|
59
|
+
|
|
60
|
+
value.freeze
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def validate_optional_integer(value, name = nil)
|
|
64
|
+
return nil if value.nil?
|
|
65
|
+
return value if value.is_a?(Integer)
|
|
66
|
+
|
|
67
|
+
# Accept numeric-like strings (e.g. "200") by converting; fail otherwise.
|
|
68
|
+
begin
|
|
69
|
+
Integer(value)
|
|
70
|
+
rescue ArgumentError, TypeError
|
|
71
|
+
raise TypeError, "expected #{name || 'value'} to be an Integer (or integer-like), got #{value.inspect}"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def validate_optional_symbol(value, name = nil)
|
|
76
|
+
return nil if value.nil?
|
|
77
|
+
raise TypeError, "expected #{name || 'value'} to be a Symbol, got #{value.class}" unless value.is_a?(Symbol)
|
|
78
|
+
|
|
79
|
+
value
|
|
80
|
+
end
|
|
13
81
|
end
|
|
14
82
|
end
|
data/lib/serpapi/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: serpapi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- victor benarbia
|
|
8
8
|
- Julien Khaleghy
|
|
9
|
+
autorequire:
|
|
9
10
|
bindir: bin
|
|
10
11
|
cert_chain: []
|
|
11
|
-
date:
|
|
12
|
+
date: 2026-02-23 00:00:00.000000000 Z
|
|
12
13
|
dependencies:
|
|
13
14
|
- !ruby/object:Gem::Dependency
|
|
14
15
|
name: http
|
|
@@ -111,6 +112,7 @@ homepage: https://github.com/serpapi/serpapi-ruby
|
|
|
111
112
|
licenses:
|
|
112
113
|
- MIT
|
|
113
114
|
metadata: {}
|
|
115
|
+
post_install_message:
|
|
114
116
|
rdoc_options: []
|
|
115
117
|
require_paths:
|
|
116
118
|
- lib
|
|
@@ -125,7 +127,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
125
127
|
- !ruby/object:Gem::Version
|
|
126
128
|
version: '0'
|
|
127
129
|
requirements: []
|
|
128
|
-
rubygems_version: 3.6
|
|
130
|
+
rubygems_version: 3.1.6
|
|
131
|
+
signing_key:
|
|
129
132
|
specification_version: 4
|
|
130
133
|
summary: Official Ruby library for SerpApi.com
|
|
131
134
|
test_files: []
|