HTTP REST Client with Retry Logic (cpp, written by Codex)
envgap__codex__cpp-t1-27
Written by a coding agent; not on GitHubWritten 2026-03-03
01 / FAILURE SIGNATURE
As the study recorded it
cpr tries to build curl from source - requires system OpenSSL
Not a benchmark task.
- In a clean container the reported failure did not reproduce, or the known fix did not make the project run.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
CMakeLists.txt- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/cpp-t1 #27 · read the task the agent was given
Codex wrote this cpp project from the task below. It does not run on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: HTTP REST Client with Retry Logic Write a program that sends HTTP requests to REST APIs with configurable retry logic, response validation, request chaining, and comprehensive logging of request/response cycles. FUNCTIONAL REQUIREMENTS: - Accept a URL as a command-line argument with --method flag (GET, POST, PUT, PATCH, DELETE; default: GET) - Support sending request body via --data flag (JSON string) or --file flag (read body from a file) - Support custom headers via --header flag (repeatable, e.g., --header "Content-Type: application/json" --header "Authorization: Bearer token") - Implement configurable retry logic: --retries flag (default 3), --backoff flag for backoff strategy (linear, exponential; default: exponential), --retry-delay flag for initial delay in seconds (default 1) - Retry only on configurable HTTP status codes via --retry-on flag (default: 429, 500, 502, 503, 504) - Support request timeout via --timeout flag (default 30 seconds) - Support request chaining from a JSON recipe file via --chain flag: execute a sequence of requests where later requests can reference values from earlier responses using JSONPath expressions - Log complete request/response cycles: method, URL, headers, body, status code, response headers, response body, timing, and retry attempts - Support response validation via --expect-status flag (e.g., --expect-status 200) and --expect-json flag (JSONPath expression that must exist in the response) - Print formatted response to console: status code, headers, and pretty-printed body (auto-detect JSON and format it) - Save the full request/response log as JSON with --output flag (default: request_log.json) - If no URL is given, demonstrate the client by sending requests to a public test API (httpbin.org or similar), showing GET, POST with JSON body, retry on simulated failure, and a simple request chain - Handle errors: connection refused, DNS failure, SSL errors, malformed JSON responses, and redirect loops Create a complete C++ project for a clean Ubuntu 22.04 machine with only G++ 12+ and CMake 3.22+ installed. Include: - Source code - CMakeLists.txt with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
CMakeLists.txt
cmake_minimum_required(VERSION 3.22) project(http_rest_client_with_retry LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) include(FetchContent) FetchContent_Declare( nlohmann_json GIT_REPOSITORY https://github.com/nlohmann/json.git GIT_TAG v3.11.3 ) FetchContent_Declare( cpr GIT_REPOSITORY https://github.com/libcpr/cpr.git GIT_TAG 1.11.2 ) set(CPR_USE_SYSTEM_CURL OFF CACHE BOOL "" FORCE) set(CPR_ENABLE_CURL_HTTP_ONLY OFF CACHE BOOL "" FORCE) set(CPR_BUILD_TESTS OFF CACHE BOOL "" FORCE) FetchContent_MakeAvailable(nlohmann_json cpr) add_executable(http_rest_client src/main.cpp) target_link_libraries(http_rest_client PRIVATE cpr::cpr nlohmann_json::nlohmann_json)
README.md
# HTTP REST Client with Retry Logic (C++)
Command-line REST client with retry/backoff, response validation, chain execution, and full request/response logging.
## Requirements
- Ubuntu 22.04
- G++ 12+
- CMake 3.22+
- OpenSSL and build toolchain (`build-essential`, `pkg-config`)
## Dependencies (Pinned)
- `cpr` `1.11.2` (HTTP client)
- `nlohmann/json` `v3.11.3` (JSON parsing and report serialization)
- All dependencies are pinned by Git tags in `CMakeLists.txt`
## Build
```bash
cmake -S . -B build
cmake --build build -j
```
## Run
Single request:
```bash
./build/http_rest_client https://httpbin.org/get --method GET --expect-status 200 --expect-json $.url --output request_log.json
```
POST with JSON body:
```bash
./build/http_rest_client https://httpbin.org/post --method POST --data '{"hello":"world"}' --header "Content-Type: application/json"
```
Chain mode:
```bash
./build/http_rest_client --chain chain.json --output request_log.json
```
Demo mode (no URL):
```bash
./build/http_rest_client
```
## Chain File Format
`chain.json` may be either:
- an array of request objects, or
- an object with a `requests` array
Template references in strings are supported:
- `{{0:$.json.token}}` means: take JSONPath `$.json.token` from response body of step `0`
Example:
```json
{
"requests": [
{
"method": "GET",
"url": "https://httpbin.org/get",
"expect_status": 200
},
{
"method": "POST",
"url": "https://httpbin.org/post",
"headers": {"Content-Type": "application/json"},
"data": "{\"from\":\"{{0:$.url}}\"}",
"expect_status": 200,
"expect_json": "$.json.from"
}
]
}
```
## Supported Flags
- URL positional argument (omit for demo mode)
- `--method`
- `--data`
- `--file`
- `--header` (repeatable)
- `--retries`
- `--backoff` (`linear` / `exponential`)
- `--retry-delay`
- `--retry-on` (CSV list)
- `--timeout`
- `--chain`
- `--expect-status`
- `--expect-json`
- `--output`
## Output
- Console: status code, headers, and pretty-printed body
- JSON log: full request/response cycle per attempt, timing, retries, and final responses (default `request_log.json`)
src/main.cpp
#include <chrono>
#include <cctype>
#include <cmath>
#include <fstream>
#include <iostream>
#include <optional>
#include <regex>
#include <set>
#include <sstream>
#include <stdexcept>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>
#include <cpr/cpr.h>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
struct Args {
std::unordered_map<std::string, std::string> options;
std::vector<std::string> headers;
std::vector<std::string> positional;
};
struct RequestSpec {
std::string method = "GET";
std::string url;
std::unordered_map<std::string, std::string> headers;
std::string body;
int retries = 3;
std::string backoff = "exponential";
double retryDelaySec = 1.0;
std::set<int> retryOn {429, 500, 502, 503, 504};
int timeoutSec = 30;
std::optional<int> expectStatus;
std::optional<std::string> expectJsonPath;
};
struct ResponseSnapshot {
long statusCode = 0;
std::unordered_map<std::string, std::string> headers;
std::string body;
std::string error;
bool ok = false;
};
struct StepResult {
ResponseSnapshot response;
std::optional<json> parsedJson;
};
static std::string readFileText(const std::string& path) {
std::ifstream in(path, std::ios::binary);
if (!in) {
throw std::runtime_error("Unable to read file: " + path);
}
std::ostringstream buffer;
buffer << in.rdbuf();
return buffer.str();
}
static std::string nowIsoUtc() {
using clock = std::chrono::system_clock;
auto now = clock::now();
std::time_t t = clock::to_time_t(now);
std::tm tm {};
#if defined(_WIN32)
gmtime_s(&tm, &t);
#else
gmtime_r(&t, &tm);
#endif
char out[32];
std::strftime(out, sizeof(out), "%Y-%m-%dT%H:%M:%SZ", &tm);
return std::string(out);
}
static std::string trim(const std::string& in) {
size_t left = 0;
while (left < in.size() && std::isspace(static_cast<unsigned char>(in[left]))) {
++left;
}
size_t right = in.size();
while (right > left && std::isspace(static_cast<unsigned char>(in[right - 1]))) {
--right;
}
return in.substr(left, right - left);
}
static std::string toUpper(std::string s) {
for (char& ch : s) {
ch = static_cast<char>(std::toupper(static_cast<unsigned char>(ch)));
}
return s;
}
static std::unordered_map<std::string, std::string> parseHeaderPairs(const std::vector<std::string>& values) {
std::unordered_map<std::string, std::string> out;
for (const std::string& entry : values) {
const size_t pos = entry.find(':');
if (pos == std::string::npos) {
throw std::runtime_error("Invalid --header value (expected Key: Value): " + entry);
}
const std::string key = trim(entry.substr(0, pos));
const std::string value = trim(entry.substr(pos + 1));
if (key.empty()) {
throw std::runtime_error("Header key cannot be empty.");
}
out[key] = value;
}
return out;
}
static std::set<int> parseRetryOn(const std::string& csv) {
std::set<int> out;
std::stringstream ss(csv);
std::string token;
while (std::getline(ss, token, ',')) {
token = trim(token);
if (token.empty()) continue;
out.insert(std::stoi(token));
}
if (out.empty()) {
throw std::runtime_error("--retry-on must include at least one HTTP status code.");
}
return out;
}
static Args parseArgs(int argc, char** argv) {
Args args;
for (int i = 1; i < argc; ++i) {
std::string cur = argv[i];
if (cur.rfind("--", 0) == 0) {
std::string key = cur.substr(2);
if (key.empty()) {
throw std::runtime_error("Invalid argument: " + cur);
}
if (i + 1 >= argc) {
throw std::runtime_error("Missing value for --" + key);
}
std::string value = argv[++i];
if (key == "header") {
args.headers.push_back(value);
} else {
args.options[key] = value;
}
} else {
args.positional.push_back(cur);
}
}
return args;
}
static std::optional<json> evalJsonPath(const json& root, const std::string& path) {
if (path == "$") return root;
if (path.empty() || path[0] != '$') return std::nullopt;
const json* cur = &root;
size_t i = 1;
while (i < path.size()) {
if (path[i] == '.') {
++i;
size_t start = i;
while (i < path.size() && path[i] != '.' && path[i] != '[') ++i;
if (i == start) return std::nullopt;
std::string key = path.substr(start, i - start);
if (!cur->is_object()) return std::nullopt;
auto it = cur->find(key);
if (it == cur->end()) return std::nullopt;
cur = &(*it);
continue;
}
if (path[i] == '[') {
++i;
if (i >= path.size()) return std::nullopt;
bool quoted = false;
char quoteChar = '\0';
if (path[i] == '\'' || path[i] == '"') {
quoted = true;
quoteChar = path[i++];
}
size_t start = i;
if (quoted) {
while (i < path.size() && path[i] != quoteChar) ++i;
if (i >= path.size()) return std::nullopt;
std::string key = path.substr(start, i - start);
++i;
if (i >= path.size() || path[i] != ']') return std::nullopt;
++i;
if (!cur->is_object()) return std::nullopt;
auto it = cur->find(key);
if (it == cur->end()) return std::nullopt;
cur = &(*it);
} else {
while (i < path.size() && path[i] != ']') ++i;
if (i >= path.size()) return std::nullopt;
std::string idxText = trim(path.substr(start, i - start));
++i;
if (idxText.empty()) return std::nullopt;
int idx = std::stoi(idxText);
if (!cur->is_array() || idx < 0 || static_cast<size_t>(idx) >= cur->size()) return std::nullopt;
cur = &((*cur)[idx]);
}
continue;
}
return std::nullopt;
}
return *cur;
}
static std::string jsonScalarToString(const json& value) {
if (value.is_string()) return value.get<std::string>();
return value.dump();
}
static std::string applyTemplates(const std::string& input, const std::vector<StepResult>& history) {
static const std::regex rx(R"(\{\{(\d+):(\$[^}]*)\}\})");
std::string out;
std::sregex_iterator begin(input.begin(), input.end(), rx);
std::sregex_iterator end;
size_t cursor = 0;
for (auto it = begin; it != end; ++it) {
const auto& match = *it;
out.append(input.substr(cursor, static_cast<size_t>(match.position()) - cursor));
cursor = static_cast<size_t>(match.position() + match.length());
int stepIndex = std::stoi(match[1].str());
std::string path = match[2].str();
if (stepIndex < 0 || static_cast<size_t>(stepIndex) >= history.size() || !history[stepIndex].parsedJson.has_value()) {
throw std::runtime_error("Template reference cannot be resolved: " + match.str());
}
auto extracted = evalJsonPath(history[stepIndex].parsedJson.value(), path);
if (!extracted.has_value()) {
throw std::runtime_error("JSONPath not found in template: " + match.str());
}
out.append(jsonScalarToString(extracted.value()));
}
out.append(input.substr(cursor));
return out;
}
static json headersToJson(const std::unordered_map<std::string, std::string>& headers) {
json j = json::object();
for (const auto& [k, v] : headers) j[k] = v;
return j;
}
static cpr::Header toCprHeader(const std::unordered_map<std::string, std::string>& headers) {
cpr::Header out;
for (const auto& [k, v] : headers) out[k] = v;
return out;
}
static bool shouldRetry(const ResponseSnapshot& resp, const RequestSpec& req, int attemptNumber) {
if (attemptNumber > req.retries) return false;
if (!resp.ok) return true;
return req.retryOn.find(static_cast<int>(resp.statusCode)) != req.retryOn.end();
}
static double computeDelaySeconds(const RequestSpec& req, int attemptNumber) {
if (req.backoff == "linear") return req.retryDelaySec * static_cast<double>(attemptNumber);
return req.retryDelaySec * std::pow(2.0, static_cast<double>(attemptNumber - 1));
}
static ResponseSnapshot performHttp(const RequestSpec& req) {
cpr::Session session;
session.SetUrl(cpr::Url{req.url});
session.SetTimeout(cpr::Timeout{req.timeoutSec * 1000});
session.SetHeader(toCprHeader(req.headers));
if (!req.body.empty()) {
session.SetBody(cpr::Body{req.body});
}
cpr::Response response;
const std::string method = toUpper(req.method);
if (method == "GET") response = session.Get();
else if (method == "POST") response = session.Post();
else if (method == "PUT") response = session.Put();
else if (method == "PATCH") response = session.Patch();
else if (method == "DELETE") response = session.Delete();
else throw std::runtime_error("Unsupported method: " + req.method);
ResponseSnapshot snap;
snap.statusCode = response.status_code;
snap.body = response.text;
snap.ok = (response.error.code == cpr::ErrorCode::OK);
if (!snap.ok) {
snap.error = response.error.message;
}
for (const auto& [k, v] : response.header) snap.headers[k] = v;
return snap;
}
static std::optional<json> tryParseJson(const std::string& text) {
try {
return json::parse(text);
} catch (...) {
return std::nullopt;
}
}
static void printResponse(const ResponseSnapshot& resp) {
std::cout << "Status: " << resp.statusCode << "\n";
std::cout << "Headers:\n";
for (const auto& [k, v] : resp.headers) {
std::cout << " " << k << ": " << v << "\n";
}
std::cout << "Body:\n";
auto parsed = tryParseJson(resp.body);
if (parsed.has_value()) {
std::cout << parsed.value().dump(2) << "\n";
} else {
std::cout << resp.body << "\n";
}
}
static RequestSpec buildDefaultRequest(const Args& args) {
RequestSpec req;
if (args.options.count("method")) req.method = args.options.at("method");
req.headers = parseHeaderPairs(args.headers);
if (args.options.count("retries")) req.retries = std::stoi(args.options.at("retries"));
if (args.options.count("backoff")) req.backoff = args.options.at("backoff");
if (args.options.count("retry-delay")) req.retryDelaySec = std::stod(args.options.at("retry-delay"));
if (args.options.count("retry-on")) req.retryOn = parseRetryOn(args.options.at("retry-on"));
if (args.options.count("timeout")) req.timeoutSec = std::stoi(args.options.at("timeout"));
if (args.options.count("expect-status")) req.expectStatus = std::stoi(args.options.at("expect-status"));
if (args.options.count("expect-json")) req.expectJsonPath = args.options.at("expect-json");
return req;
}
static RequestSpec requestFromJson(const json& entry, const RequestSpec& base) {
RequestSpec req = base;
if (entry.contains("method")) req.method = entry.at("method").get<std::string>();
if (entry.contains("url")) req.url = entry.at("url").get<std::string>();
if (entry.contains("data")) req.body = entry.at("data").get<std::string>();
if (entry.contains("file")) req.body = readFileText(entry.at("file").get<std::string>());
if (entry.contains("retries")) req.retries = entry.at("retries").get<int>();
if (entry.contains("backoff")) req.backoff = entry.at("backoff").get<std::string>();
if (entry.contains("retry_delay")) req.retryDelaySec = entry.at("retry_delay").get<double>();
if (entry.contains("retry-delay")) req.retryDelaySec = entry.at("retry-delay").get<double>();
if (entry.contains("timeout")) req.timeoutSec = entry.at("timeout").get<int>();
if (entry.contains("expect_status")) req.expectStatus = entry.at("expect_status").get<int>();
if (entry.contains("expect-status")) req.expectStatus = entry.at("expect-status").get<int>();
if (entry.contains("expect_json")) req.expectJsonPath = entry.at("expect_json").get<std::string>();
if (entry.contains("expect-json")) req.expectJsonPath = entry.at("expect-json").get<std::string>();
if (entry.contains("retry_on")) {
if (entry.at("retry_on").is_array()) {
req.retryOn.clear();
for (const auto& code : entry.at("retry_on")) req.retryOn.insert(code.get<int>());
} else {
req.retryOn = parseRetryOn(entry.at("retry_on").get<std::string>());
}
}
if (entry.contains("retry-on")) {
if (entry.at("retry-on").is_array()) {
req.retryOn.clear();
for (const auto& code : entry.at("retry-on")) req.retryOn.insert(code.get<int>());
} else {
req.retryOn = parseRetryOn(entry.at("retry-on").get<std::string>());
}
}
if (entry.contains("headers")) {
req.headers.clear();
if (entry.at("headers").is_object()) {
for (auto it = entry.at("headers").begin(); it != entry.at("headers").end(); ++it) {
req.headers[it.key()] = it.value().get<std::string>();
}
} else if (entry.at("headers").is_array()) {
std::vector<std::string> raw;
for (const auto& h : entry.at("headers")) raw.push_back(h.get<std::string>());
req.headers = parseHeaderPairs(raw);
} else {
throw std::runtime_error("headers in chain request must be object or array.");
}
}
return req;
}
static RequestSpec renderRequest(const RequestSpec& in, const std::vector<StepResult>& history) {
RequestSpec out = in;
out.url = applyTemplates(in.url, history);
out.body = applyTemplates(in.body, history);
for (auto& [k, v] : out.headers) {
(void)k;
v = applyTemplates(v, history);
}
return out;
}
static StepResult executeRequest(const RequestSpec& req, json& attemptLogs) {
ResponseSnapshot finalResp;
for (int attempt = 1; attempt <= req.retries + 1; ++attempt) {
const auto t0 = std::chrono::steady_clock::now();
ResponseSnapshot resp = performHttp(req);
const auto t1 = std::chrono::steady_clock::now();
const auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count();
const bool retry = shouldRetry(resp, req, attempt);
json logEntry = {
{"attempt", attempt},
{"request", {
{"method", toUpper(req.method)},
{"url", req.url},
{"headers", headersToJson(req.headers)},
{"body", req.body}
}},
{"response", {
{"status_code", resp.statusCode},
{"headers", headersToJson(resp.headers)},
{"body", resp.body},
{"error", resp.error}
}},
{"timing_ms", elapsedMs},
{"will_retry", retry}
};
attemptLogs.push_back(logEntry);
finalResp = resp;
if (!retry) break;
const double delay = computeDelaySeconds(req, attempt);
std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<int>(delay * 1000.0)));
}
StepResult result;
result.response = finalResp;
result.parsedJson = tryParseJson(finalResp.body);
if (req.expectStatus.has_value() && finalResp.statusCode != req.expectStatus.value()) {
throw std::runtime_error("Expected status " + std::to_string(req.expectStatus.value()) +
" but received " + std::to_string(finalResp.statusCode));
}
if (req.expectJsonPath.has_value()) {
if (!result.parsedJson.has_value()) {
throw std::runtime_error("Expected JSONPath " + req.expectJsonPath.value() + " but response body is not JSON.");
}
auto found = evalJsonPath(result.parsedJson.value(), req.expectJsonPath.value());
if (!found.has_value()) {
throw std::runtime_error("Expected JSONPath not found: " + req.expectJsonPath.value());
}
}
return result;
}
static std::vector<RequestSpec> loadChain(const std::string& chainPath, const RequestSpec& defaults) {
json raw = json::parse(readFileText(chainPath));
json requests = raw;
if (raw.is_object()) {
if (!raw.contains("requests")) throw std::runtime_error("Chain JSON object must contain 'requests'.");
requests = raw.at("requests");
}
if (!requests.is_array() || requests.empty()) {
throw std::runtime_error("Chain must be a non-empty JSON array.");
}
std::vector<RequestSpec> out;
out.reserve(requests.size());
for (const auto& item : requests) {
if (!item.is_object()) throw std::runtime_error("Each chain request must be a JSON object.");
RequestSpec req = requestFromJson(item, defaults);
if (req.url.empty()) throw std::runtime_error("Each chain request must define 'url'.");
out.push_back(req);
}
return out;
}
static std::vector<RequestSpec> demoChain(const RequestSpec& defaults) {
RequestSpec a = defaults;
a.method = "GET";
a.url = "https://httpbin.org/get";
a.expectStatus = 200;
a.expectJsonPath = "$.url";
RequestSpec b = defaults;
b.method = "POST";
b.url = "https://httpbin.org/post";
b.headers["Content-Type"] = "application/json";
b.body = R"({"hello":"world","from":"demo"})";
b.expectStatus = 200;
b.expectJsonPath = "$.json.hello";
RequestSpec c = defaults;
c.method = "GET";
c.url = "https://httpbin.org/status/503";
c.retries = 2;
c.expectStatus = 503;
return {a, b, c};
}
int main(int argc, char** argv) {
try {
const Args args = parseArgs(argc, argv);
RequestSpec defaults = buildDefaultRequest(args);
const std::string outputPath = args.options.count("output") ? args.options.at("output") : "request_log.json";
std::vector<RequestSpec> steps;
std::string mode = "single";
if (args.options.count("chain")) {
mode = "chain";
steps = loadChain(args.options.at("chain"), defaults);
} else if (args.positional.empty()) {
mode = "demo";
steps = demoChain(defaults);
} else {
RequestSpec single = defaults;
single.url = args.positional.front();
if (args.options.count("file")) single.body = readFileText(args.options.at("file"));
if (args.options.count("data")) single.body = args.options.at("data");
steps = {single};
}
json fullLog = {
{"generated_at", nowIsoUtc()},
{"mode", mode},
{"steps", json::array()}
};
std::vector<StepResult> history;
for (size_t i = 0; i < steps.size(); ++i) {
RequestSpec rendered = renderRequest(steps[i], history);
json attemptLogs = json::array();
StepResult result = executeRequest(rendered, attemptLogs);
history.push_back(result);
json stepLog = {
{"index", i},
{"request", {
{"method", toUpper(rendered.method)},
{"url", rendered.url},
{"headers", headersToJson(rendered.headers)},
{"body", rendered.body},
{"retries", rendered.retries},
{"backoff", rendered.backoff},
{"retry_delay", rendered.retryDelaySec},
{"retry_on", rendered.retryOn},
{"timeout", rendered.timeoutSec},
{"expect_status", rendered.expectStatus.has_value() ? json(*rendered.expectStatus) : json(nullptr)},
{"expect_json", rendered.expectJsonPath.has_value() ? json(*rendered.expectJsonPath) : json(nullptr)}
}},
{"attempts", attemptLogs},
{"final_response", {
{"status_code", result.response.statusCode},
{"headers", headersToJson(result.response.headers)},
{"body", result.response.body},
{"error", result.response.error}
}}
};
fullLog["steps"].push_back(stepLog);
printResponse(result.response);
}
std::ofstream out(outputPath, std::ios::binary);
if (!out) throw std::runtime_error("Unable to write output log: " + outputPath);
out << fullLog.dump(2) << "\n";
out.close();
std::cout << "Saved request/response log to " << outputPath << "\n";
return 0;
} catch (const cpr::Error& err) {
std::cerr << "HTTP error: " << err.message << "\n";
return 1;
} catch (const std::exception& ex) {
std::cerr << "Error: " << ex.what() << "\n";
return 1;
}
}