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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf26fc67e0314db3dcdc7505883936a534faa9ca10021ee5db722cd40ffc1fd8
4
- data.tar.gz: 047216ed0cceeb4bc18edc82a104c961b527076527c68e4f10eb5f576dec48fd
3
+ metadata.gz: 256135fd8adcf51d34456850db13dc6110f8f9886d1c6ee162344b78b3ffacf1
4
+ data.tar.gz: 51b6b6482ac8ac3ca5bd882d8bfe7750a7de0a5b84bf85199ef80b06ae670968
5
5
  SHA512:
6
- metadata.gz: d1413d68e289bc72ae2de9a264c9fa6e4a757fa2993d3cff8a2a8d9cc76e8a0441dc92e6164a93ab7fb972d60e63fe3ea716c09db26fa3fb187aeacdce7f4c67
7
- data.tar.gz: 546ae88d007ba76a7ae9633c9b3780f7ff255892e2d9951971d2b32ef4a1cca5148de8857f2ba1de7113d19221b099d4bfd75e8e71fc6437be7548b09235d01e
6
+ metadata.gz: a42455d35414bd84de2e7e1f8c1185c9da913994b83738e7207d5622070202d839c185007d629e0aa19880aaebf64d146ad86fd038110239722032b695e19dab
7
+ data.tar.gz: fd987c93d188904abface957957483be583f8ae54ec68a2876d99354a8dd704c07df5d7c55f7c51451a128a0c5f3f457f7b5b0a2f0f4e157eb74a30ac38a8342
@@ -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
- # execute get via open socket
214
- response = if persistent?
215
- @socket.get(endpoint, params: query(params))
216
- else
217
- HTTP.timeout(timeout).get("https://#{BACKEND}#{endpoint}", params: query(params))
218
- end
219
-
220
- # decode response using JSON native parser
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
- # read http response
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
- # html decoder
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
- # SerpApiError wraps any errors related to the SerpApi client.
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
- # List the specific types of errors handled by the Error class.
8
- # - HTTP response errors from SerpApi.com
9
- # - Missing API key
10
- # - Credit limit
11
- # - Incorrect query
12
- # - more ...
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
@@ -1,4 +1,4 @@
1
1
  module SerpApi
2
2
  # Current version of the gem
3
- VERSION = '1.0.1'.freeze
3
+ VERSION = '1.0.3'.freeze
4
4
  end
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.1
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: 1980-01-02 00:00:00.000000000 Z
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.7
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: []