Something went wrong. Try again.
Monorepo for PackTools - tooling for Minecraft Modpacks!
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157// AI-authored (Claude).#include "records.hpp"
#include <algorithm>#include <cstddef>#include <optional>#include <string>#include <string_view>#include <utility>#include <vector>
namespace packutils::records {
std::string_view name_of(Mode mode) { switch (mode) { case Mode::symlink: return "symlink"; case Mode::junction: return "junction"; case Mode::copy: return "copy"; } return "copy";}
std::optional<Mode> mode_named(std::string_view name) { for (const Mode mode : {Mode::symlink, Mode::junction, Mode::copy}) { if (name_of(mode) == name) { return mode; } } return std::nullopt;}
bool less(const Link &a, const Link &b) { if (a.instance != b.instance) { return a.instance < b.instance; } return a.folder < b.folder;}
bool is_folder_name(std::string_view name) { if (name.empty() || name.size() > 255 || name == "." || name == "..") { return false; } return name.find_first_of(std::string_view("/\\:\0", 4)) == std::string_view::npos;}
namespace {
Failure invalid(std::string_view what) { return Failure::of(PU_KIND_INVALID_REQUEST, "LinksInvalid", std::string(what) + " is not a link record this build writes");}
} // namespace
Result<std::vector<Link>> parse(std::string_view bytes, std::string_view what) { auto value = json::parse(bytes); if (!value || !value->is(json::Value::Type::object)) { return std::unexpected(invalid(what)); } const json::Value *version = value->get("version"); if (version == nullptr || !version->is(json::Value::Type::number)) { return std::unexpected(invalid(what)); } const auto number = version->integer(INT64_MIN, INT64_MAX); if (!number) { return std::unexpected(invalid(what)); } if (*number > schema_version) { Failure failure = Failure::of(PU_KIND_INVALID_REQUEST, "LinksTooNew", std::string(what) + " is version " + std::to_string(*number) + ", newer than this build reads (" + std::to_string(schema_version) + " at most)"); failure.upgrade = true; return std::unexpected(std::move(failure)); } if (*number != schema_version || value->members.size() != 2) { return std::unexpected(invalid(what)); } const json::Value *list = value->get("links"); if (list == nullptr || !list->is(json::Value::Type::array)) { return std::unexpected(invalid(what)); } std::vector<Link> links; links.reserve(list->items.size()); for (const json::Value &item : list->items) { if (!item.is(json::Value::Type::object)) { return std::unexpected(invalid(what)); } auto link = from_json(item); if (!link || item.members.size() != 3 + (link->backup ? 1U : 0U) || link->instance.empty() || !is_folder_name(link->folder) || (link->backup && !is_folder_name(*link->backup))) { return std::unexpected(invalid(what)); } links.push_back(std::move(*link)); } std::ranges::sort(links, less); for (std::size_t i = 1; i < links.size(); ++i) { if (links.at(i - 1).same(links.at(i).instance, links.at(i).folder)) { return std::unexpected(invalid(what)); } } return links;}
json::Value to_json(const Link &link) { json::Value object = json::Value::object(); object.put("instance", json::Value::of_string(link.instance)); object.put("folder", json::Value::of_string(link.folder)); object.put("mode", json::Value::of_string(name_of(link.mode))); if (link.backup) { object.put("backup", json::Value::of_string(*link.backup)); } return object;}
std::optional<Link> from_json(const json::Value &value) { const auto instance = value.string_at("instance"); const auto folder = value.string_at("folder"); const auto mode_name = value.string_at("mode"); if (!instance || !folder || !mode_name) { return std::nullopt; } const auto mode = mode_named(*mode_name); if (!mode) { return std::nullopt; } Link link{.instance = std::string(*instance), .folder = std::string(*folder), .mode = *mode, .backup = std::nullopt}; if (const json::Value *backup = value.get("backup")) { if (!backup->is(json::Value::Type::string)) { return std::nullopt; } link.backup = backup->text; } return link;}
std::string encode(std::vector<Link> links) { std::ranges::sort(links, less); json::Value array = json::Value::array(); for (const Link &link : links) { array.push(to_json(link)); } json::Value root = json::Value::object(); root.put("version", json::Value::of_integer(schema_version)); root.put("links", std::move(array)); return json::canonical(root);}
} // namespace packutils::records