Skip to content

Instantly share code, notes, and snippets.

@sm9cc

sm9cc/Json.cpp Secret

Last active September 29, 2023 13:11
Show Gist options
  • Save sm9cc/0eaacc508205ff22c78b13b5dcf80553 to your computer and use it in GitHub Desktop.
Save sm9cc/0eaacc508205ff22c78b13b5dcf80553 to your computer and use it in GitHub Desktop.
/*
* GNU Lesser General Public License
* Version 3, 29 June 2007
*
* Copyright (C) 2023 Michael Bolden Jnr
*
* Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not
* allowed.
*
* This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU
* General Public License.
*
* ---------------------------------------------------------------------------------------------------------------------
*
* This software is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This software 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 Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along with this program. If not, see
* <http://www.gnu.org/licenses/>.
*
*/
#include "libSM9/Json.hpp"
#include "libSM9/Time.hpp"
#include <iostream>
namespace SM9 {
int JsonValue::getAnInt(const std::initializer_list<const char*>& keys, int fallback) const {
for (const auto& key : keys) {
if (value_.HasMember(key) && value_[key].IsInt()) {
return value_[key].GetInt();
}
}
return fallback;
}
double JsonValue::getADouble(const std::initializer_list<const char*>& keys, double fallback) const {
for (const auto& key : keys) {
if (value_.HasMember(key) && value_[key].IsDouble()) {
return value_[key].GetDouble();
}
}
return fallback;
}
std::chrono::system_clock::time_point JsonValue::getATimePoint(
const std::initializer_list<const char*>& keys, const std::chrono::system_clock::time_point& fallback) const {
for (const auto& key : keys) {
if (value_.HasMember(key)) {
// Issue: Detecting the correct JSON value type (int or string) for time can be unreliable.
// Workaround: Convert the value to an integer as a first attempt, then to a string if needed.
std::string value = std::to_string(value_[key].GetInt());
if (value.empty()) {
value = value_[key].GetString();
}
return Time::toPoint(value);
}
}
return fallback;
}
std::string JsonValue::getAString(const std::initializer_list<const char*>& keys, const std::string& fallback) const {
if (!value_.IsObject()) {
return fallback;
}
for (const auto& key : keys) {
if (value_.HasMember(key) && value_[key].IsString()) {
return value_[key].GetString();
}
}
return fallback;
}
std::vector<std::string> JsonValue::getAnArrayOfStrings(
const std::initializer_list<const char*>& keys, const std::vector<std::string>& fallback) const {
for (const auto& key : keys) {
if (value_.HasMember(key) && value_[key].IsArray()) {
const auto& jsonArray = value_[key].GetArray();
std::vector<std::string> result;
result.reserve(jsonArray.Size());
for (const auto& value : jsonArray) {
if (value.IsString()) {
result.push_back(value.GetString());
}
}
return result;
}
}
return fallback;
}
} // namespace SM9
/*
* GNU Lesser General Public License
* Version 3, 29 June 2007
*
* Copyright (C) 2023 Michael Bolden Jnr
*
* Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not
* allowed.
*
* This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU
* General Public License.
*
* ---------------------------------------------------------------------------------------------------------------------
*
* This software is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This software 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 Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along with this program. If not, see
* <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#include "libSM9/Arch/Package.hpp"
#include <chrono>
#include <rapidjson/document.h>
#include <string>
#include <vector>
namespace SM9 {
class JsonValue {
public:
explicit JsonValue(const rapidjson::Value& value) : value_(value) { }
int getAnInt(const std::initializer_list<const char*>& keys, int fallback = 0) const;
double getADouble(const std::initializer_list<const char*>& keys, double fallback = 0.0) const;
std::string getAString(const std::initializer_list<const char*>& keys, const std::string& fallback = "") const;
std::vector<std::string> getAnArrayOfStrings(
const std::initializer_list<const char*>& keys, const std::vector<std::string>& fallback = {}) const;
std::chrono::system_clock::time_point getATimePoint(const std::initializer_list<const char*>& keys,
const std::chrono::system_clock::time_point& fallback = std::chrono::system_clock::time_point{}) const;
private:
const rapidjson::Value& value_;
};
} // namespace SM9
/*
* GNU Lesser General Public License
* Version 3, 29 June 2007
*
* Copyright (C) 2023 Michael Bolden Jnr
*
* Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not
* allowed.
*
* This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU
* General Public License.
*
* ---------------------------------------------------------------------------------------------------------------------
*
* This software is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This software 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 Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along with this program. If not, see
* <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "libSM9/Arch/Package.hpp"
#include "libSM9/Formatting/Formatting.hpp"
#include <cpr/api.h>
#include <cpr/response.h>
#include <glaze/core/common.hpp>
#include <glaze/json/read.hpp>
#include <optional>
#include <vector>
namespace glz {
template <class T> struct meta;
}
struct ResultMeta {
std::optional<SM9::Arch::Package> getPackage() const;
std::optional<std::string> base{};
std::optional<std::string> name{};
std::optional<std::string> version{};
std::optional<std::string> release{};
std::optional<long> epoch{};
std::optional<std::string> description{};
std::optional<std::string> architecture{};
std::optional<std::string> url{};
std::optional<std::vector<std::string>> licenses{};
std::optional<std::vector<std::string>> groups{};
std::optional<std::vector<std::string>> dependencies{};
std::optional<std::vector<std::string>> makeDependencies{};
std::optional<std::vector<std::string>> checkDependencies{};
std::optional<std::vector<std::string>> optDependencies{};
std::optional<std::vector<std::string>> provides{};
std::optional<std::vector<std::string>> conflicts{};
std::optional<std::vector<std::string>> replaces{};
std::optional<std::string> repository{};
std::optional<std::string> filename{};
std::optional<int> compressedSize{};
std::optional<int> installedSize{};
std::optional<std::string> buildDate{};
std::optional<std::variant<std::string, int>> lastUpdated{};
std::optional<std::variant<std::string, int>> outOfDateSince{};
std::optional<std::variant<std::string, std::vector<std::string>>> maintainers{};
std::optional<std::string> packager{};
std::optional<int> firstSubmitted{};
std::optional<int> ID{};
std::optional<std::vector<std::string>> keywords{};
std::optional<int> voteCount{};
std::optional<int> baseID{};
std::optional<double> popularity{};
std::optional<std::string> urlPath{};
};
struct QueryMeta {
int resultCount{0};
bool hasResults{false};
std::optional<std::vector<ResultMeta>> results{};
std::optional<int> numPages{1};
std::optional<int> page{1};
static QueryMeta fetch(const std::string& apiUrl);
};
// clang-format off
template <> struct glz::meta<ResultMeta> {
using T = ResultMeta;
static constexpr auto value = ::glz::object(
// Repository // AUR
"pkgbase", &T::base, "PackageBase", &T::base,
"pkgname", &T::name, "Name", &T::name,
"pkgver", &T::version, "Version", &T::version,
"pkgrel", &T::release,
"epoch", &T::epoch,
"pkgdesc", &T::description, "Description", &T::description,
"arch", &T::architecture,
"url", &T::url, "URL", &T::url,
"licenses", &T::licenses, "License", &T::licenses,
"groups", &T::groups,
"depends", &T::dependencies, "Depends", &T::dependencies,
"makedepends", &T::makeDependencies, "MakeDepends", &T::makeDependencies,
"checkdepends", &T::checkDependencies, "CheckDepends", &T::checkDependencies,
"optdepends", &T::optDependencies, "OptDepends", &T::optDependencies,
"provides", &T::provides, "Provides", &T::provides,
"conflicts", &T::conflicts, "Conflicts", &T::conflicts,
"replaces", &T::replaces, "Replaces", &T::replaces,
"repo", &T::repository,
"filename", &T::filename,
"compressed_size", &T::compressedSize,
"installed_size", &T::installedSize,
"build_date", &T::buildDate,
"last_update", &T::lastUpdated, "LastModified", &T::lastUpdated,
"flag_date", &T::outOfDateSince, "OutOfDate", &T::outOfDateSince,
"maintainers", &T::maintainers, "Maintainer", &T::maintainers,
"CoMaintainers", &T::maintainers,
"packager", &T::packager, "Submitter", &T::packager,
"FirstSubmitted", &T::firstSubmitted,
"ID", &T::ID,
"Keywords", &T::keywords,
"NumVotes", &T::voteCount,
"PackageBaseID", &T::baseID,
"Popularity", &T::popularity,
"URLPath", &T::urlPath
);
};
template <> struct glz::meta<QueryMeta> {
using T = QueryMeta;
static constexpr auto value = ::glz::object(
"resultcount", skip{},
"results", &T::results,
"type", skip{},
"version", skip{},
"limit", skip{},
"valid", skip{},
"num_pages", &T::numPages,
"page", &T::page
);
};
// clang-format on
std::optional<SM9::Arch::Package> ResultMeta::getPackage() const {
SM9::Arch::Package package;
if (!name.has_value() || name.value().empty() || !base.has_value() || base.value().empty()) {
return std::nullopt; // Invalid package
}
package.base = base;
package.name = name;
if (version.has_value()) {
const auto hyphenPos = version->find('-');
if (hyphenPos != std::string::npos) {
package.version = version->substr(0, hyphenPos);
package.release = version->substr(hyphenPos + 1);
} else {
package.version = version;
package.release = release;
}
}
package.release = release;
package.epoch = epoch;
package.description = description;
package.architecture = architecture;
package.url = url;
package.licenses = licenses;
package.groups = groups;
package.dependencies = dependencies;
package.makeDependencies = makeDependencies;
package.checkDependencies = checkDependencies;
package.optDependencies = optDependencies;
package.provides = provides;
package.conflicts = conflicts;
package.replaces = replaces;
package.repository = repository;
package.filename = filename;
package.compressedSize = compressedSize;
package.installedSize = installedSize;
if (buildDate.has_value()) {
package.buildDate = SM9::Formatting::Time::toPoint(buildDate.value());
}
if (lastUpdated.has_value()) {
if (std::holds_alternative<std::string>(*lastUpdated)) {
package.lastUpdated = SM9::Formatting::Time::toPoint(std::get<std::string>(*lastUpdated));
} else if (std::holds_alternative<int>(*lastUpdated)) {
package.lastUpdated = SM9::Formatting::Time::toPoint(std::get<int>(*lastUpdated));
}
}
if (outOfDateSince.has_value()) {
if (std::holds_alternative<std::string>(*outOfDateSince)) {
package.outOfDateSince = SM9::Formatting::Time::toPoint(std::get<std::string>(*outOfDateSince));
} else if (std::holds_alternative<int>(*outOfDateSince)) {
package.outOfDateSince = SM9::Formatting::Time::toPoint(std::get<int>(*outOfDateSince));
}
}
if (maintainers.has_value()) {
if (std::holds_alternative<std::vector<std::string>>(*maintainers)) {
package.maintainers = std::get<std::vector<std::string>>(*maintainers);
}
if (std::holds_alternative<std::string>(*maintainers)) {
const auto& maintainer = std::get<std::string>(*maintainers);
if (!maintainer.empty()) {
if (!package.maintainers.has_value()) {
package.maintainers = std::vector<std::string>{};
}
package.maintainers->push_back(maintainer);
}
}
}
package.packager = packager;
if (firstSubmitted.has_value()) {
package.firstSubmitted = SM9::Formatting::Time::toPoint(firstSubmitted.value());
}
package.ID = ID;
package.keywords = keywords;
package.voteCount = voteCount;
package.baseID = baseID;
package.popularity = popularity;
package.urlPath = urlPath;
return package;
}
QueryMeta QueryMeta::fetch(const std::string& apiUrl) {
cpr::Response response = cpr::Get(cpr::Url{apiUrl});
if (response.status_code != 200) {
throw std::runtime_error("Invalid response (HTTP Status: " + std::to_string(response.status_code) + ")");
}
try {
const auto& responseText = response.text;
QueryMeta queryMeta;
const auto err = glz::read_json(queryMeta, responseText.c_str());
if (err) {
throw std::runtime_error("Failed to read JSON: " + format_error(err, responseText));
}
if (queryMeta.results.has_value()) {
queryMeta.resultCount = queryMeta.results->size();
queryMeta.hasResults = queryMeta.resultCount > 0;
}
return queryMeta;
} catch (const std::exception& e) {
throw std::runtime_error("Failed to parse the JSON response: " + std::string(e.what()) + "\n" + apiUrl);
}
}
Package Package::fromJsonResult(const rapidjson::Value& result) {
Package package{};
const SM9::JsonValue value(result);
package.base = value.getAString({"PackageBase", "pkgbase"});
package.name = value.getAString({"Name", "pkgname"});
const auto versionString = value.getAString({"Version", "pkgver"});
const auto hyphenPos = versionString.find('-');
if (hyphenPos != std::string::npos) {
package.version = versionString.substr(0, hyphenPos);
package.release = versionString.substr(hyphenPos + 1);
} else {
package.version = versionString;
package.release = value.getAString({"pkgrel"});
}
package.epoch = value.getAnInt({"epoch"});
package.description = value.getAString({"Description", "pkgdesc"});
package.architecture
= value.getAString({"arch"}); // TODO: Retrieve this in another way (ie parse .SRCINFO or PKGBUILD)
package.url = value.getAString({"URL", "url"});
package.licenses = value.getAnArrayOfStrings({"License", "licenses"});
package.groups = value.getAnArrayOfStrings({"Groups", "groups"});
package.dependencies = value.getAnArrayOfStrings({"Depends", "depends"});
package.makeDependencies = value.getAnArrayOfStrings({"MakeDepends", "makedepends"});
package.checkDependencies = value.getAnArrayOfStrings({"CheckDepends", "checkdepends"});
package.optDependencies = value.getAnArrayOfStrings({"OptDepends", "optdepends"});
package.provides = value.getAnArrayOfStrings({"Provides", "provides"});
package.conflicts = value.getAnArrayOfStrings({"Conflicts", "conflicts"});
package.replaces = value.getAnArrayOfStrings({"Replaces", "replaces"});
package.repository = value.getAString({"repo", "repository"}, "AUR");
package.filename = value.getAString({"filename"});
package.buildOptions = value.getAString(
{"Options", "options"}); // TODO: Retrieve this in another way (ie parse .SRCINFO or PKGBUILD)
package.compressedSize = value.getAnInt({"compressed_size"});
package.installedSize = value.getAnInt({"installed_size"});
package.buildDate = value.getATimePoint({"build_date"});
package.lastUpdated = value.getATimePoint({"last_update"});
package.outOfDateSince = value.getATimePoint({"OutOfDate", "flag_date"});
package.maintainers = value.getAnArrayOfStrings({"CoMaintainers", "maintainers"});
const auto maintainer = value.getAString({"Maintainer"});
if (!maintainer.empty()) {
package.maintainers.push_back(maintainer);
}
package.packager = value.getAString({"Submitter", "packager"});
package.firstSubmitted = value.getATimePoint({"FirstSubmitted"});
package.packageID = value.getAnInt({"ID"});
package.keywords = value.getAnArrayOfStrings({"Keywords"});
package.voteCount = value.getAnInt({"NumVotes"});
package.basePackageID = value.getAnInt({"PackageBaseID"});
package.popularity = value.getADouble({"PackageBaseID"});
package.urlPath = value.getAString({"URLPath"});
return package;
}
class Package {
public:
static Package fromJsonResult(const rapidjson::Value& result);
std::string base{};
std::string name{};
std::string version{};
std::string release{};
int epoch{};
std::string description{};
std::string architecture{};
std::string url{};
std::vector<std::string> licenses{};
std::vector<std::string> groups{};
std::vector<std::string> dependencies{};
std::vector<std::string> makeDependencies{};
std::vector<std::string> checkDependencies{};
std::vector<std::string> optDependencies{};
std::vector<std::string> provides{};
std::vector<std::string> conflicts{};
std::vector<std::string> replaces{};
std::string repository{};
std::string filename{};
std::string buildOptions{};
int compressedSize{};
int installedSize{};
std::chrono::system_clock::time_point buildDate{};
std::chrono::system_clock::time_point lastUpdated{};
std::chrono::system_clock::time_point outOfDateSince{};
std::vector<std::string> maintainers{};
std::string packager{};
std::chrono::system_clock::time_point firstSubmitted{};
int packageID{};
std::vector<std::string> keywords{};
int voteCount{};
int basePackageID{};
double popularity{};
std::string urlPath{};
};
/*
* GNU Lesser General Public License
* Version 3, 29 June 2007
*
* Copyright (C) 2023 Michael Bolden Jnr
*
* Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not
* allowed.
*
* This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU
* General Public License.
*
* ---------------------------------------------------------------------------------------------------------------------
*
* This software is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This software 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 Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along with this program. If not, see
* <http://www.gnu.org/licenses/>.
*
*/
#include "libSM9/Time.hpp"
#include <iostream>
#include <regex>
#include <sstream>
#include <stdexcept>
namespace SM9 {
std::string Time::pointToString(const std::chrono::system_clock::time_point& timePoint, const std::string& format) {
return epochToString(pointToEpoch(timePoint), format);
}
int Time::pointToEpoch(const std::chrono::system_clock::time_point& timePoint) {
return std::chrono::duration_cast<std::chrono::seconds>(timePoint.time_since_epoch()).count();
}
std::string Time::epochToString(const std::time_t& epochTime, const std::string& format) {
if (epochTime == std::time_t(0)) {
return "";
}
std::stringstream ss;
ss << std::put_time(std::gmtime(&epochTime), format.c_str());
return ss.str();
}
std::string Time::epochToString(const int& epochTime, const std::string& format) {
return epochToString(static_cast<std::time_t>(epochTime), format);
}
std::chrono::system_clock::time_point Time::toPoint(const std::string& timestamp) {
const std::regex isoFormatPattern(R"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z)");
const std::regex numericFormatPattern("\\d+");
if (std::regex_match(timestamp, isoFormatPattern)) {
std::tm timeInfo = {};
std::istringstream ss(timestamp);
ss >> std::get_time(&timeInfo, "%Y-%m-%dT%H:%M:%S");
if (!ss.fail()) {
long long milliseconds = 0;
std::size_t dotPos = timestamp.find('.');
if (dotPos != std::string::npos) {
milliseconds = std::stoll(timestamp.substr(dotPos + 1));
}
auto seconds = std::chrono::seconds(std::mktime(&timeInfo));
auto millisecondsDuration = std::chrono::milliseconds(milliseconds);
return std::chrono::system_clock::time_point(seconds + millisecondsDuration);
}
} else if (std::regex_match(timestamp, numericFormatPattern)) {
return toPoint(std::stoi(timestamp));
}
throw std::invalid_argument("Invalid timestamp format: " + timestamp);
}
std::chrono::system_clock::time_point Time::toPoint(const int& epochTime) {
return std::chrono::system_clock::from_time_t(static_cast<std::time_t>(epochTime));
}
} // namespace SM9
/*
* GNU Lesser General Public License
* Version 3, 29 June 2007
*
* Copyright (C) 2023 Michael Bolden Jnr
*
* Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not
* allowed.
*
* This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU
* General Public License.
*
* ---------------------------------------------------------------------------------------------------------------------
*
* This software is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This software 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 Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along with this program. If not, see
* <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <chrono>
#include <string>
namespace SM9 {
struct Time {
static std::string pointToString(
const std::chrono::system_clock::time_point& timePoint, const std::string& format = "%d-%m-%Y %H:%M:%S");
static int pointToEpoch(const std::chrono::system_clock::time_point& timePoint);
static std::string epochToString(const std::time_t& epochTime, const std::string& format = "%d-%m-%Y %H:%M:%S");
static std::string epochToString(const int& epochTime, const std::string& format = "%d-%m-%Y %H:%M:%S");
static std::chrono::system_clock::time_point toPoint(const std::string& timestamp);
static std::chrono::system_clock::time_point toPoint(const int& epochTime);
};
} // namespace SM9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment