55#else
66#include < cstring> // strerror
77#endif
8+ #include < nlohmann/json.hpp>
9+
10+ #include < exception> // exception
11+ #include < optional>
812#include < sstream> // ostringstream
913#include < utility> // move
1014
15+ #include " databento/detail/json_helpers.hpp"
16+
1117using databento::HttpRequestError;
1218
1319std::string HttpRequestError::BuildMessage (std::string_view request_path,
@@ -32,12 +38,58 @@ std::string TcpError::BuildMessage(int err_num, std::string message) {
3238
3339using databento::HttpResponseError;
3440
35- std::string HttpResponseError::BuildMessage (std::string_view request_path,
36- std::int32_t status_code,
37- std::string_view response_body) {
41+ HttpResponseError::HttpResponseError (const std::string& request_path,
42+ std::int32_t status_code,
43+ const std::string& response_body)
44+ : HttpResponseError{request_path, status_code, response_body,
45+ ParseDetail (request_path, response_body)} {}
46+
47+ HttpResponseError::HttpResponseError (std::string request_path, std::int32_t status_code,
48+ const std::string& response_body,
49+ ParsedDetail parsed)
50+ : Exception{
51+ BuildMessage (request_path, status_code, response_body, parsed.case_str )},
52+ request_path_{std::move (request_path)},
53+ status_code_{status_code},
54+ case_{std::move (parsed.case_str )},
55+ detail_message_{std::move (parsed.detail_message )},
56+ docs_url_{std::move (parsed.docs_url )} {}
57+
58+ HttpResponseError::ParsedDetail HttpResponseError::ParseDetail (
59+ std::string_view request_path, const std::string& response_body) {
60+ // Best-effort parse of the historical API rich error format
61+ // {"detail": {"case": "...", "message": "...", "docs": "...", "payload": {...}}}
62+ // Falls back to {"detail": "..."} or leaves all fields empty for non-JSON bodies
63+ ParsedDetail out;
64+ try {
65+ const auto json = nlohmann::json::parse (response_body);
66+ const auto & detail = detail::CheckedAt (request_path, json, " detail" );
67+ if (detail.is_string ()) {
68+ out.detail_message = detail.get <std::string>();
69+ } else if (detail.is_object ()) {
70+ out.case_str =
71+ detail::ParseAt<std::optional<std::string>>(request_path, detail, " case" );
72+ out.detail_message =
73+ detail::ParseAt<std::optional<std::string>>(request_path, detail, " message" );
74+ out.docs_url =
75+ detail::ParseAt<std::optional<std::string>>(request_path, detail, " docs" );
76+ }
77+ } catch (const std::exception&) { // NOLINT(bugprone-empty-catch)
78+ // Body wasn't JSON, didn't have a `detail` key, or had unexpected types; the
79+ // raw body is still embedded in `what()`, so leave the parsed fields empty.
80+ }
81+ return out;
82+ }
83+
84+ std::string HttpResponseError::BuildMessage (
85+ std::string_view request_path, std::int32_t status_code,
86+ std::string_view response_body, const std::optional<std::string>& case_str) {
3887 std::ostringstream err_msg;
3988 err_msg << " Received an error response from request to " << request_path
4089 << " with status " << status_code << " and body '" << response_body << ' \' ' ;
90+ if (case_str) {
91+ err_msg << " (case: " << *case_str << ' )' ;
92+ }
4193 return err_msg.str ();
4294}
4395
0 commit comments