diff --git a/src/main.cpp b/src/main.cpp index a7b8337..af6bb8f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,63 +1,16 @@ -#define HAS_UNCAUGHT_EXCEPTIONS 1 - -#include -#include -#include -#include +#include "oro/api.hpp" #include -#include "date/date.h" - -namespace rc = restc_cpp; - -namespace oro { - typedef date::sys_time timestamp_t; - - struct Timestamp { - Timestamp& operator= (const std::string& str) { - std::istringstream iss(str); - date::from_stream(iss, "%FT%T%Ez", ts); - return *this; - } - - timestamp_t ts; - }; - - struct Ping { - Timestamp generation_timestamp; - std::string message; - int version; - }; -} //namespace oro - -BOOST_FUSION_ADAPT_STRUCT( - oro::Ping, - (oro::Timestamp, generation_timestamp) - (std::string, message) - (int, version) -) int main() { - using date::operator<<; + oro::Api oro_api("https://api.originsro.org", "RESTC_CPP", "Testing"); + auto ping = oro_api.ping(); - auto rest_client = rc::RestClient::Create(); - oro::Ping ping = rest_client->ProcessWithPromiseT([&](rc::Context& ctx) { - oro::Ping ping; - std::unique_ptr reply = rc::RequestBuilder(ctx) - .Get("https://api.originsro.org/api/v1/ping") - .Header("X-Client", "RESTC_CPP") - .Header("X-Client-Purpose", "Testing") - .Execute(); - - std::cout << "I got X-RateLimit-Limit: " << *reply->GetHeader("X-RateLimit-Limit") << '\n'; - - rc::SerializeFromJson(ping, std::move(reply)); - return ping; - }).get(); - - std::cout << "Response received:\n\ttimestamp: " << ping.generation_timestamp.ts; - std::cout << "\n\tmessage: " << ping.message; - std::cout << "\n\tversion: " << ping.version; - - std::cout << std::endl; + std::cout << "rate limit: " << ping.first.rate_limit << '\n'; + std::cout << "remaining: " << ping.first.rate_limit_remaining << '\n'; + std::cout << "reset: " << ping.first.rate_limit_reset << '\n'; + std::cout << "retry after: " << ping.first.retry_after << '\n'; + std::cout << "server: " << ping.first.server << '\n'; + std::cout << "answer: " << ping.second.message << '\n'; + std::cout << "version: " << ping.second.version << '\n'; return 0; } diff --git a/src/meson.build b/src/meson.build index 2108f82..5e51c11 100644 --- a/src/meson.build +++ b/src/meson.build @@ -7,6 +7,8 @@ restc_cpp_dep = dependency('restc-cpp', version: '>=0.1.1', executable(meson.project_name(), 'main.cpp', + 'oro/datatypes.cpp', + 'oro/api.cpp', install: true, dependencies: [restc_cpp_dep], include_directories: date_incdir, diff --git a/src/oro/api.cpp b/src/oro/api.cpp new file mode 100644 index 0000000..dce130d --- /dev/null +++ b/src/oro/api.cpp @@ -0,0 +1,75 @@ +#include "api.hpp" +#include "datatypes.hpp" +#include +#include +#include + +namespace rc = restc_cpp; + +BOOST_FUSION_ADAPT_STRUCT( + oro::Ping, + (oro::Timestamp, generation_timestamp) + (std::string, message) + (int, version) +) + +namespace oro { +namespace { + constexpr const char g_endpoint_ping[] = "api/v1/ping"; + + template + std::pair call_rest_api ( + rc::RestClient& rest_client, + const std::string& url, + const std::string& client_name, + const std::string& client_purpose + ) { + return rest_client.template ProcessWithPromiseT< std::pair >([&](rc::Context& ctx) { + std::unique_ptr reply = rc::RequestBuilder(ctx) + .Get(url) + .Header("X-Client", client_name) + .Header("X-Client-Purpose", client_purpose) + .Execute(); + + Header h; + if (auto val = reply->GetHeader("X-RateLimit-Limit")) { + h.rate_limit = std::stoi(*val); + } + if (auto val = reply->GetHeader("X-RateLimit-Remaining")) { + h.rate_limit_remaining = std::stoi(*val); + } + if (auto val = reply->GetHeader("X-RateLimit-Reset")) { + h.rate_limit_reset = std::stoul(*val); + } + if (auto val = reply->GetHeader("Retry-After")) { + h.retry_after = std::stoul(*val); + } + if (auto val = reply->GetHeader("Server")) { + h.server = std::move(*val); + } + + T retval; + rc::SerializeFromJson(retval, std::move(reply)); + + return std::make_pair(std::move(h), std::move(retval)); + }).get(); + } +} //unnamed namespace + +Api::Api (std::string&& root_address, std::string&& client_name, std::string&& client_purpose) : + m_prefix(std::move(root_address)), + m_client_name(std::move(client_name)), + m_client_purpose(std::move(client_purpose)), + m_client(rc::RestClient::Create()) +{ + if (not m_prefix.empty() and m_prefix[m_prefix.size() - 1] != '/') + m_prefix.push_back('/'); +} + +Api::~Api() noexcept = default; + +std::pair Api::ping() { + return call_rest_api(*m_client, m_prefix + g_endpoint_ping, m_client_name, m_client_purpose); +} + +} //namespace oro diff --git a/src/oro/api.hpp b/src/oro/api.hpp new file mode 100644 index 0000000..9fd1725 --- /dev/null +++ b/src/oro/api.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include "ping.hpp" +#include +#include +#include + +namespace restc_cpp { +class RestClient; +} //namespace restc_cpp + +namespace oro { + +struct Header { + //Date: Fri, 19 Jun 2020 22:33:43 GMT + Timestamp date; + + //Content-Type: application/json + //Content-Length: 89 + //Connection: keep-alive + //Set-Cookie: __cfduid=dccdd965b47650be8b0107bb1292154981592606023; expires=Sun, 19-Jul-20 22:33:43 GMT; path=/; domain=.originsro.org; HttpOnly; SameSite=Lax + + //X-RateLimit-Limit: 2 + int rate_limit; + + //X-RateLimit-Remaining: 0 + int rate_limit_remaining; + + //X-RateLimit-Reset: 1592606027 + unsigned long rate_limit_reset; + + //Retry-After: 3 + unsigned long retry_after; + + //CF-Cache-Status: DYNAMIC + //cf-request-id: 03705077e200000091668c6200000001 + //Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" + //Server: cloudflare + std::string server; + + //CF-RAY: 5a60b69fdc270091-LHR + + +}; + +class Api { +public: + Api ( + std::string&& root_address, + std::string&& client_name, + std::string&& client_purpose + ); + ~Api() noexcept; + + std::pair ping(); + +private: + std::string m_prefix; + std::string m_client_name; + std::string m_client_purpose; + + std::unique_ptr m_client; +}; + +} //namespace oro diff --git a/src/oro/datatypes.cpp b/src/oro/datatypes.cpp new file mode 100644 index 0000000..1805630 --- /dev/null +++ b/src/oro/datatypes.cpp @@ -0,0 +1,15 @@ +#include "datatypes.hpp" + +#define HAS_UNCAUGHT_EXCEPTIONS 1 +#include "date/date.h" +#include + +namespace oro { + Timestamp& Timestamp::operator= (const std::string& str) { + std::istringstream iss(str); + + //date has this format: 2020-06-19T22:33:36.855672+00:00 + date::from_stream(iss, "%FT%T%Ez", ts); + return *this; + } +} //namespace oro diff --git a/src/oro/datatypes.hpp b/src/oro/datatypes.hpp new file mode 100644 index 0000000..e976c4a --- /dev/null +++ b/src/oro/datatypes.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +namespace oro { + typedef std::chrono::time_point timestamp_t; + + struct Timestamp { + Timestamp& operator= (const std::string& str); + operator timestamp_t() const { return ts; } + + timestamp_t ts; + }; +} //namespace oro diff --git a/src/oro/ping.hpp b/src/oro/ping.hpp new file mode 100644 index 0000000..e173c20 --- /dev/null +++ b/src/oro/ping.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "datatypes.hpp" +#include + +namespace oro { + struct Ping { + Timestamp generation_timestamp; + std::string message; + int version; + }; +} //namespace oro