fix(serialization): no longer aborts if wrong type is parsed

This commit is contained in:
maelstrom 2025-04-10 18:50:36 +02:00
parent 22fcd1e55e
commit eeaaef8c88
11 changed files with 88 additions and 78 deletions

View file

@ -9,9 +9,9 @@ std::string Error::getMessage() {
}
void Error::logMessage(Logger::LogLevel logLevel) {
Logger::logf("%s", logLevel, this->message);
Logger::log(this->message, logLevel);
}
void Error::logMessageFatal() {
Logger::fatalErrorf("%s", this->message);
Logger::fatalError(this->message);
}

View file

@ -12,6 +12,6 @@ protected:
public:
std::string getMessage();
void logMessage(Logger::LogLevel logLevel = Logger::LogLevel::INFO);
void logMessage(Logger::LogLevel logLevel = Logger::LogLevel::ERROR);
void logMessageFatal();
};

View file

@ -1,9 +0,0 @@
#pragma once
#include "result.h"
struct DUMMY_VALUE;
template <typename ...E>
class fallible : public result<DUMMY_VALUE, E...> {
};

View file

@ -1,36 +0,0 @@
#include "result.h"
#include <optional>
#include <variant>
template <typename Result, typename ...E>
result<Result, E...>::result(Result value) : value(SuccessContainer { value }) {
}
template <typename Result, typename ...E>
result<Result, E...>::result(std::variant<E...> value) : value(ErrorContainer { value }) {
}
template <typename Result, typename ...E>
bool result<Result, E...>::is_success() {
return std::holds_alternative<SuccessContainer>(value);
}
template <typename Result, typename ...E>
bool result<Result, E...>::is_error() {
return std::holds_alternative<ErrorContainer>(value);
}
template <typename Result, typename ...E>
std::optional<Result> result<Result, E...>::success() {
return std::holds_alternative<SuccessContainer>(value) ? std::make_optional(std::get<SuccessContainer>(value).success) : std::nullopt;
}
template <typename Result, typename ...E>
std::optional<std::variant<E...>> result<Result, E...>::error() {
return std::holds_alternative<ErrorContainer>(value) ? std::make_optional(std::get<ErrorContainer>(value).error) : std::nullopt;
}
template <typename Result, typename ...E>
result<Result, E...>::operator std::optional<Result>() {
return this->success();
}

View file

