orotool/src/oro/api_nap.cpp

255 lines
8 KiB
C++

/* Copyright 2020, Michele Santullo
* This file is part of orotool.
*
* Orotool is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Orotool is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Orotool. If not, see <http://www.gnu.org/licenses/>.
*/
#include "private/api_nap.hpp"
#include "private/v1_endpoints.hpp"
#include "private/dateconv.hpp"
#include "duckhandy/int_conv.hpp"
#include "api_nap_exception.hpp"
#include <optional>
#include <algorithm>
#include <cctype>
namespace sjd = simdjson::dom;
namespace sj = simdjson;
namespace oro {
namespace {
bool equal (std::string_view a, std::string_view b) {
return a.size() == b.size() and std::equal(
a.begin(), a.end(),
b.begin(),
[](auto c1, auto c2) {return std::tolower(c1) == std::tolower(c2); }
);
}
Header to_header (const nap::HttpResponse& resp) {
using dhandy::int_conv;
Header ret;
for (const auto& entry : resp.header_list) {
if (equal(entry.first, "Date"))
ret.date = from_header_timestamp(std::string(entry.second));
else if (equal(entry.first, "X-RateLimit-Limit"))
ret.rate_limit = int_conv<decltype(ret.rate_limit)>(entry.second);
else if (equal(entry.first, "X-RateLimit-Remaining"))
ret.rate_limit_remaining = int_conv<decltype(ret.rate_limit_remaining)>(entry.second);
else if (equal(entry.first, "X-RateLimit-Reset"))
ret.rate_limit_reset = int_conv<decltype(ret.rate_limit_reset)>(entry.second);
else if (equal(entry.first, "Retry-After"))
ret.retry_after = int_conv<decltype(ret.retry_after)>(entry.second);
else if (equal(entry.first, "Server"))
ret.server = std::string(entry.second);
}
return ret;
}
void fill_base_json (const simdjson::dom::element& doc, BaseJsonReply& out) {
out.generation_timestamp = std::string(doc["generation_timestamp"]);
out.version = static_cast<int>(doc["version"].get_int64());
}
template <typename R, typename T>
std::optional<R> get_optional (const sj::simdjson_result<sjd::element>& elem) {
typedef std::optional<R> RetType;
T retval;
const simdjson::error_code error = elem.get(retval);
return (not error ? RetType{static_cast<R>(retval)} : RetType{});
}
template <typename R, typename T>
R get_or_default (const sj::simdjson_result<sjd::element>& elem, R def) {
T retval;
const simdjson::error_code error = elem.get(retval);
return (not error ? static_cast<R>(retval) : def);
}
void fill_creator_list (const sjd::element& elem, std::vector<Creator>& out) {
if (elem.is_array()) {
auto creators = elem.get_array();
out.reserve(creators.size());
for (auto creator : creators) {
Creator new_entry;
new_entry.char_id = static_cast<unsigned int>(creator["char_id"].get_uint64());
new_entry.name = creator["name"];
new_entry.points = static_cast<unsigned int>(creator["points"].get_uint64());
out.push_back(std::move(new_entry));
}
}
}
} //unnamed namespace
ApiNap::ApiNap (
std::string&& root_address,
std::string&& api_key,
std::string&& client_name,
std::string&& client_purpose
) :
Api(std::move(root_address), std::move(api_key), std::move(client_name), std::move(client_purpose)),
m_qrest(simdjson::SIMDJSON_PADDING)
{
m_qrest.add_headers({
{"X-Client", m_client_name},
{"X-Client-Purpose", m_client_purpose},
{"x-api-key", m_api_key}
});
m_qrest.set_user_agent(std::string(m_client_name));
}
ApiNap::~ApiNap() noexcept = default;
std::pair<Header, Ping> ApiNap::ping() {
return fetch_and_parse<Ping>(
g_endpoint_ping,
[](const simdjson::dom::element& doc, Ping& out) {
out.message = doc["message"];
}
);
}
std::pair<Header, WhoAmI> ApiNap::who_am_i() {
return fetch_and_parse<WhoAmI>(
g_endpoint_whoami,
[](const simdjson::dom::element& doc, WhoAmI& out) {
out.master_id = static_cast<unsigned int>(doc["master_id"].get_uint64());
}
);
}
std::pair<Header, Items> ApiNap::items_list() {
return fetch_and_parse<Items>(
g_endpoint_items_list,
[](const simdjson::dom::element& doc, Items& out) {
auto items = doc["items"].get_array();
out.items.reserve(items.size());
for (auto item : items) {
Item new_entry;
new_entry.item_id = static_cast<unsigned int>(item["item_id"].get_uint64());
new_entry.unique_name = item["unique_name"];
new_entry.name = item["name"];
new_entry.type = item["type"].get_string();
new_entry.subtype = get_optional<ItemSubtypeWrapper, std::string_view>(item["subtype"]);
new_entry.npc_price = static_cast<unsigned int>(item["npc_price"].get_uint64());
new_entry.slots = get_optional<unsigned int, uint64_t>(item["slots"]);
out.items.push_back(std::move(new_entry));
}
}
);
}
std::pair<Header, Icons> ApiNap::items_icons() {
return fetch_and_parse<Icons>(
g_endpoint_items_icons,
[](const simdjson::dom::element& doc, Icons& out) {
auto icons = doc["icons"].get_array();
out.icons.reserve(icons.size());
for (auto icon : icons) {
Icon new_entry;
new_entry.item_id = static_cast<unsigned int>(icon["item_id"].get_uint64());
new_entry.icon = icon["icon"];
out.icons.push_back(std::move(new_entry));
}
}
);
}
std::pair<Header, Shops> ApiNap::market_list() {
return fetch_and_parse<Shops>(
g_endpoint_market_list,
[](const simdjson::dom::element& doc, Shops& out) {
auto shops = doc["shops"].get_array();
out.shops.reserve(shops.size());
for (auto shop : shops) {
Shop new_shop;
new_shop.title = shop["title"];
new_shop.owner = shop["owner"];
new_shop.creation_date = std::string(shop["creation_date"]);
new_shop.type = shop["type"].get_string();
{
auto loc = shop["location"];
new_shop.location.map = loc["map"];
new_shop.location.x = static_cast<int>(loc["x"].get_int64());
new_shop.location.y = static_cast<int>(loc["y"].get_int64());
}
{
auto items = shop["items"].get_array();
new_shop.items.reserve(items.size());
for (auto item : items) {
ShopItem new_item;
new_item.item_id = static_cast<unsigned int>(item["item_id"].get_uint64());
new_item.amount = static_cast<unsigned int>(item["amount"].get_uint64());
new_item.price = static_cast<unsigned int>(item["price"].get_uint64());
new_item.refine = get_or_default<unsigned int, uint64_t>(item["refine"], 0);
new_item.star_crumbs = get_or_default<unsigned int, uint64_t>(item["star_crumbs"], 0);
new_item.element = get_optional<std::string, std::string_view>(item["element"]);
new_item.creator = get_optional<unsigned int, uint64_t>(item["creator"]);
new_item.beloved = get_optional<bool, bool>(item["beloved"]);
auto item_cards = item["cards"];
if (item_cards.is_array()) {
auto cards = item_cards.get_array();
new_item.cards.reserve(cards.size());
for (uint64_t card : cards) {
new_item.cards.push_back(static_cast<unsigned int>(card));
}
}
new_shop.items.push_back(std::move(new_item));
}
}
out.shops.push_back(std::move(new_shop));
}
}
);
}
std::pair<Header, Creators> ApiNap::fame_list() {
return fetch_and_parse<Creators>(
g_endpoint_fame_list,
[](const simdjson::dom::element& doc, Creators& out) {
fill_creator_list(doc["brewers"], out.brewers);
fill_creator_list(doc["forgers"], out.brewers);
}
);
}
template <typename T, typename F>
std::pair<Header, T> ApiNap::fetch_and_parse (const char* endpoint, F&& data_fill) {
auto resp = m_qrest.fetch(m_prefix + endpoint);
if (200 != resp.code)
throw ServerError(resp);
T dataret;
{
std::unique_lock lock(m_json_mutex);
simdjson::dom::element doc = m_json.parse(
reinterpret_cast<const uint8_t*>(resp.body.data()),
resp.body.size()
);
fill_base_json(doc, dataret);
data_fill(doc, dataret);
}
return {to_header(resp), std::move(dataret)};
}
} //namespace oro