32 #include <type_traits>
35 #include "exceptions.hpp"
37 #include "cpp-httplib/httplib.h"
38 #define FMT_HEADER_ONLY 1
39 #include "fmt/include/fmt/args.h"
40 #include "fmt/include/fmt/format.h"
41 #include "rapidcsv/src/rapidcsv.h"
42 #include "rapidjson/include/rapidjson/document.h"
43 #include "rapidjson/include/rapidjson/encodings.h"
44 #include "rapidjson/include/rapidjson/rapidjson.h"
45 #include "rapidjson/include/rapidjson/stringbuffer.h"
46 #include "rapidjson/include/rapidjson/writer.h"
51 #else // Windows check
53 #define WORDS_BIGENDIAN 1
55 #ifdef __LITTLE_ENDIAN__
56 #undef WORDS_BIGENDIAN
59 #include <sys/endian.h>
63 #if __BYTE_ORDER == __BIG_ENDIAN
64 #define WORDS_BIGENDIAN 1
65 #elif __BYTE_ORDER == __LITTLE_ENDIAN
66 #undef WORDS_BIGENDIAN
68 #error "unable to determine endianess!"
72 #endif // Windows check
75 using fmt::literals::operator
""_a;
77 #define FMT fmt::format
80 #define GENERATE_FLUENT_METHOD(returnType, fieldType, fieldName, methodName) \
81 returnType& methodName(fieldType arg) { \
86 namespace kiteconnect::internal::utils {
88 namespace rj = rapidjson;
91 using FmtArgs = std::vector<string>;
93 struct isOptional : std::false_type {};
95 struct isOptional<std::optional<T>> : std::true_type {};
98 struct isVector : std::false_type {};
100 struct isVector<std::vector<T>> : std::true_type {};
102 constexpr uint16_t MILLISECONDS_IN_A_SECOND = 1000;
106 using JsonObject = rj::GenericValue<rj::UTF8<>>::Object;
107 using JsonArray = rj::GenericValue<rj::UTF8<>>::Array;
109 using CustomObjectParser = std::function<Res(JsonObject&)>;
111 using CustomArrayParser = std::function<Res(JsonArray&)>;
112 template <
class Res,
class Data,
bool UseCustomParser>
113 using CustomParser = std::conditional_t<std::is_same_v<Data, JsonObject>,
114 const CustomObjectParser<Res>&,
const CustomArrayParser<Res>&>;
116 using JsonEncoder = std::function<void(
const T&, rj::Value&)>;
119 inline JsonObject extractObject(rj::Document& doc) {
121 return doc[
"data"].GetObject();
122 }
catch (
const std::exception& ex) {
throw libException(
"invalid body"); }
125 inline JsonArray extractArray(rj::Document& doc) {
127 return doc[
"data"].GetArray();
128 }
catch (
const std::exception& ex) {
throw libException(
"invalid body"); }
131 inline bool extractBool(rj::Document& doc) {
133 return doc[
"data"].GetBool();
134 }
catch (
const std::exception& ex) {
throw libException(
"invalid body"); }
137 inline string extractString(rj::Document& doc) {
139 return doc[
"data"].GetString();
140 }
catch (
const std::exception& ex) {
throw libException(
"invalid body"); }
143 template <
class Output,
class Document = rj::Value::Object>
145 inline Output get(
const Document& val,
const char* name) {
146 static const auto exceptionString = [&name](
const string& type) {
147 return FMT(
"type of {0} not is not {1}", name, type);
150 auto it = val.FindMember(name);
151 if (it != val.MemberEnd()) {
152 if constexpr (!isVector<Output>::value) {
153 if constexpr (std::is_same_v<std::decay_t<Output>,
string>) {
154 if (it->value.IsString()) {
return it->value.GetString(); };
155 if (it->value.IsNull()) {
return ""; };
156 throw libException(exceptionString(
"string"));
157 }
else if constexpr (std::is_same_v<std::decay_t<Output>,
double>) {
158 if (it->value.IsDouble()) {
return it->value.GetDouble(); };
161 if (it->value.IsInt()) {
return it->value.GetInt(); };
162 throw libException(exceptionString(
"double"));
163 }
else if constexpr (std::is_same_v<std::decay_t<Output>,
int>) {
164 if (it->value.IsInt()) {
return it->value.GetInt(); };
165 throw libException(exceptionString(
"int"));
166 }
else if constexpr (std::is_same_v<std::decay_t<Output>,
168 if (it->value.IsUint()) {
return it->value.GetUint(); };
169 throw libException(exceptionString(
"uint32_t"));
170 }
else if constexpr (std::is_same_v<std::decay_t<Output>,
172 if (it->value.IsInt64()) {
return it->value.GetInt64(); };
173 throw libException(exceptionString(
"int64_t"));
174 }
else if constexpr (std::is_same_v<std::decay_t<Output>,
bool>) {
175 if (it->value.IsBool()) {
return it->value.GetBool(); };
176 throw libException(exceptionString(
"bool"));
178 throw libException(
"type not supported");
181 if (it->value.IsArray()) {
183 for (
const auto& v : it->value.GetArray()) {
184 if constexpr (std::is_same_v<
185 std::decay_t<typename Output::value_type>,
188 out.emplace_back(v.GetString()) :
189 throw libException(exceptionString(
"string"));
190 }
else if constexpr (std::is_same_v<
192 typename Output::value_type>,
195 out.emplace_back(v.GetDouble());
201 out.emplace_back(v.GetInt());
204 throw libException(exceptionString(
"array of doubles"));
206 throw libException(
"type not supported");
211 throw libException(exceptionString(
"array"));
218 template <
class Val,
class Output>
219 Output get(
const rj::Value::Object& val,
const char* name) {
220 static const auto exceptionString = [&name](
const string& type) {
221 return FMT(
"type of {0} not is not {1}", name, type);
224 auto it = val.FindMember(name);
225 if constexpr (std::is_same_v<Val, JsonObject>) {
226 static_assert(std::is_constructible_v<Output, rj::Value::Object>);
227 rj::Value out(rj::kObjectType);
228 if (it != val.MemberEnd()) {
229 if (it->value.IsObject()) {
return Output(it->value.GetObject()); };
230 throw libException(exceptionString(
"object"));
233 }
else if constexpr (std::is_same_v<Val, JsonArray>) {
234 static_assert(std::is_constructible_v<Output, rj::Value::Array>);
235 rj::Value out(rj::kArrayType);
236 if (it != val.MemberEnd()) {
237 if (it->value.IsArray()) {
return Output(it->value.GetArray()); };
238 throw libException(exceptionString(
"array"));
246 bool get(
const rj::Value::Object& val, rj::Value& out,
const char* name) {
247 static const auto exceptionString = [&name](
const string& type) {
248 return FMT(
"type of {0} not is not {1}", name, type);
251 auto it = val.FindMember(name);
252 if constexpr (std::is_same_v<Val, JsonObject>) {
253 if (it != val.MemberEnd()) {
254 if (it->value.IsObject()) {
255 out = it->value.GetObject();
258 throw libException(exceptionString(
"object"));
261 }
else if constexpr (std::is_same_v<Val, JsonArray>) {
262 if (it != val.MemberEnd()) {
263 if (it->value.IsArray()) {
264 out = it->value.GetArray();
267 throw libException(exceptionString(
"array"));
274 template <
class Res,
class Data,
bool UseCustomParser>
276 rj::Document& doc, CustomParser<Res, Data, UseCustomParser> customParser) {
277 if constexpr (std::is_same_v<Data, JsonObject>) {
278 auto object = extractObject(doc);
279 if constexpr (UseCustomParser) {
280 return customParser(
object);
282 static_assert(std::is_constructible_v<Res, JsonObject>,
283 "Res should be constructable using JsonObject");
286 }
else if constexpr (std::is_same_v<Data, JsonArray>) {
287 auto array = extractArray(doc);
288 if constexpr (UseCustomParser) {
289 return customParser(array);
291 static_assert(std::is_constructible_v<Res, JsonArray>,
292 "Res should be constructable using JsonArray");
295 }
else if constexpr (std::is_same_v<Data, bool>) {
297 std::is_same_v<Res, bool>,
"Res needs to be bool if Data is bool");
298 return extractBool(doc);
302 inline bool parse(rj::Document& dom,
const string& str) {
303 rj::ParseResult result = dom.Parse(str.c_str());
304 if (result ==
nullptr) {
305 throw libException(FMT(
"failed to parse json string: {0}", str));
310 inline string serialize(rj::Document& dom) {
311 rj::StringBuffer buffer;
312 rj::Writer<rj::StringBuffer> writer(buffer);
314 return buffer.GetString();
321 if constexpr (std::is_same_v<T, JsonObject>) {
328 template <
class Value>
329 void field(
const string& name,
const Value& value,
330 rj::Value* docOverride =
nullptr) {
331 auto& allocater = dom.GetAllocator();
333 if constexpr (std::is_same_v<std::decay_t<Value>,
string>) {
334 buffer.SetString(value.c_str(), value.size(), allocater);
335 }
else if constexpr (std::is_integral_v<std::decay_t<Value>>) {
336 buffer.SetInt64(value);
337 }
else if constexpr (std::is_floating_point_v<std::decay_t<Value>>) {
338 buffer.SetDouble(value);
341 if (docOverride ==
nullptr) {
343 rj::Value(name.c_str(), allocater).Move(), buffer, allocater);
345 docOverride->AddMember(
346 rj::Value(name.c_str(), allocater).Move(), buffer, allocater);
350 template <
class Value>
351 void field(
const string& name,
const std::vector<Value>& values,
352 const JsonEncoder<Value>& encode = {}) {
353 auto& allocater = dom.GetAllocator();
354 rj::Value arrayBuffer(rj::kArrayType);
355 for (
const auto& i : values) {
356 if constexpr (std::is_fundamental_v<std::decay_t<Value>>) {
357 arrayBuffer.PushBack(i, allocater);
359 rj::Value objectBuffer(rj::kObjectType);
360 encode(i, objectBuffer);
361 arrayBuffer.PushBack(objectBuffer, allocater);
365 if constexpr (std::is_same_v<T, JsonObject>) {
366 dom.AddMember(rj::Value(name.c_str(), dom.GetAllocator()).Move(),
367 arrayBuffer, allocater);
369 dom.Swap(arrayBuffer);
373 template <
class Value>
374 void array(
const std::vector<Value>& values,
375 const JsonEncoder<Value>& encode = {}) {
376 field(
"", values, encode);
380 rj::StringBuffer strBuffer;
381 rj::Writer<rj::StringBuffer> writer(strBuffer);
383 return strBuffer.GetString();
394 using Params = httplib::Params;
397 constexpr uint16_t OK = 200;
400 enum class METHOD : uint8_t
409 enum class CONTENT_TYPE : uint8_t
416 bool operator==(
const endpoint& lhs)
const {
417 return lhs.method == this->method && lhs.Path.Path == this->Path.Path &&
418 lhs.contentType == this->contentType;
421 METHOD method = METHOD::GET;
424 string operator()(
const FmtArgs& fmtArgs = {})
const {
425 if (!fmtArgs.empty()) {
426 fmt::dynamic_format_arg_store<fmt::format_context> store;
427 for (
const auto& arg : fmtArgs) { store.push_back(arg); };
428 return fmt::vformat(Path, store);
435 CONTENT_TYPE contentType = CONTENT_TYPE::NON_JSON;
436 CONTENT_TYPE responseType = CONTENT_TYPE::JSON;
441 response(uint16_t Code,
const string& body,
bool json =
true): code(Code) {
442 parse(Code, body, json);
445 explicit operator bool()
const {
return !error; };
458 void parse(uint16_t code,
const string& body,
bool json) {
460 json::parse(data, body);
461 if (code !=
static_cast<uint16_t
>(code::OK)) {
463 status = utils::json::get<string, rj::Document>(data,
"status");
465 utils::json::get<string, rj::Document>(data,
"error_type");
467 utils::json::get<string, rj::Document>(data,
"message");
468 if (status !=
"success") { error =
true; };
471 if (code !=
static_cast<uint16_t
>(code::OK)) { error =
true; };
480 response send(httplib::Client& client)
const {
481 const httplib::Headers headers = { {
"Authorization", authToken } };
488 case utils::http::METHOD::GET:
489 if (
auto res = client.Get(path, headers)) {
493 throw libException(FMT(
"request failed ({0})",
494 httplib::to_string(res.error())));
497 case utils::http::METHOD::POST:
498 if (contentType != CONTENT_TYPE::JSON) {
499 if (
auto res = client.Post(path, headers, body)) {
503 throw libException(FMT(
"request failed ({0})",
504 httplib::to_string(res.error())));
507 if (
auto res = client.Post(path, headers, serializedBody,
508 "application/json")) {
512 throw libException(FMT(
"request failed({0})",
513 httplib::to_string(res.error())));
517 case utils::http::METHOD::PUT:
518 if (contentType != CONTENT_TYPE::JSON) {
519 if (
auto res = client.Put(path, headers, body)) {
523 throw libException(FMT(
"request failed ({0})",
524 httplib::to_string(res.error())));
527 if (
auto res = client.Put(path, headers, serializedBody,
528 "application/json")) {
532 throw libException(FMT(
"request failed({0})",
533 httplib::to_string(res.error())));
537 case utils::http::METHOD::DEL:
538 if (
auto res = client.Delete(path, headers)) {
542 throw libException(FMT(
"request failed ({0})",
543 httplib::to_string(res.error())));
546 default:
throw libException(
"unsupported http method");
549 return { code, data, responseType == CONTENT_TYPE::JSON };
552 utils::http::METHOD method;
556 CONTENT_TYPE contentType = CONTENT_TYPE::NON_JSON;
557 CONTENT_TYPE responseType = CONTENT_TYPE::JSON;
558 string serializedBody;
562 namespace ws::ERROR_CODE {
563 const unsigned int NORMAL_CLOSURE = 1000;
564 const unsigned int NO_REASON = 1006;
567 template <
class Param>
568 void addParam(http::Params& bodyParams, Param& param,
const string& fieldName) {
570 isOptional<std::decay_t<Param>>::value,
"Param must be std::optional");
571 if (param.has_value()) {
573 if constexpr (!std::is_same_v<typename Param::value_type, string>) {
574 fieldValue = std::to_string(param.value());
576 fieldValue = param.value();
578 if (param.has_value()) { bodyParams.emplace(fieldName, fieldValue); }
582 template <
class Instrument>
583 inline std::vector<Instrument> parseInstruments(
const std::string& data) {
584 static_assert(std::is_constructible_v<Instrument, std::vector<string>>,
585 "Instrument must have a constructor that accepts vector of strings");
587 std::stringstream sstream(data);
588 rapidcsv::Document csv(sstream, rapidcsv::LabelParams(0, -1));
589 const size_t numberOfRows = csv.GetRowCount();
591 std::vector<Instrument> instruments;
592 for (
size_t row = 0; row < numberOfRows; row++) {
593 instruments.emplace_back(csv.GetRow<
string>(row));