Skip to content

Commit 6bb3a98

Browse files
committed
ADD: Add support for warning header to C++ client
1 parent 0fcfd6d commit 6bb3a98

15 files changed

+439
-279
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- Added optional `exception_callback` argument to `LiveThreaded::Start` to improve
66
error handling options
77
- Added batch download support data files (`condition.json` and `symbology.json`)
8+
- Added support for logging warnings from Historical API
89
- Changed `use_ts_out` default to `false`
910
- Fixed missing definition for `operator==` for `ImbalanceMsg`
1011

cmake/SourcesAndHeaders.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ set(headers
2222
include/databento/with_ts_out.hpp
2323
include/databento/detail/file_stream.hpp
2424
include/databento/detail/http_client.hpp
25+
include/databento/detail/json_helpers.hpp
2526
include/databento/detail/scoped_fd.hpp
2627
include/databento/detail/scoped_thread.hpp
2728
include/databento/detail/shared_channel.hpp
@@ -49,6 +50,7 @@ set(sources
4950
src/symbology.cpp
5051
src/detail/file_stream.cpp
5152
src/detail/http_client.cpp
53+
src/detail/json_helpers.cpp
5254
src/detail/scoped_fd.cpp
5355
src/detail/shared_channel.cpp
5456
src/detail/tcp_client.cpp

cmake/StandardSettings.cmake

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,6 @@ option(${PROJECT_NAME_UPPERCASE}_USE_EXTERNAL_GTEST "Use an external google test
1111
#
1212

1313
option(${PROJECT_NAME_UPPERCASE}_WARNINGS_AS_ERRORS "Treat compiler warnings as errors." ${IS_MAIN_PROJECT})
14-
option(${PROJECT_NAME_UPPERCASE}_FORCE_COLOR_OUTPUT "Always produce ANSI-colored output" OFF)
15-
16-
if(${PROJECT_NAME_UPPERCASE}_FORCE_COLOR_OUTPUT)
17-
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
18-
add_compile_options(-fdiagnostics-color=always)
19-
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
20-
add_compile_options(-fcolor-diagnostics)
21-
else()
22-
message(WARNING "Couldn't force color output with unsupported compiler: ${CMAKE_CXX_COMPILER_ID}")
23-
endif()
24-
endif()
2514

2615
#
2716
# Unit testing

include/databento/detail/http_client.hpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212
#include <string>
1313

1414
namespace databento {
15+
class ILogReceiver;
1516
namespace detail {
1617
class HttpClient {
1718
public:
18-
HttpClient(const std::string& key, const std::string& gateway);
19-
HttpClient(const std::string& key, const std::string& gateway,
20-
std::uint16_t port);
19+
HttpClient(ILogReceiver* log_receiver, const std::string& key,
20+
const std::string& gateway);
21+
HttpClient(ILogReceiver* log_receiver, const std::string& key,
22+
const std::string& gateway, std::uint16_t port);
2123

2224
nlohmann::json GetJson(const std::string& path,
2325
const httplib::Params& params);
@@ -27,12 +29,14 @@ class HttpClient {
2729
const httplib::ContentReceiver& callback);
2830

2931
private:
30-
static nlohmann::json CheckAndParseResponse(const std::string& path,
31-
httplib::Result&& res);
32+
nlohmann::json CheckAndParseResponse(const std::string& path,
33+
httplib::Result&& res) const;
34+
void CheckWarnings(const httplib::Response& response) const;
3235
static bool IsErrorStatus(int status_code);
3336

3437
static const httplib::Headers kHeaders;
3538

39+
ILogReceiver* log_receiver_;
3640
httplib::Client client_;
3741
};
3842
} // namespace detail
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#pragma once
2+
3+
#include <nlohmann/json.hpp>
4+
5+
#include <map> // multimap
6+
#include <string>
7+
#include <vector>
8+
9+
#include "databento/datetime.hpp" // UnixNanos
10+
#include "databento/enums.hpp" // FromString
11+
#include "databento/exceptions.hpp" // JsonResponseError
12+
13+
namespace httplib {
14+
using Params = std::multimap<std::string, std::string>;
15+
}
16+
17+
namespace databento {
18+
namespace detail {
19+
void SetIfNotEmpty(httplib::Params* params, const std::string& key,
20+
const std::string& value);
21+
void SetIfNotEmpty(httplib::Params* params, const std::string& key,
22+
const std::vector<databento::JobState>& states);
23+
24+
template <typename T>
25+
void SetIfPositive(httplib::Params* params, const std::string& key,
26+
const T value) {
27+
if (value > 0) {
28+
params->emplace(key, std::to_string(value));
29+
}
30+
}
31+
32+
template <>
33+
inline void SetIfPositive<databento::UnixNanos>(
34+
httplib::Params* params, const std::string& key,
35+
const databento::UnixNanos value) {
36+
if (value.time_since_epoch().count()) {
37+
params->emplace(key, databento::ToString(value));
38+
}
39+
}
40+
41+
const nlohmann::json& CheckedAt(const std::string& endpoint,
42+
const nlohmann::json& json,
43+
const std::string& key);
44+
45+
template <typename T>
46+
T FromCheckedAtString(const std::string& endpoint, const nlohmann::json& json,
47+
const std::string& key) {
48+
const auto& val_json = CheckedAt(endpoint, json, key);
49+
if (!val_json.is_string()) {
50+
throw JsonResponseError::TypeMismatch(endpoint, key + " string", val_json);
51+
}
52+
return databento::FromString<T>(val_json);
53+
}
54+
55+
template <typename T>
56+
T FromCheckedAtStringOrNull(const std::string& endpoint,
57+
const nlohmann::json& json, const std::string& key,
58+
T null_value) {
59+
const auto& val_json = CheckedAt(endpoint, json, key);
60+
if (val_json.is_null()) {
61+
return null_value;
62+
}
63+
if (val_json.is_string()) {
64+
return databento::FromString<T>(val_json);
65+
}
66+
throw JsonResponseError::TypeMismatch(endpoint, key + " null or string",
67+
val_json);
68+
}
69+
70+
template <typename T>
71+
T ParseAt(const std::string& endpoint, const nlohmann::json& json,
72+
const std::string& key);
73+
template <>
74+
bool ParseAt(const std::string& endpoint, const nlohmann::json& json,
75+
const std::string& key);
76+
template <>
77+
std::string ParseAt(const std::string& endpoint, const nlohmann::json& json,
78+
const std::string& key);
79+
template <>
80+
std::size_t ParseAt(const std::string& endpoint, const nlohmann::json& json,
81+
const std::string& key);
82+
template <>
83+
double ParseAt(const std::string& endpoint, const nlohmann::json& json,
84+
const std::string& key);
85+
template <>
86+
std::vector<std::string> ParseAt(const std::string& endpoint,
87+
const nlohmann::json& json,
88+
const std::string& key);
89+
90+
} // namespace detail
91+
} // namespace databento

include/databento/historical.hpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616
#include "databento/timeseries.hpp" // KeepGoing, MetadataCallback, RecordCallback
1717

1818
namespace databento {
19+
class ILogReceiver;
20+
1921
// A client for interfacing with Databento's historical market data API.
2022
class Historical {
2123
public:
22-
Historical(std::string key, HistoricalGateway gateway);
24+
Historical(ILogReceiver* log_receiver, std::string key,
25+
HistoricalGateway gateway);
2326
// Primarily for unit tests
24-
Historical(std::string key, std::string gateway, std::uint16_t port);
27+
Historical(ILogReceiver* log_receiver, std::string key, std::string gateway,
28+
std::uint16_t port);
2529

2630
/*
2731
* Getters
@@ -248,6 +252,7 @@ class Historical {
248252
DbnFileStore TimeseriesGetRangeToFile(const HttplibParams& params,
249253
const std::string& file_path);
250254

255+
ILogReceiver* log_receiver_;
251256
const std::string key_;
252257
const std::string gateway_;
253258
detail::HttpClient client_;
@@ -265,11 +270,14 @@ class HistoricalBuilder {
265270
HistoricalBuilder& SetKeyFromEnv();
266271
HistoricalBuilder& SetKey(std::string key);
267272
HistoricalBuilder& SetGateway(HistoricalGateway gateway);
273+
// Sets the receiver of the logs to be used by the client.
274+
HistoricalBuilder& SetLogReceiver(ILogReceiver* log_receiver);
268275
// Attempts to construct an instance of Historical or throws an exception if
269276
// no key has been set.
270277
Historical Build();
271278

272279
private:
280+
ILogReceiver* log_receiver_{};
273281
std::string key_;
274282
HistoricalGateway gateway_{HistoricalGateway::Bo1};
275283
};

src/detail/http_client.cpp

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#include "databento/detail/http_client.hpp"
22

3-
#include <chrono> // seconds
3+
#include <chrono> // seconds
4+
#include <sstream> // ostringstream
45

56
#include "databento/exceptions.hpp" // HttpResponseError, HttpRequestError, JsonResponseError
7+
#include "databento/log.hpp" // ILogReceiver, LogLevel
68
#include "databento/version.hpp" // DATABENTO_VERSION
79

810
using databento::detail::HttpClient;
@@ -13,17 +15,19 @@ const httplib::Headers HttpClient::kHeaders{
1315
{"user-agent", "Databento/" DATABENTO_VERSION " C++"},
1416
};
1517

16-
HttpClient::HttpClient(const std::string& key, const std::string& gateway)
17-
: client_{gateway} {
18+
HttpClient::HttpClient(databento::ILogReceiver* log_receiver,
19+
const std::string& key, const std::string& gateway)
20+
: log_receiver_{log_receiver}, client_{gateway} {
1821
client_.set_default_headers(HttpClient::kHeaders);
1922
client_.set_basic_auth(key, "");
2023
client_.set_read_timeout(kTimeout);
2124
client_.set_write_timeout(kTimeout);
2225
}
2326

24-
HttpClient::HttpClient(const std::string& key, const std::string& gateway,
27+
HttpClient::HttpClient(databento::ILogReceiver* log_receiver,
28+
const std::string& key, const std::string& gateway,
2529
std::uint16_t port)
26-
: client_{gateway, port} {
30+
: log_receiver_{log_receiver}, client_{gateway, port} {
2731
client_.set_default_headers(HttpClient::kHeaders);
2832
client_.set_basic_auth(key, "");
2933
client_.set_read_timeout(kTimeout);
@@ -79,19 +83,45 @@ void HttpClient::GetRawStream(const std::string& path,
7983
}
8084

8185
nlohmann::json HttpClient::CheckAndParseResponse(const std::string& path,
82-
httplib::Result&& res) {
86+
httplib::Result&& res) const {
8387
if (res.error() != httplib::Error::Success) {
8488
throw HttpRequestError{path, res.error()};
8589
}
86-
const auto status_code = res.value().status;
90+
auto& response = res.value();
91+
const auto status_code = response.status;
8792
if (HttpClient::IsErrorStatus(status_code)) {
88-
throw HttpResponseError{path, status_code, std::move(res.value().body)};
93+
throw HttpResponseError{path, status_code, std::move(response.body)};
8994
}
95+
CheckWarnings(response);
9096
try {
91-
return nlohmann::json::parse(std::move(res.value().body));
97+
return nlohmann::json::parse(std::move(response.body));
9298
} catch (const nlohmann::json::parse_error& parse_err) {
9399
throw JsonResponseError::ParseError(path, parse_err);
94100
}
95101
}
96102

103+
void HttpClient::CheckWarnings(const httplib::Response& response) const {
104+
// Returns empty string if not found. `get_header_value` is case insensitive
105+
const auto raw = response.get_header_value("X-Warning");
106+
if (!raw.empty()) {
107+
try {
108+
const auto json = nlohmann::json::parse(raw);
109+
if (json.is_array()) {
110+
for (const auto& warning_json : json.items()) {
111+
const std::string warning = warning_json.value();
112+
std::ostringstream msg;
113+
msg << __PRETTY_FUNCTION__ << " Server " << warning;
114+
log_receiver_->Receive(LogLevel::Warning, msg.str());
115+
}
116+
return;
117+
}
118+
} catch (const std::exception&) {
119+
}
120+
std::ostringstream msg;
121+
msg << __PRETTY_FUNCTION__
122+
<< " Failed to parse warnings from HTTP header. Raw contents: " << raw;
123+
log_receiver_->Receive(LogLevel::Warning, msg.str());
124+
}
125+
}
126+
97127
bool HttpClient::IsErrorStatus(int status_code) { return status_code >= 400; }

0 commit comments

Comments
 (0)