@ -1,10 +1,14 @@
#pragma once
#include "error.h"
#include "logger.h"
#include "panic.h"
#include <optional>
#include <string>
#include <variant>
struct DUMMY_VALUE {};
template <typename Result, typename ...E>
class [[nodiscard]] result {
struct ErrorContainer {
@ -17,18 +21,39 @@ class [[nodiscard]] result {
std::variant<SuccessContainer, ErrorContainer> value;
public:
result(Result value);
result(std::variant<E...> value);
result(Result success) : value(SuccessContainer { success }) {}
result(std::variant<E...> error) : value(ErrorContainer { error }) {}
// Expects the result to be successful, otherwise panic with error message
Result expect(std::string errMsg = "Unwrapped a result with failure value");
Result expect(std::string errMsg = "Unwrapped a result with failure value") {
if (is_success())
return std::get<SuccessContainer>(value).success;
Logger::fatalError(errMsg);
panic();
}
bool is_success();
bool is_error();
bool is_success() { return std::holds_alternative<SuccessContainer>(value); }
bool is_error() { return std::holds_alternative<ErrorContainer>(value); }
std::optional<Result> success();
std::optional<std::variant<E...>> error();
std::optional<Result> success() { return is_success() ? std::get<SuccessContainer>(value).success : std::nullopt; }
std::optional<std::variant<E...>> error() { return is_error() ? std::make_optional(std::get<ErrorContainer>(value).error) : std::nullopt; }
void logError(Logger::LogLevel logLevel = Logger::LogLevel::ERROR) {
if (is_success()) return;
std::visit([&](auto&& it) {
it.logMessage(logLevel);
}, error().value());
}
// Equivalent to .success
operator std::optional<Result>();
operator std::optional<Result>() { return success(); }
operator bool() { return is_success(); }
bool operator !() { return is_error(); }
};
template <typename ...E>
class fallible : public result<DUMMY_VALUE, E...> {
public:
fallible() : result<DUMMY_VALUE, E...>(DUMMY_VALUE {}) {}
fallible(std::variant<E...> error) : result<DUMMY_VALUE, E...>(error) {}
};

View file

@ -207,11 +207,10 @@ void Instance::Serialize(pugi::xml_node parent) {
}
}
InstanceRef Instance::Deserialize(pugi::xml_node node) {
result<InstanceRef, NoSuchInstance> Instance::Deserialize(pugi::xml_node node) {
std::string className = node.attribute("class").value();
if (INSTANCE_MAP.count(className) == 0) {
Logger::fatalErrorf("Unknown type for instance: '%s'", className.c_str());
panic();
return std::variant<NoSuchInstance>(NoSuchInstance(className));
}
// This will error if an abstract instance is used in the file. Oh well, not my prob rn.
// printf("What are you? A %s sandwich\n", className.c_str());
@ -235,8 +234,12 @@ InstanceRef Instance::Deserialize(pugi::xml_node node) {
// Read children
for (pugi::xml_node childNode : node.children("Item")) {
InstanceRef child = Instance::Deserialize(childNode);
object->AddChild(child);
result<InstanceRef, NoSuchInstance> child = Instance::Deserialize(childNode);
if (child.is_error()) {
std::get<NoSuchInstance>(child.error().value()).logMessage();
continue;
}
object->AddChild(child.expect());
}
return object;

View file

@ -15,6 +15,8 @@
#include <expected.hpp>
#include <pugixml.hpp>
#include "error/error.h"
#include "error/result.h"
#include "member.h"
class Instance;
@ -38,6 +40,12 @@ struct InstanceType {
InstanceFlags flags;
};
// Errors
class NoSuchInstance : public Error {
public:
inline NoSuchInstance(std::string className) : Error("Cannot create instance of unknown type " + className) {}
};
class DescendantsIterator;
// Base class for all instances in the data model
@ -95,7 +103,7 @@ public:
// Serialization
void Serialize(pugi::xml_node parent);
static std::shared_ptr<Instance> Deserialize(pugi::xml_node node);
static result<std::shared_ptr<Instance>, NoSuchInstance> Deserialize(pugi::xml_node node);
};
typedef std::shared_ptr<Instance> InstanceRef;

View file

@ -66,7 +66,7 @@ void DataModel::DeserializeService(pugi::xml_node node) {
std::string className = node.attribute("class").value();
if (INSTANCE_MAP.count(className) == 0) {
Logger::fatalErrorf("Unknown service: '%s'", className.c_str());
panic();
return;
}
if (services.count(className) != 0) {
@ -93,8 +93,12 @@ void DataModel::DeserializeService(pugi::xml_node node) {
// Add children
for (pugi::xml_node childNode : node.children("Item")) {
InstanceRef child = Instance::Deserialize(childNode);
object->AddChild(child);
result<InstanceRef, NoSuchInstance> child = Instance::Deserialize(childNode);
if (child.is_error()) {
std::get<NoSuchInstance>(child.error().value()).logMessage();
continue;
}
object->AddChild(child.expect());
}
// We add the service to the list

View file

@ -1,17 +1,29 @@
#pragma once
#include "base.h"
#include "error/result.h"
#include "logger.h"
#include "objects/base/instance.h"
#include "objects/meta.h"
#include "panic.h"
#include <memory>
#include <variant>
class Workspace;
class DataModel;
class Service;
class NoSuchService : public Error {
public:
inline NoSuchService(std::string className) : Error("Cannot insert service of unknown type " + className) {}
};
class ServiceAlreadyExists : public Error {
public:
inline ServiceAlreadyExists(std::string className) : Error("Service " + className + " is already inserted") {}
};
// The root instance to all objects in the hierarchy
class DataModel : public Instance {
private:
@ -30,9 +42,9 @@ public:
virtual const InstanceType* GetClass() override;
// Inserts a service if it doesn't already exist
bool InsertService(std::string name) {
fallible<ServiceAlreadyExists, NoSuchService> InsertService(std::string name) {
if (services.count(name) != 0)
return false;
return fallible<ServiceAlreadyExists, NoSuchService>(ServiceAlreadyExists(name));
if (!INSTANCE_MAP[name] || (INSTANCE_MAP[name]->flags ^ (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE)) != 0) {
Logger::fatalErrorf("Attempt to create instance of unknown type %s", name);
@ -42,18 +54,17 @@ public:
services[name] = std::dynamic_pointer_cast<Service>(INSTANCE_MAP[name]->constructor());
AddChild(std::dynamic_pointer_cast<Instance>(services[name]));
return true;
return {};
}
template <typename T>
std::shared_ptr<T> GetService(std::string name) {
result<std::shared_ptr<T>, NoSuchService> GetService(std::string name) {
if (services.count(name) != 0)
return services[name];
// TODO: Replace this with a result return type
if (!INSTANCE_MAP[name] || (INSTANCE_MAP[name]->flags ^ (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE)) != 0) {
Logger::fatalErrorf("Attempt to create instance of unknown type %s", name);
panic();
return NoSuchService(name);
}
services[name] = std::dynamic_pointer_cast<Service>(INSTANCE_MAP[name]->constructor());

View file

@ -5,4 +5,4 @@
// before shutting down.
// If this process fails, or the panic function is called within itself, it will hard-abort
void panic();
[[noreturn]] void panic();

View file

@ -10,6 +10,7 @@
#include <QWidget>
#include <QTreeView>
#include <QAbstractItemView>
#include <cstdio>
#include <functional>
#include <memory>
#include <optional>
@ -284,8 +285,9 @@ MainWindow::MainWindow(QWidget *parent)
rootDoc.load_string(encoded.c_str());
for (pugi::xml_node instNode : rootDoc.children()) {
InstanceRef inst = Instance::Deserialize(instNode);
gWorkspace()->AddChild(inst);
result<InstanceRef, NoSuchInstance> inst = Instance::Deserialize(instNode);
if (!inst) { inst.logError(); continue; }
gWorkspace()->AddChild(inst.expect());
}
});
@ -303,8 +305,9 @@ MainWindow::MainWindow(QWidget *parent)
rootDoc.load_string(encoded.c_str());
for (pugi::xml_node instNode : rootDoc.children()) {
InstanceRef inst = Instance::Deserialize(instNode);
selectedParent->AddChild(inst);
result<InstanceRef, NoSuchInstance> inst = Instance::Deserialize(instNode);
if (!inst) { inst.logError(); continue; }
selectedParent->AddChild(inst.expect());
}
});
@ -337,8 +340,9 @@ MainWindow::MainWindow(QWidget *parent)
modelDoc.load(inStream);
for (pugi::xml_node instNode : modelDoc.child("openblocks").children("Item")) {
InstanceRef inst = Instance::Deserialize(instNode);
selectedParent->AddChild(inst);
result<InstanceRef, NoSuchInstance> inst = Instance::Deserialize(instNode);
if (!inst) { inst.logError(); continue; }
selectedParent->AddChild(inst.expect());
}
});