diff --git a/CMakeLists.txt b/CMakeLists.txt index 849366d9..7673e63a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -236,8 +236,8 @@ if (ANDROID) endif() set(MAT_JSON_AS_INTERFACE ON) -CPMAddPackage("gh:geode-sdk/json#1b182dd") CPMAddPackage("gh:geode-sdk/result@1.1.0") +CPMAddPackage("gh:geode-sdk/json#8c6c325") CPMAddPackage("gh:fmtlib/fmt#10.2.1") target_compile_definitions(${PROJECT_NAME} INTERFACE MAT_JSON_DYNAMIC=1) diff --git a/loader/include/Geode/cocos/base_nodes/CCNode.h b/loader/include/Geode/cocos/base_nodes/CCNode.h index bf4f00c9..1bbf681c 100644 --- a/loader/include/Geode/cocos/base_nodes/CCNode.h +++ b/loader/include/Geode/cocos/base_nodes/CCNode.h @@ -41,7 +41,7 @@ #include #ifndef GEODE_IS_MEMBER_TEST -#include +#include #endif namespace geode { diff --git a/loader/include/Geode/loader/Hook.hpp b/loader/include/Geode/loader/Hook.hpp index 1ea4d792..6a3eb105 100644 --- a/loader/include/Geode/loader/Hook.hpp +++ b/loader/include/Geode/loader/Hook.hpp @@ -2,7 +2,7 @@ #include "../DefaultInclude.hpp" #include "../utils/general.hpp" -#include +#include #include "Tulip.hpp" #include #include diff --git a/loader/include/Geode/loader/IPC.hpp b/loader/include/Geode/loader/IPC.hpp index 015b246f..73e32d1e 100644 --- a/loader/include/Geode/loader/IPC.hpp +++ b/loader/include/Geode/loader/IPC.hpp @@ -3,7 +3,7 @@ #include "Event.hpp" #include "Loader.hpp" #include "Mod.hpp" -#include +#include namespace geode::ipc { #ifdef GEODE_IS_WINDOWS diff --git a/loader/include/Geode/loader/Loader.hpp b/loader/include/Geode/loader/Loader.hpp index 41ef428a..62c9f70b 100644 --- a/loader/include/Geode/loader/Loader.hpp +++ b/loader/include/Geode/loader/Loader.hpp @@ -8,7 +8,7 @@ #include "Types.hpp" #include -#include +#include #include #include #include diff --git a/loader/include/Geode/loader/Log.hpp b/loader/include/Geode/loader/Log.hpp index 88ad3cb5..563776ec 100644 --- a/loader/include/Geode/loader/Log.hpp +++ b/loader/include/Geode/loader/Log.hpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include // for formatting std::vector and such diff --git a/loader/include/Geode/loader/Mod.hpp b/loader/include/Geode/loader/Mod.hpp index ecec1005..aff3aa8a 100644 --- a/loader/include/Geode/loader/Mod.hpp +++ b/loader/include/Geode/loader/Mod.hpp @@ -13,7 +13,7 @@ #include "Types.hpp" #include "Loader.hpp" -#include +#include #include #include #include diff --git a/loader/include/Geode/loader/ModMetadata.hpp b/loader/include/Geode/loader/ModMetadata.hpp index cf08961c..435c4fca 100644 --- a/loader/include/Geode/loader/ModMetadata.hpp +++ b/loader/include/Geode/loader/ModMetadata.hpp @@ -4,7 +4,7 @@ #include "../utils/VersionInfo.hpp" #include "Types.hpp" -#include +#include #include namespace geode { diff --git a/loader/include/Geode/loader/Types.hpp b/loader/include/Geode/loader/Types.hpp index 5f887925..e5243a47 100644 --- a/loader/include/Geode/loader/Types.hpp +++ b/loader/include/Geode/loader/Types.hpp @@ -2,7 +2,7 @@ #include "../DefaultInclude.hpp" #include "../platform/cplatform.h" -#include +#include #include diff --git a/loader/include/Geode/utils/JsonValidation.hpp b/loader/include/Geode/utils/JsonValidation.hpp index 1776614f..b2522a83 100644 --- a/loader/include/Geode/utils/JsonValidation.hpp +++ b/loader/include/Geode/utils/JsonValidation.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include "../loader/Log.hpp" #include #include @@ -223,6 +223,13 @@ namespace geode { * @returns The key, which is a no-op value if it didn't exist */ JsonExpectedValue has(std::string_view key); + /** + * Check if this object has an optional key. Asserts that this JSON + * value is an object. If the key doesn't exist, or the value is null, returns a + * `JsonExpectValue` that does nothing + * @returns The key, which is a no-op value if it didn't exist, or was null + */ + JsonExpectedValue hasNullable(std::string_view key); /** * Check if this object has an optional key. Asserts that this JSON * value is an object. If the key doesn't exist, sets an error and diff --git a/loader/include/Geode/utils/VersionInfo.hpp b/loader/include/Geode/utils/VersionInfo.hpp index ad19692f..ed51ada9 100644 --- a/loader/include/Geode/utils/VersionInfo.hpp +++ b/loader/include/Geode/utils/VersionInfo.hpp @@ -2,7 +2,7 @@ #include "../DefaultInclude.hpp" #include -#include +#include #include #include @@ -258,9 +258,9 @@ requires std::is_same_v || std::is_same_v { static geode::Result fromJson(Value const& value) { - auto str = GEODE_UNWRAP(value.asString()); - auto version = GEODE_UNWRAP(V::parse(str).mapErr([](auto&& err) { - return geode::Err("Invalid version format: {}", err); + GEODE_UNWRAP_INTO(auto str, value.asString()); + GEODE_UNWRAP_INTO(auto version, V::parse(str).mapErr([](auto&& err) { + return fmt::format("Invalid version format: {}", err); })); return geode::Ok(version); } diff --git a/loader/include/Geode/utils/cocos.hpp b/loader/include/Geode/utils/cocos.hpp index b72c096d..e2337aef 100644 --- a/loader/include/Geode/utils/cocos.hpp +++ b/loader/include/Geode/utils/cocos.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include "casts.hpp" #include "general.hpp" #include "../DefaultInclude.hpp" diff --git a/loader/include/Geode/utils/file.hpp b/loader/include/Geode/utils/file.hpp index 2ff28895..3cc7f7c5 100644 --- a/loader/include/Geode/utils/file.hpp +++ b/loader/include/Geode/utils/file.hpp @@ -5,7 +5,7 @@ #include "../loader/Event.hpp" #include "Task.hpp" -#include +#include #include #include #include @@ -15,7 +15,7 @@ template <> struct matjson::Serialize { static geode::Result fromJson(Value const& value) { - auto str = GEODE_UNWRAP(value.asString()); + GEODE_UNWRAP_INTO(auto str, value.asString()); return geode::Ok(std::filesystem::path(str).make_preferred()); } diff --git a/loader/include/Geode/utils/general.hpp b/loader/include/Geode/utils/general.hpp index 4cca1163..78298810 100644 --- a/loader/include/Geode/utils/general.hpp +++ b/loader/include/Geode/utils/general.hpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include #include @@ -170,9 +170,8 @@ namespace geode { template<> struct matjson::Serialize { - static Value toJson(geode::ByteVector const& bytes) - { - return matjson::Array(bytes.begin(), bytes.end()); + static Value toJson(geode::ByteVector const& bytes) { + return std::vector(bytes.begin(), bytes.end()); } }; diff --git a/loader/include/Geode/utils/web.hpp b/loader/include/Geode/utils/web.hpp index 5f01a89b..ad06e247 100644 --- a/loader/include/Geode/utils/web.hpp +++ b/loader/include/Geode/utils/web.hpp @@ -1,7 +1,7 @@ #pragma once #include // another great circular dependency fix -#include +#include #include #include "Task.hpp" #include diff --git a/loader/src/loader/HookImpl.cpp b/loader/src/loader/HookImpl.cpp index 558a1756..e10adc70 100644 --- a/loader/src/loader/HookImpl.cpp +++ b/loader/src/loader/HookImpl.cpp @@ -98,7 +98,7 @@ std::string_view Hook::Impl::getDisplayName() const { } matjson::Value Hook::Impl::getRuntimeInfo() const { - auto json = matjson::Object(); + matjson::Value json; json["address"] = std::to_string(reinterpret_cast(m_address)); json["detour"] = std::to_string(reinterpret_cast(m_detour)); json["name"] = m_displayName; diff --git a/loader/src/loader/IPC.cpp b/loader/src/loader/IPC.cpp index 427efef1..30d54d3e 100644 --- a/loader/src/loader/IPC.cpp +++ b/loader/src/loader/IPC.cpp @@ -1,6 +1,6 @@ #include #include "IPC.hpp" -#include +#include #include using namespace geode::prelude; @@ -34,7 +34,7 @@ matjson::Value ipc::processRaw(void* rawHandle, std::string const& buffer) { matjson::Value reply; auto res = matjson::Value::parse(buffer); - if (error.size() > 0) { + if (!res) { log::warn("Received IPC message that isn't valid JSON: {}", res.unwrapErr()); return reply; } diff --git a/loader/src/loader/IPC.hpp b/loader/src/loader/IPC.hpp index 7ea91ff5..35fbc81f 100644 --- a/loader/src/loader/IPC.hpp +++ b/loader/src/loader/IPC.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include namespace geode::ipc { void setup(); diff --git a/loader/src/loader/LoaderImpl.hpp b/loader/src/loader/LoaderImpl.hpp index 3a340720..7af233d5 100644 --- a/loader/src/loader/LoaderImpl.hpp +++ b/loader/src/loader/LoaderImpl.hpp @@ -2,7 +2,7 @@ #include "FileWatcher.hpp" -#include +#include #include #include #include diff --git a/loader/src/loader/ModImpl.cpp b/loader/src/loader/ModImpl.cpp index ef237fe7..cc345252 100644 --- a/loader/src/loader/ModImpl.cpp +++ b/loader/src/loader/ModImpl.cpp @@ -190,15 +190,12 @@ Result<> Mod::Impl::loadData() { auto savedPath = m_saveDirPath / "saved.json"; if (std::filesystem::exists(savedPath)) { GEODE_UNWRAP_INTO(auto data, utils::file::readString(savedPath)); - std::string error; - auto res = matjson::parse(data, error); - if (error.size() > 0) { - return Err("Unable to parse saved values: " + error); - } - m_saved = res.value(); - if (!m_saved.is_object()) { + m_saved = GEODE_UNWRAP(matjson::parse(data).mapErr([](auto&& err) { + return fmt::format("Unable to parse saved values: {}", err); + })); + if (!m_saved.isObject()) { log::warn("saved.json was somehow not an object, forcing it to one"); - m_saved = matjson::Object(); + m_saved = matjson::Value::object(); } } @@ -218,11 +215,15 @@ Result<> Mod::Impl::saveData() { // saveData is expected to be synchronous, and always called from GD thread ModStateEvent(m_self, ModEventType::DataSaved).post(); - auto res = utils::file::writeString(m_saveDirPath / "settings.json", json.dump()); + auto res = json.dump().andThen([&](auto const& str) { + return utils::file::writeString(m_saveDirPath / "settings.json", str); + }); if (!res) { log::error("Unable to save settings: {}", res.unwrapErr()); } - auto res2 = utils::file::writeString(m_saveDirPath / "saved.json", m_saved.dump()); + auto res2 = m_saved.dump().andThen([&](auto const& str) { + return utils::file::writeString(m_saveDirPath / "saved.json", str); + }); if (!res2) { log::error("Unable to save values: {}", res2.unwrapErr()); } @@ -387,7 +388,7 @@ Result<> Mod::Impl::uninstall(bool deleteSaveData) { ModRequestedAction::Uninstall; // Make loader forget the mod should be disabled - Mod::get()->getSaveContainer().try_erase("should-load-" + m_metadata.getID()); + Mod::get()->getSaveContainer().erase("should-load-" + m_metadata.getID()); std::error_code ec; std::filesystem::remove(m_metadata.getPath(), ec); @@ -674,14 +675,14 @@ std::string_view Mod::Impl::expandSpriteName(std::string_view name) { ModJson Mod::Impl::getRuntimeInfo() const { auto json = m_metadata.toJSON(); - auto obj = matjson::Object(); - obj["hooks"] = matjson::Array(); + auto obj = matjson::Value::object(); + obj["hooks"] = matjson::Value::array(); for (auto hook : m_hooks) { - obj["hooks"].as_array().push_back(ModJson(hook->getRuntimeInfo())); + obj["hooks"].push(ModJson(hook->getRuntimeInfo())); } - obj["patches"] = matjson::Array(); + obj["patches"] = matjson::Value::array(); for (auto patch : m_patches) { - obj["patches"].as_array().push_back(ModJson(patch->getRuntimeInfo())); + obj["patches"].push(ModJson(patch->getRuntimeInfo())); } obj["loaded"] = m_enabled; obj["temp-dir"] = this->getTempDir(); @@ -722,12 +723,9 @@ std::vector Mod::Impl::getProblems() const { } static Result getModImplInfo() { - std::string error; - auto res = matjson::parse(about::getLoaderModJson(), error); - if (error.size() > 0) { - return Err("Unable to parse mod.json: " + error); - } - matjson::Value json = res.value(); + auto json = GEODE_UNWRAP(matjson::parse(about::getLoaderModJson()).mapErr([](auto&& err) { + return fmt::format("Unable to parse mod.json: {}", err); + })); GEODE_UNWRAP_INTO(auto info, ModMetadata::create(json)); return Ok(info); diff --git a/loader/src/loader/ModImpl.hpp b/loader/src/loader/ModImpl.hpp index 5c64be24..436dece4 100644 --- a/loader/src/loader/ModImpl.hpp +++ b/loader/src/loader/ModImpl.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include "ModPatch.hpp" #include #include diff --git a/loader/src/loader/ModMetadataImpl.cpp b/loader/src/loader/ModMetadataImpl.cpp index 6dd60bf2..8c048b8d 100644 --- a/loader/src/loader/ModMetadataImpl.cpp +++ b/loader/src/loader/ModMetadataImpl.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include @@ -113,14 +113,14 @@ Result ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs auto checkerRoot = fmt::format( "[{}/v0.0.0/mod.json]", - rawJson.contains("id") ? rawJson["id"].as_string() : "unknown.mod" + rawJson.contains("id") ? GEODE_UNWRAP(rawJson["id"].asString()) : "unknown.mod" ); // JsonChecker did it this way too try { checkerRoot = fmt::format( "[{}/{}/mod.json]", - rawJson.contains("id") ? rawJson["id"].as_string() : "unknown.mod", - rawJson.contains("version") ? rawJson["version"].as().toVString() : "v0.0.0" + rawJson.contains("id") ? GEODE_UNWRAP(rawJson["id"].asString()) : "unknown.mod", + rawJson.contains("version") ? GEODE_UNWRAP(rawJson["version"].as()).toVString() : "v0.0.0" ); } catch (...) { } @@ -304,10 +304,10 @@ Result ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs Result ModMetadata::Impl::create(ModJson const& json) { // Check mod.json target version auto schema = about::getLoaderVersion(); - if (json.contains("geode") && json["geode"].is_string()) { + if (json.contains("geode") && json["geode"].isString()) { GEODE_UNWRAP_INTO( schema, - VersionInfo::parse(json["geode"].as_string()).mapErr( + VersionInfo::parse(GEODE_UNWRAP(json["geode"].asString())).mapErr( [](auto const& err) { return fmt::format("[mod.json] has invalid target loader version: {}", err); } @@ -355,13 +355,9 @@ Result ModMetadata::Impl::create(ModJson const& json) { Result ModMetadata::Impl::createFromFile(std::filesystem::path const& path) { GEODE_UNWRAP_INTO(auto read, utils::file::readString(path)); - std::string error; - auto res = matjson::parse(read, error); - if (error.size() > 0) { - return Err(std::string("Unable to parse mod.json: ") + error); - } - - GEODE_UNWRAP_INTO(auto info, ModMetadata::create(res.value())); + GEODE_UNWRAP_INTO(auto info, ModMetadata::create(GEODE_UNWRAP(matjson::parse(read).mapErr([&](auto const& err) { + return fmt::format("Unable to parse mod.json: {}", err); + })))); auto impl = info.m_impl.get(); @@ -390,18 +386,13 @@ Result ModMetadata::Impl::createFromGeodeZip(file::Unzip& unzip) { }) ); - std::string error; - auto res = matjson::parse(std::string(jsonData.begin(), jsonData.end()), error); - if (error.size() > 0) { - return Err(std::string("Unable to parse mod.json: ") + error); - } - ModJson json = res.value(); + ModJson json = GEODE_UNWRAP(matjson::parse(std::string(jsonData.begin(), jsonData.end())).mapErr([](auto const& err) { + return fmt::format("Unable to parse mod.json: {}", err); + })); - auto res2 = ModMetadata::create(json); - if (!res2) { - return Err("\"" + unzip.getPath().string() + "\" - " + res2.unwrapErr()); - } - auto info = res2.unwrap(); + auto info = GEODE_UNWRAP(ModMetadata::create(json).mapErr([&](auto const& err) { + return fmt::format("\"{}\" - {}", unzip.getPath().string(), err); + })); auto impl = info.m_impl.get(); impl->m_path = unzip.getPath(); diff --git a/loader/src/loader/ModSettingsManager.cpp b/loader/src/loader/ModSettingsManager.cpp index 494a26da..1ab4fb9e 100644 --- a/loader/src/loader/ModSettingsManager.cpp +++ b/loader/src/loader/ModSettingsManager.cpp @@ -124,16 +124,11 @@ public: // Store the value in an intermediary so if `save` fails the existing // value loaded from disk isn't overwritten matjson::Value value; - try { - if (sett.v3->save(value)) { - this->savedata[key] = value; - } - else { - log::error("Unable to save setting '{}' for mod {}", key, this->modID); - } + if (sett.v3->save(value)) { + this->savedata[key] = value; } - catch(matjson::JsonException const& e) { - log::error("Unable to save setting '{}' for mod {} (JSON exception): {}", key, this->modID, e.what()); + else { + log::error("Unable to save setting '{}' for mod {}", key, this->modID); } } } @@ -209,11 +204,11 @@ Result<> ModSettingsManager::registerCustomSettingType(std::string_view type, Se } Result<> ModSettingsManager::load(matjson::Value const& json) { - if (json.is_object()) { + if (json.isObject()) { // Save this so when custom settings are registered they can load their // values properly - m_impl->savedata = json.as_object(); - for (auto const& [key, _] : json.as_object()) { + m_impl->savedata = json; + for (auto const& [key, _] : json) { m_impl->loadSettingValueFromSave(key); } } diff --git a/loader/src/loader/PatchImpl.cpp b/loader/src/loader/PatchImpl.cpp index 4f30a73e..c483613f 100644 --- a/loader/src/loader/PatchImpl.cpp +++ b/loader/src/loader/PatchImpl.cpp @@ -104,7 +104,7 @@ uintptr_t Patch::Impl::getAddress() const { } matjson::Value Patch::Impl::getRuntimeInfo() const { - auto json = matjson::Object(); + auto json = matjson::Value::object(); json["address"] = std::to_string(reinterpret_cast(m_address)); json["original"] = m_original; json["patch"] = m_patch; diff --git a/loader/src/loader/updater.hpp b/loader/src/loader/updater.hpp index 07a96b7f..31641b93 100644 --- a/loader/src/loader/updater.hpp +++ b/loader/src/loader/updater.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include namespace geode::updater { diff --git a/loader/src/platform/windows/IPC.cpp b/loader/src/platform/windows/IPC.cpp index 7c2a1514..42ad4cb7 100644 --- a/loader/src/platform/windows/IPC.cpp +++ b/loader/src/platform/windows/IPC.cpp @@ -21,7 +21,11 @@ void ipcPipeThread(HANDLE pipe) { if (ReadFile(pipe, buffer, sizeof(buffer) - 1, &read, nullptr)) { buffer[read] = '\0'; - std::string reply = ipc::processRaw((void*)pipe, buffer).dump(); + auto res = ipc::processRaw((void*)pipe, buffer).dump(); + if (!res) { + log::warn("Failed to process IPC message: {}", res.unwrapErr()); + } + std::string reply = res.unwrapOr(""); DWORD written; WriteFile(pipe, reply.c_str(), reply.size(), &written, nullptr); diff --git a/loader/src/server/Server.cpp b/loader/src/server/Server.cpp index ed53871f..49867ffd 100644 --- a/loader/src/server/Server.cpp +++ b/loader/src/server/Server.cpp @@ -172,24 +172,23 @@ static Result parseServerPayload(web::WebResponse c return Err(ServerError(response.code(), "Response was not valid JSON: {}", asJson.unwrapErr())); } auto json = std::move(asJson).unwrap(); - if (!json.is_object()) { + if (!json.isObject()) { return Err(ServerError(response.code(), "Expected object, got {}", jsonTypeToString(json.type()))); } - auto obj = json.as_object(); - if (!obj.contains("payload")) { - return Err(ServerError(response.code(), "Object does not contain \"payload\" key - got {}", json.dump())); + if (!json.contains("payload")) { + return Err(ServerError(response.code(), "Object does not contain \"payload\" key - got {}", json.dump().unwrapOr("?"))); } - return Ok(obj["payload"]); + return Ok(json["payload"]); } static ServerError parseServerError(web::WebResponse const& error) { // The server should return errors as `{ "error": "...", "payload": "" }` if (auto asJson = error.json()) { auto json = asJson.unwrap(); - if (json.is_object() && json.contains("error")) { + if (json.isObject() && json.contains("error") && json["error"].isString()) { return ServerError( error.code(), - "{}", json.get("error") + "{}", json["error"].asString().unwrapOr("Unknown (no error message)") ); } else { @@ -267,8 +266,8 @@ Result ServerModVersion::parse(matjson::Value const& raw) { // Verify target GD version auto gd_obj = root.needs("gd"); std::string gd = "0.000"; - if (gd_obj.has(GEODE_PLATFORM_SHORT_IDENTIFIER)) { - gd = gd_obj.has(GEODE_PLATFORM_SHORT_IDENTIFIER). get(); + if (gd_obj.hasNullable(GEODE_PLATFORM_SHORT_IDENTIFIER)) { + gd = gd_obj.hasNullable(GEODE_PLATFORM_SHORT_IDENTIFIER). get(); } if (gd != "*") { @@ -288,11 +287,11 @@ Result ServerModVersion::parse(matjson::Value const& raw) { res.metadata.setIsAPI(root.needs("api").get()); std::vector dependencies {}; - for (auto& obj : root.has("dependencies").items()) { + for (auto& obj : root.hasNullable("dependencies").items()) { // todo: this should probably be generalized to use the same function as mod.json - bool onThisPlatform = !obj.has("platforms"); - for (auto& plat : obj.has("platforms").items()) { + bool onThisPlatform = !obj.hasNullable("platforms"); + for (auto& plat : obj.hasNullable("platforms").items()) { if (PlatformID::coveredBy(plat.get(), GEODE_PLATFORM_TARGET)) { onThisPlatform = true; } @@ -304,7 +303,7 @@ Result ServerModVersion::parse(matjson::Value const& raw) { ModMetadata::Dependency dependency; obj.needs("mod_id").mustBe("a valid id", &ModMetadata::validateID).into(dependency.id); obj.needs("version").into(dependency.version); - obj.has("importance").into(dependency.importance); + obj.hasNullable("importance").into(dependency.importance); // Check if this dependency is installed, and if so assign the `mod` member to mark that auto mod = Loader::get()->getInstalledMod(dependency.id); @@ -317,9 +316,9 @@ Result ServerModVersion::parse(matjson::Value const& raw) { res.metadata.setDependencies(dependencies); std::vector incompatibilities {}; - for (auto& obj : root.has("incompatibilities").items()) { + for (auto& obj : root.hasNullable("incompatibilities").items()) { ModMetadata::Incompatibility incompatibility; - obj.has("importance").into(incompatibility.importance); + obj.hasNullable("importance").into(incompatibility.importance); auto modIdValue = obj.needs("mod_id"); @@ -362,8 +361,8 @@ Result ServerModUpdate::parse(matjson::Value const& raw) { root.needs("id").into(res.id); root.needs("version").into(res.version); - if (root.has("replacement")) { - GEODE_UNWRAP_INTO(res.replacement, ServerModReplacement::parse(root.has("replacement").json())); + if (root.hasNullable("replacement")) { + GEODE_UNWRAP_INTO(res.replacement, ServerModReplacement::parse(root.hasNullable("replacement").json())); } return root.ok(res); @@ -400,9 +399,9 @@ Result ServerModMetadata::parse(matjson::Value const& raw) { root.needs("id").into(res.id); root.needs("featured").into(res.featured); root.needs("download_count").into(res.downloadCount); - root.has("about").into(res.about); - root.has("changelog").into(res.changelog); - root.has("repository").into(res.repository); + root.hasNullable("about").into(res.about); + root.hasNullable("changelog").into(res.changelog); + root.hasNullable("repository").into(res.repository); if (root.has("created_at")) { GEODE_UNWRAP_INTO(res.createdAt, ServerDateTime::parse(root.has("created_at").get())); } @@ -439,7 +438,7 @@ Result ServerModMetadata::parse(matjson::Value const& raw) { return Err("Mod '{}' has no (valid) versions", res.id); } - for (auto& item : root.has("tags").items()) { + for (auto& item : root.hasNullable("tags").items()) { res.tags.insert(item.get()); } @@ -705,16 +704,16 @@ ServerRequest> server::getTags(bool useCache) { return Err(payload.unwrapErr()); } matjson::Value json = payload.unwrap(); - if (!json.is_array()) { + if (!json.isArray()) { return Err(ServerError(response->code(), "Expected a string array")); } std::unordered_set tags; - for (auto item : json.as_array()) { - if (!item.is_string()) { + for (auto item : json) { + if (!item.isString()) { return Err(ServerError(response->code(), "Expected a string array")); } - tags.insert(item.as_string()); + tags.insert(item.asString().unwrap()); } return Ok(tags); } diff --git a/loader/src/server/Server.hpp b/loader/src/server/Server.hpp index b801f8d7..110f4e8f 100644 --- a/loader/src/server/Server.hpp +++ b/loader/src/server/Server.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include using namespace geode::prelude; diff --git a/loader/src/utils/JsonValidation.cpp b/loader/src/utils/JsonValidation.cpp index 1f1df324..f0a936aa 100644 --- a/loader/src/utils/JsonValidation.cpp +++ b/loader/src/utils/JsonValidation.cpp @@ -168,6 +168,19 @@ JsonExpectedValue JsonExpectedValue::has(std::string_view key) { } return JsonExpectedValue(m_impl.get(), m_impl->scope[key], key); } +JsonExpectedValue JsonExpectedValue::hasNullable(std::string_view key) { + if (this->hasError()) { + return JsonExpectedValue(); + } + if (!this->assertIs(matjson::Type::Object)) { + return JsonExpectedValue(); + } + m_impl->knownKeys.insert(std::string(key)); + if (!m_impl->scope.contains(key) || m_impl->scope[key].isNull()) { + return JsonExpectedValue(); + } + return JsonExpectedValue(m_impl.get(), m_impl->scope[key], key); +} JsonExpectedValue JsonExpectedValue::needs(std::string_view key) { if (this->hasError()) { return JsonExpectedValue(); @@ -190,7 +203,7 @@ std::vector> JsonExpectedValue::proper return std::vector>(); } std::vector> res; - for (auto& [k, v] : m_impl->scope.as_object()) { + for (auto& [k, v] : m_impl->scope) { res.push_back(std::make_pair(k, JsonExpectedValue(m_impl.get(), v, k))); } return res; @@ -211,7 +224,7 @@ size_t JsonExpectedValue::length() { if (!this->assertIs(matjson::Type::Array)) { return 0; } - return m_impl->scope.as_array().size(); + return m_impl->scope.size(); } JsonExpectedValue JsonExpectedValue::at(size_t index) { if (this->hasError()) { @@ -220,15 +233,14 @@ JsonExpectedValue JsonExpectedValue::at(size_t index) { if (!this->assertIs(matjson::Type::Array)) { return JsonExpectedValue(); } - auto& arr = m_impl->scope.as_array(); - if (arr.size() <= index) { + if (index >= m_impl->scope.size()) { this->setError( "array expected to have at least size {}, but its size was only {}", - index + 1, arr.size() + index + 1, m_impl->scope.size() ); return JsonExpectedValue(); } - return JsonExpectedValue(m_impl.get(), arr.at(index), std::to_string(index)); + return JsonExpectedValue(m_impl.get(), m_impl->scope[index], std::to_string(index)); } std::vector JsonExpectedValue::items() { if (this->hasError()) { @@ -239,7 +251,7 @@ std::vector JsonExpectedValue::items() { } std::vector res; size_t i = 0; - for (auto& v : m_impl->scope.as_array()) { + for (auto& v : m_impl->scope) { res.push_back(JsonExpectedValue(m_impl.get(), v, std::to_string(i++))); } return res; diff --git a/loader/src/utils/VersionInfo.cpp b/loader/src/utils/VersionInfo.cpp index 0c5c9658..069f674b 100644 --- a/loader/src/utils/VersionInfo.cpp +++ b/loader/src/utils/VersionInfo.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include using namespace geode::prelude; diff --git a/loader/src/utils/cocos.cpp b/loader/src/utils/cocos.cpp index 3a8fa960..cfce8735 100644 --- a/loader/src/utils/cocos.cpp +++ b/loader/src/utils/cocos.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include @@ -32,6 +32,7 @@ Result matjson::Serialize::fromJson( } return Ok(res.unwrap()); } + return Err("Expected color to be array, object or hex string"); } matjson::Value matjson::Serialize::toJson(cocos2d::ccColor3B const& value) { return matjson::makeObject({ @@ -68,6 +69,7 @@ Result matjson::Serialize::fromJson( } return Ok(res.unwrap()); } + return Err("Expected color to be array, object or hex string"); } matjson::Value matjson::Serialize::toJson(cocos2d::ccColor4B const& value) { diff --git a/loader/src/utils/file.cpp b/loader/src/utils/file.cpp index 5cba9d46..22037899 100644 --- a/loader/src/utils/file.cpp +++ b/loader/src/utils/file.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include #include @@ -53,14 +53,10 @@ Result utils::file::readString(std::filesystem::path const& path) { } Result utils::file::readJson(std::filesystem::path const& path) { - auto str = utils::file::readString(path); - if (!str) - return Err(str.unwrapErr()); - std::string error; - auto res = matjson::parse(str.unwrap(), error); - if (error.size()) - return Err("Unable to parse JSON: " + error); - return Ok(res.value()); + auto str = GEODE_UNWRAP(utils::file::readString(path)); + return matjson::parse(str).mapErr([&](auto const& err) { + return fmt::format("Unable to parse JSON: {}", err); + }); } Result utils::file::readBinary(std::filesystem::path const& path) { diff --git a/loader/src/utils/web.cpp b/loader/src/utils/web.cpp index 0cf732ec..4f872b36 100644 --- a/loader/src/utils/web.cpp +++ b/loader/src/utils/web.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #define CURL_STATICLIB #include @@ -130,12 +130,9 @@ Result WebResponse::string() const { } Result WebResponse::json() const { GEODE_UNWRAP_INTO(auto value, this->string()); - std::string error; - auto res = matjson::parse(value, error); - if (error.size() > 0) { - return Err("Error parsing JSON: " + error); - } - return Ok(res.value()); + return matjson::parse(value).mapErr([&](auto const& err) { + return fmt::format("Error parsing JSON: {}", err); + }); } ByteVector WebResponse::data() const { return m_impl->m_data; @@ -595,7 +592,8 @@ WebRequest& WebRequest::bodyString(std::string_view str) { } WebRequest& WebRequest::bodyJSON(matjson::Value const& json) { this->header("Content-Type", "application/json"); - std::string str = json.dump(matjson::NO_INDENTATION); + // TODO: mat + std::string str = json.dump(matjson::NO_INDENTATION).unwrapOr(""); m_impl->m_body = ByteVector { str.begin(), str.end() }; return *this; }