Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
# Build
build

# Generated docs
docs

# Editor
.vscode
.cache
43 changes: 43 additions & 0 deletions Doxyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Doxyfile for serpapi-cpp
#
# Generate documentation with:
# doxygen Doxyfile
# Output is written to docs/html (open docs/html/index.html).
#
# This file only lists settings that differ from Doxygen's defaults;
# any option not listed here uses the standard Doxygen default value.

#---------------------------------------------------------------------------
# Project
#---------------------------------------------------------------------------
PROJECT_NAME = "SerpApi C++"
PROJECT_BRIEF = "Official C++ client library for SerpApi"
PROJECT_NUMBER = 0.5.0
OUTPUT_DIRECTORY = docs

#---------------------------------------------------------------------------
# Build
#---------------------------------------------------------------------------
EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
EXTRACT_STATIC = YES
RECURSIVE = YES

#---------------------------------------------------------------------------
# Input
#---------------------------------------------------------------------------
INPUT = src README.md
FILE_PATTERNS = *.cpp *.hpp
USE_MDFILE_AS_MAINPAGE = README.md

#---------------------------------------------------------------------------
# Output formats
#---------------------------------------------------------------------------
GENERATE_HTML = YES
GENERATE_LATEX = NO

#---------------------------------------------------------------------------
# Preprocessing / diagrams
#---------------------------------------------------------------------------
HAVE_DOT = NO
CLASS_DIAGRAMS = YES
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ sudo apt update && sudo apt install -y rapidjson-dev
Download and extract the latest release:

```bash
curl -sL https://github.com/serpapi/serpapi-cpp/archive/refs/tags/v0.4.1.tar.gz | tar xz
cd serpapi-0.4.1
curl -sL https://github.com/serpapi/serpapi-cpp/archive/refs/tags/v0.5.0.tar.gz | tar xz
cd serpapi-0.5.0
meson setup build
meson compile -C build
sudo meson install -C build
Expand Down Expand Up @@ -118,6 +118,9 @@ rapidjson::Document results = client.search(params);

// raw search engine html as a String
std::string raw_html = client.html(params);

// raw search engine results as Markdown String
std::string raw_markdown = client.markdown(params);
```

