-
Notifications
You must be signed in to change notification settings - Fork 19
Add initial Markdown support #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
379f946
Add initial Markdown support
strzibny 3fb5120
Rename markdown method to md
strzibny b063cf8
Update archive to :md
strzibny 21b5347
Remove double and keep only live tests
strzibny d006bcb
Pass .md directly in search_archive
strzibny 086f6f8
Choose the right decoder based on output parameter
strzibny 3d41fe7
Include json explicitely
strzibny fb033a1
Join response handling into process_text_response
strzibny 385cab6
Introduce response type decoders to always return expected type
strzibny 39b7e3b
Keep .md and .html methods as pure shortcuts
strzibny ce01fc3
Do not change query signature
strzibny c6b1075
Do not change query signature
strzibny 1c351bc
Update spec
strzibny ea4be3b
Address review recommendations
strzibny 10a324d
Increase Metrics/ClassLength
strzibny File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,14 +7,17 @@ module SerpApi | |
| # features: | ||
| # * async non-block search | ||
| # * persistent HTTP connection | ||
| # * search API | ||
| # * search API with JSON, HTML, and Markdown output | ||
| # * location API | ||
| # * account API | ||
| # * search archive API | ||
| # | ||
| class Client | ||
| # Backend service URL | ||
| BACKEND = 'serpapi.com'.freeze | ||
| # SerpApi errors are JSON even when HTML or Markdown output was requested. | ||
| # Decode the actual Content-Type so structured errors and successful JSON responses become Hashes. | ||
| CONTENT_TYPE_DECODERS = { 'application/json' => :json, 'text/html' => :html, 'text/markdown' => :md }.freeze | ||
|
|
||
| # HTTP timeout requests | ||
| attr_reader :timeout, | ||
|
|
@@ -108,7 +111,7 @@ def initialize(params = {}) | |
| # thus, most of the compute power is on the backsdend and not on the client side. | ||
| # @param [Hash] params includes engine, api_key, search fields and more.. | ||
| # this override the default params provided to the constructor. | ||
| # @return [Hash] search results formatted as a Hash. | ||
| # @return [Hash|String] search results formatted as a Hash or raw text. | ||
| def search(params = {}) | ||
| get('/search', :json, params) | ||
| end | ||
|
|
@@ -118,9 +121,18 @@ def search(params = {}) | |
| # it is useful for training AI models, RAG, debugging | ||
| # or when you need to parse the HTML yourself. | ||
| # | ||
| # @return [String] raw html search results directly from the search engine. | ||
| # @return [String] raw HTML search results. | ||
| def html(params = {}) | ||
| get('/search', :html, params) | ||
| get('/search', :html, force_output(params, 'html')) | ||
| end | ||
|
|
||
| # Perform a search using SerpApi.com and return results optimized for LLMs and AI agents. | ||
| # The output contains Markdown tables, links, and YAML frontmatter. | ||
| # | ||
| # @param [Hash] params includes engine, api_key, search fields and more. | ||
| # @return [String] search results formatted as Markdown. | ||
| def md(params = {}) | ||
| get('/search', :md, force_output(params, 'md')) | ||
| end | ||
|
|
||
| # Get location using Location API | ||
|
|
@@ -146,10 +158,10 @@ def location(params = {}) | |
| # doc: https://serpapi.com/search-archive-api | ||
| # | ||
| # @param [String|Integer] search_id from original search `results[:search_metadata][:id]` | ||
| # @param [Symbol] format :json or :html [default: json, optional] | ||
| # @return [String|Hash] raw html or JSON / Hash | ||
| # @param [Symbol] format :json, :html, or :md [default: json, optional] | ||
| # @return [String|Hash] raw HTML, Markdown, or JSON / Hash | ||
| def search_archive(search_id, format = :json) | ||
| raise SerpApiError, 'format must be json or html' unless [:json, :html].include?(format) | ||
| raise SerpApiError, 'format must be json, html, or md' unless [:json, :html, :md].include?(format) | ||
|
|
||
| get("/searches/#{search_id}.#{format}", format) | ||
| end | ||
|
|
@@ -188,13 +200,21 @@ def inspect | |
|
|
||
| private | ||
|
|
||
| def force_output(params, format) | ||
| return params unless params.is_a?(Hash) | ||
|
|
||
| params.reject { |key, _| key.to_s == 'output' }.merge(output: format) | ||
| end | ||
|
|
||
| # @param [Hash] params to merge with default parameters provided to the constructor. | ||
| # @return [Hash] merged query parameters after cleanup | ||
| def query(params) | ||
| raise SerpApiError, "params must be hash, not: #{params.class}" unless params.instance_of?(Hash) | ||
|
|
||
| # merge default params with custom params | ||
| q = @params.clone.merge(params) | ||
| q.delete('output') if params.key?(:output) | ||
| q.delete(:output) if params.key?('output') && !params.key?(:output) | ||
|
|
||
| # do not pollute default params with custom params | ||
| q.delete(:symbolize_names) if q.key?(:symbolize_names) | ||
|
|
@@ -211,12 +231,14 @@ def persistent? | |
| # Perform HTTP GET request to the SerpApi.com backend endpoint. | ||
| # | ||
| # @param [String] endpoint HTTP service URI | ||
| # @param [Symbol] decoder type :json or :html | ||
| # @param [Symbol] decoder type :json, :html, or :md | ||
| # @param [Hash] params custom search inputs | ||
| # @return [String|Hash] raw HTML or decoded response as JSON / Hash | ||
| # @return [String|Hash] raw text or decoded response as JSON / Hash | ||
| def get(endpoint, decoder = :json, params = {}) | ||
| response = execute_request(endpoint, params) | ||
| handle_response(response, decoder, endpoint, params) | ||
| handle_response(response, response_decoder(response, decoder), endpoint, params) | ||
| ensure | ||
| response&.flush if persistent? | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
| end | ||
|
|
||
| def execute_request(endpoint, params) | ||
|
|
@@ -228,14 +250,19 @@ def execute_request(endpoint, params) | |
| end | ||
| end | ||
|
|
||
| def response_decoder(response, default) | ||
| content_type = response.headers['Content-Type'].to_s.split(';').first | ||
| CONTENT_TYPE_DECODERS.fetch(content_type, default) | ||
| end | ||
|
|
||
| def handle_response(response, decoder, endpoint, params) | ||
| case decoder | ||
| when :json | ||
| process_json_response(response, endpoint, params) | ||
| when :html | ||
| process_html_response(response, endpoint, params) | ||
| when :html, :md | ||
| process_text_response(response, endpoint, params, decoder) | ||
| else | ||
| raise SerpApiError, "not supported decoder: #{decoder}, available: :json, :html" | ||
| raise SerpApiError, "not supported decoder: #{decoder}, available: :json, :html, :md" | ||
| end | ||
| end | ||
|
|
||
|
|
@@ -249,13 +276,13 @@ def process_json_response(response, endpoint, params) | |
| raise_parser_error(response, endpoint, params) | ||
| end | ||
|
|
||
| response.flush if persistent? | ||
| data | ||
| end | ||
|
|
||
| def process_html_response(response, endpoint, params) | ||
| raise_http_error(response, nil, endpoint, params, decoder: :html) if response.status != 200 | ||
| response.body | ||
| def process_text_response(response, endpoint, params, decoder) | ||
| raise_http_error(response, nil, endpoint, params, decoder: decoder) if response.status != 200 | ||
|
|
||
| response.body.to_s | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
| end | ||
|
|
||
| def validate_json_content!(data, response, endpoint, params) | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be worth calling out the behaviour change on
get('/invalid'), which now raises a 404