[Google search documentation](https://serpapi.com/search-api).
Expand Down Expand Up @@ -206,6 +209,7 @@ C++ versions validated by Github Actions:
- C++20

## Change logs
* [2026-08-16] 0.5.0 Add markdown() support via the output=md format.
* [2026-06-26] 0.4.1 Exclude test dependencies from release build.
* [2026-06-08] 0.4.0 Add unit tests and improve error handling.
* [2026-02-12] 0.3.0 Refactor and cleanup.
Expand All @@ -226,6 +230,21 @@ C++ versions validated by Github Actions:
- 100% tests passing: `rake test`
- 100% code coverage: `rake coverage`

### Latest coverage report

Generated with `rake coverage` (requires `SERPAPI_KEY` set to exercise the live API tests):

| File | Lines | Exec | Cover |
|-------------------|------:|-----:|------:|
| src/callback.cpp | 11 | 8 | 72% |
| src/serpapi.cpp | 97 | 76 | 78% |
| src/serpapi.hpp | 1 | 1 | 100% |
| **TOTAL** | **109** | **85** | **78%** |

- lines: 78.0% (85/109)
- functions: 100.0% (15/15)
- branches: 45.9% (79/172)

## Design : UML diagram
### Class diagram
```mermaid
Expand All @@ -238,6 +257,7 @@ classDiagram
parameter map
search() Document
html() String
markdown() String
location() Document
search_archive() Document
account() Document
Expand Down
2 changes: 1 addition & 1 deletion meson.build
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
project('serpapi', 'cpp',
version : '0.4.1',
version : '0.5.0',
default_options : ['warning_level=3', 'cpp_std=c++17'])

compiler = meson.get_compiler('cpp')
Expand Down
74 changes: 73 additions & 1 deletion src/serpapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,83 @@ namespace serpapi {

const static std::string HOST = "https://serpapi.com";
const static std::string NAME = "serpapi-cpp";
const static std::string VERSION = "0.4.1";
const static std::string VERSION = "0.5.0";

static std::once_flag curl_init_flag;

/**
* @brief Constructs a client with default parameters merged into every request.
* @param parameter Default parameters (e.g. api_key, engine) applied to all calls.
*/
Client::Client(const std::map<std::string, std::string> &parameter) {
this->parameter = parameter;
}

Client::~Client() {}

/**
* @brief Runs a search and returns the raw search engine result page as HTML.
* @param parameter Search parameters (e.g. q, location) merged with the client defaults.
* @return Raw HTML response body.
*/
std::string Client::html(const std::map<std::string, std::string> &parameter) {
GetResponse gr = Client::get("/search", "html", parameter);
return gr.payload;
}

/**
* @brief Runs a search and returns the raw search engine result page as Markdown.
* @param parameter Search parameters (e.g. q, location) merged with the client defaults.
* @return Raw Markdown response body.
*/
std::string Client::markdown(const std::map<std::string, std::string> &parameter) {
GetResponse gr = Client::get("/search", "md", parameter);
return gr.payload;
}

/**
* @brief Runs a search and returns the parsed/structured results as JSON.
* @param parameter Search parameters (e.g. q, location) merged with the client defaults.
* @return Parsed JSON response as a rapidjson::Document.
*/
rapidjson::Document Client::search(const std::map<std::string, std::string> &parameter) {
return Client::json("/search", parameter);
}

/**
* @brief Retrieves a previously run search from the search archive.
* @param id Search id, as returned in search_metadata.id from a prior search().
* @return Parsed JSON response as a rapidjson::Document.
*/
rapidjson::Document Client::search_archive(const std::string &id) {
return Client::json("/searches/" + id + ".json", std::map<std::string, std::string>());
}

/**
* @brief Retrieves account information (e.g. plan, usage) for the given api_key.
* @param parameter Request parameters (e.g. api_key) merged with the client defaults.
* @return Parsed JSON response as a rapidjson::Document.
*/
rapidjson::Document Client::account(const std::map<std::string, std::string> &parameter) {
return Client::json("/account.json", parameter);
}

/**
* @brief Looks up supported locations matching a query.
* @param parameter Request parameters (e.g. q, limit) merged with the client defaults.
* @return Parsed JSON response as a rapidjson::Document.
*/
rapidjson::Document Client::location(const std::map<std::string, std::string> &parameter) {
return Client::json("/locations.json", parameter);
}

/**
* @brief Fetches a JSON endpoint and parses it into a rapidjson::Document.
* @param uri Endpoint path, relative to HOST, to request with output=json.
* @param parameter Request parameters merged with the client defaults.
* @return Parsed JSON response, or a Document with an "error" member if the
* payload could not be parsed.
*/
rapidjson::Document Client::json(const std::string &uri, const std::map<std::string, std::string> &parameter) {
GetResponse gr = get(uri, "json", parameter);
rapidjson::Document d;
Expand All @@ -53,6 +99,12 @@ rapidjson::Document Client::json(const std::string &uri, const std::map<std::str
return d;
}

/**
* @brief URL-encodes and joins a parameter map into a query string.
* @param curl Initialized CURL handle used to escape keys and values.
* @param parameter Parameters to encode.
* @return "key=value&key=value" encoded string, or "" if escaping any entry failed.
*/
std::string encodeUrl(CURL *curl, const std::map<std::string, std::string> &parameter) {
std::ostringstream oss;
bool first = true;
Expand All @@ -77,6 +129,17 @@ std::string encodeUrl(CURL *curl, const std::map<std::string, std::string> &para
return oss.str();
}

/**
* @brief Builds the full query string for a request.
*
* Concatenates call-specific parameters, then the client's default
* parameters, then the output format and source tag.
*
* @param curl Initialized CURL handle used to escape parameters.
* @param output Desired response format (e.g. "json", "html", "md").
* @param parameter Call-specific parameters, merged with the client defaults.
* @return Fully encoded query string, without the leading '?'.
*/
std::string Client::url(CURL *curl, const std::string &output,
const std::map<std::string, std::string> &parameter) {
std::string url_str = encodeUrl(curl, parameter);
Expand All @@ -89,6 +152,15 @@ std::string Client::url(CURL *curl, const std::string &output,
return url_str;
}

/**
* @brief Performs the HTTP GET request against SerpApi.
* @param uri Endpoint path, relative to HOST (e.g. "/search").
* @param output Desired response format (e.g. "json", "html", "md").
* @param parameter Call-specific parameters, merged with the client defaults.
* @return HTTP status code and raw response body. httpCode is 0 on a
* client-side failure (CURL init or transport error), with the
* error message in payload.
*/
GetResponse Client::get(const std::string &uri, const std::string &output,
const std::map<std::string, std::string> &parameter) {
std::call_once(curl_init_flag, []() { curl_global_init(CURL_GLOBAL_DEFAULT); });
Expand Down
2 changes: 2 additions & 0 deletions src/serpapi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class Client {

std::string html(const std::map<std::string, std::string> &parameter = {});

std::string markdown(const std::map<std::string, std::string> &parameter = {});

rapidjson::Document search_archive(const std::string &search_id);

rapidjson::Document
Expand Down
20 changes: 20 additions & 0 deletions test/test_serpapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,23 @@ TEST(client, html) {
ASSERT_FALSE(html.empty());
ASSERT_TRUE(html.find("coffee") != std::string::npos);
}

TEST(client, markdown) {
const char *env_p = std::getenv("SERPAPI_KEY");
if (env_p == nullptr) {
GTEST_SKIP() << "SERPAPI_KEY not set";
}
std::string apiKey(env_p);
std::map<string, string> default_parameter;
default_parameter["api_key"] = apiKey;
default_parameter["engine"] = "google";

serpapi::Client client(default_parameter);

map<string, string> parameter;
parameter["q"] = "coffee";

std::string markdown = client.markdown(parameter);
ASSERT_FALSE(markdown.empty());
ASSERT_TRUE(markdown.find("coffee") != std::string::npos);
}
Loading