From 9ad3d62b14dcdd285761bcf5bd4460787473b609 Mon Sep 17 00:00:00 2001 From: Folling Date: Wed, 3 Jan 2024 17:14:26 +0100 Subject: [PATCH] implement remaining logic Signed-off-by: Folling --- .clang-tidy | 6 +- CMakeLists.txt | 2 +- include/ikarus/errors.h | 87 +++-- include/ikarus/objects/entity.h | 51 ++- include/ikarus/objects/object.h | 16 +- include/ikarus/objects/object_type.h | 3 +- .../objects/properties/number_property.h | 4 +- include/ikarus/objects/properties/property.h | 2 +- .../ikarus/objects/properties/property_type.h | 6 +- .../ikarus/objects/properties/text_property.h | 4 +- .../objects/properties/toggle_property.h | 4 +- include/ikarus/persistence/project.h | 68 +--- include/ikarus/values/entity_property_value.h | 46 +++ include/ikarus/values/value.h | 6 +- src/CMakeLists.txt | 2 + src/errors.cpp | 64 ++-- src/errors.hpp | 93 ++++++ src/objects/blueprint.cpp | 174 +++++----- src/objects/entity.cpp | 297 ++++++++++++++++-- src/objects/object.cpp | 104 ++++++ src/objects/object.hpp | 4 - src/objects/properties/number_property.cpp | 29 ++ src/objects/properties/number_property.hpp | 11 +- src/objects/properties/property.cpp | 131 ++++---- src/objects/properties/property.hpp | 17 +- src/objects/properties/property_source.cpp | 12 + src/objects/properties/property_source.hpp | 5 +- src/objects/properties/text_property.cpp | 29 ++ src/objects/properties/text_property.hpp | 11 +- src/objects/properties/toggle_property.cpp | 30 +- src/objects/properties/toggle_property.hpp | 7 + src/objects/properties/util.hpp | 100 ++++++ src/persistence/migrations.hpp | 40 +++ src/persistence/migrations/m0_genesis.sql | 7 + src/persistence/project.cpp | 233 +++++++++++++- src/persistence/project.hpp | 18 +- src/values/entity_property_value.cpp | 21 ++ src/values/entity_property_value.hpp | 7 + src/values/value.cpp | 2 +- src/values/value.hpp | 46 ++- vendor/sqlitecpp | 2 +- 41 files changed, 1393 insertions(+), 408 deletions(-) create mode 100644 include/ikarus/values/entity_property_value.h create mode 100644 src/errors.hpp create mode 100644 src/objects/properties/util.hpp create mode 100644 src/persistence/migrations.hpp create mode 100644 src/persistence/migrations/m0_genesis.sql create mode 100644 src/values/entity_property_value.cpp create mode 100644 src/values/entity_property_value.hpp diff --git a/.clang-tidy b/.clang-tidy index c2cf005..63731e9 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,7 +1,7 @@ Checks: >- -*, bugprone-*, -bugprone-lambda-function-name, - cppcoreguidelines-*, -cppcoreguidelines-owning-memory, -cppcoreguidelines-non-private-member-variables-in-classes, + cppcoreguidelines-*, -cppcoreguidelines-macro-usage, -cppcoreguidelines-owning-memory, -cppcoreguidelines-non-private-member-variables-in-classes, clang-analyzer-*, google-*, -google-readability-todo, modernize-*, -modernize-use-trailing-return-type, @@ -9,9 +9,9 @@ Checks: >- portability-*, readability-*, -readability-redundant-access-specifiers CheckOptions: - readability-identifier-length.IgnoredParameterNames: '^(db|rc|id)$' + readability-identifier-length.IgnoredParameterNames: '^(db|rc|id|ec)$' readability-identifier-length.IgnoredLoopCounterNames: '^[ij]$' - readability-identifier-length.IgnoredVariableNames: '^(db|rc|id)$' + readability-identifier-length.IgnoredVariableNames: '^(db|rc|id|ec)$' cppcoreguidelines-avoid-do-while.IgnoreMacros: Yes HeaderFileExtensions: - h diff --git a/CMakeLists.txt b/CMakeLists.txt index ae81667..8389d4f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ add_subdirectory(vendor) add_subdirectory(include) add_subdirectory(src) -find_package(Boost REQUIRED) +find_package(Boost COMPONENTS system filesystem REQUIRED) add_library( libikarus SHARED diff --git a/include/ikarus/errors.h b/include/ikarus/errors.h index 92b16cf..a4887a3 100644 --- a/include/ikarus/errors.h +++ b/include/ikarus/errors.h @@ -4,6 +4,7 @@ /// \author Folling #include +#include /// \addtogroup errors Errors /// \brief Error handling within libikarus @@ -24,94 +25,81 @@ IKARUS_BEGIN_HEADER enum IkarusErrorInfo { /// \brief No error occurred. IkarusErrorInfo_None = 0x0, - /// \brief The error was caused by the client. - IkarusErrorInfo_Client = 0x10100000, - /// \brief The error was caused by a dependency (e.g. boost) of libikarus. - IkarusErrorInfo_Dependency = 0x10200000, - /// \brief The error was caused by the filesystem. - IkarusErrorInfo_Filesystem = 0x10300000, - /// \brief The error was caused by the database. - IkarusErrorInfo_Database = 0x10400000, - /// \brief The error was caused by the underlying OS. - IkarusErrorInfo_OS = 0x10500000, - /// \brief The error was caused by libikarus itself. - IkarusErrorInfo_LibIkarus = 0x10600000, - /// \brief The client misused the API. /// Example: Accessing a resource that does not exist. - IkarusErrorInfo_Client_Misuse = 0x10100001, + IkarusErrorInfo_Client_Misuse = 0x01000001, /// \brief The client provided a null value for a parameter that must not be null. /// Example: Passing null for `ikarus_project_get_name` - IkarusErrorInfo_Client_InvalidNull = 0x10100002, + IkarusErrorInfo_Client_InvalidNull = 0x01000002, /// \brief The client provided an index that was out of bounds for some array. /// Example: Passing the index 3 for an `IkarusToggleValue` with size 3. - IkarusErrorInfo_Client_IndexOutOfBounds = 0x10100003, + IkarusErrorInfo_Client_IndexOutOfBounds = 0x01000003, /// \brief The client provided a numeric value that was out of bounds /// Example: Passing the value 2^32 to an i32 (might be passed as a string). - IkarusErrorInfo_Client_ValueOutOfBounds = 0x10100004, + IkarusErrorInfo_Client_ValueOutOfBounds = 0x01000004, /// \brief The client provided invalid input that doesn't fit in any of the other categories. /// Example: Passing an empty/blank string for a string that must be non-empty/-blank. - IkarusErrorInfo_Client_InvalidInput = 0x10100005, + IkarusErrorInfo_Client_InvalidInput = 0x01000005, /// \brief The client provided valid data in an invalid format. /// Example: Passing a malformed JSON string. - IkarusErrorInfo_Client_InvalidFormat = 0x10100006, + IkarusErrorInfo_Client_InvalidFormat = 0x01000006, /// \brief The client violated a constraint. /// \details This error is most likely caused by endusers. /// Example: A user tries to set the age of a character to an value outside of their specified range. - IkarusErrorInfo_Client_ConstraintViolated = 0x10100007, + IkarusErrorInfo_Client_ConstraintViolated = 0x10000007, + + // 0x02 reserved for dependency errors - /// \brief A file was not found. - IkarusErrorInfo_Filesystem_NotFound = 0x10300001, /// \brief A file or directory already exists. - IkarusErrorInfo_Filesystem_AlreadyExists = 0x10300002, + IkarusErrorInfo_Filesystem_AccessIssue = 0x03000001, + /// \brief A file was not found. + IkarusErrorInfo_Filesystem_NotFound = 0x03000002, + /// \brief A file or directory already exists. + IkarusErrorInfo_Filesystem_AlreadyExists = 0x03000003, /// \brief Missing permissions to access a file or directory. - IkarusErrorInfo_Filesystem_MissingPermissions = 0x10300003, + IkarusErrorInfo_Filesystem_MissingPermissions = 0x03000004, /// \brief Insufficient space to perform an operation. - IkarusErrorInfo_Filesystem_InsufficientSpace = 0x10300004, + IkarusErrorInfo_Filesystem_InsufficientSpace = 0x03000005, /// \brief A path is invalid. - IkarusErrorInfo_Filesystem_InvalidPath = 0x10300005, + IkarusErrorInfo_Filesystem_InvalidPath = 0x03000006, /// \brief A database connection failed. - IkarusErrorInfo_Database_ConnectionFailed = 0x10400001, + IkarusErrorInfo_Database_ConnectionFailed = 0x04000001, /// \brief A database query failed. - IkarusErrorInfo_Database_QueryFailed = 0x10400002, + IkarusErrorInfo_Database_QueryFailed = 0x04000002, /// \brief A database migration failed. - IkarusErrorInfo_Database_MigrationFailed = 0x10400003, + IkarusErrorInfo_Database_MigrationFailed = 0x04000003, /// \brief A database is in an invalid state. This indicates a corrupt project. /// Example: An entity is linked to a non-existant blueprint. - IkarusErrorInfo_Database_InvalidState = 0x10400004, + IkarusErrorInfo_Database_InvalidState = 0x04000004, /// \brief A system call failed. - IkarusErrorInfo_OS_SystemCallFailed = 0x10500001, + IkarusErrorInfo_OS_SystemCallFailed = 0x05000001, /// \brief A system call returned an invalid value. - IkarusErrorInfo_OS_InvalidReturnValue = 0x10500002, + IkarusErrorInfo_OS_InvalidReturnValue = 0x05000002, /// \brief An OOM error occurred. - IkarusErrorInfo_OS_InsufficientMemory = 0x10500003, + IkarusErrorInfo_OS_InsufficientMemory = 0x05000003, /// \brief A datapoint within ikarus is invalid for the current state of the system. + /// \details This differs from IkarusErrorInfo_Database_InvalidState in that the latter implies the database itself holds invalid state, + /// whereas the former may imply that the state is ephemeral, e.g. data within a function. /// Example: The name of an object is found to be invalid UTF8. - IkarusErrorInfo_LibIkarus_InvalidState = 0x20030001, + IkarusErrorInfo_LibIkarus_InvalidState = 0x06000001, + /// \brief libikarus is unable to perform a certain operation that should succeed. + IkarusErrorInfo_LibIkarus_CannotPerformOperation = 0x06000002, /// \brief libikarus is unable to perform a certain operation within a given timeframe. /// Example: A query takes longer than the timeout. - IkarusErrorInfo_LibIkarus_Timeout = 0x20030003, + IkarusErrorInfo_LibIkarus_Timeout = 0x06000003, }; -/// \brief The data limits for an error. -enum IkarusErrorDataLimit { - /// \brief The maximum number of error infos that can be stored in an error. - IkarusErrorDataLimit_MaxErrorInfos = 8, - /// \brief The maximum length of an error message. - IkarusErrorDataLimit_MaxMessageLength = 128, -}; +size_t const IKARUS_ERROR_DATA_MAX_MESSAGE_LIMIT = 128; /// \brief The data stored for an error struct IkarusErrorData { - /// \brief The scope of the error. - /// \details This array may at most hold #IkarusErrorDataLimit_MaxErrorInfos elements. - /// The first occurrence of #IkarusErrorInfo_None signifies the end of the array. If this happens at idx x== 0, no error occurred. - IkarusErrorInfo infos[IkarusErrorDataLimit_MaxErrorInfos]; + /// \brief The error type + IkarusErrorInfo info; - char message[IkarusErrorDataLimit_MaxMessageLength]; + char message[IKARUS_ERROR_DATA_MAX_MESSAGE_LIMIT]; }; /// \brief Gets the name of an error info. @@ -128,11 +116,6 @@ IKA_API bool ikarus_error_data_is_success(IkarusErrorData const * data); /// \param data The error data to check. /// \return True if the error data is an error, false otherwise. IKA_API bool ikarus_error_data_is_error(IkarusErrorData const * data); -/// \brief Formats the error data in a reasonable but unspecified way. -/// \param data The error data to format. -/// \return The formatted error data. -/// \remark Ownership of the returned pointer is passed to the user and must be freed at their leisure using ikarus_free. -IKA_API char const * ikarus_error_data_pretty_format(IkarusErrorData const * data); IKARUS_END_HEADER diff --git a/include/ikarus/objects/entity.h b/include/ikarus/objects/entity.h index cfc09f3..9610230 100644 --- a/include/ikarus/objects/entity.h +++ b/include/ikarus/objects/entity.h @@ -89,6 +89,30 @@ IKA_API void ikarus_entity_link_to_blueprint(IkarusEntity * entity, struct Ikaru /// \remark No-op if the entity is not linked to the blueprint. IKA_API void ikarus_entity_unlink_from_blueprint(IkarusEntity * entity, struct IkarusBlueprint * blueprint, IkarusErrorData * error_out); +/// \brief Gets the blueprints an entity is linked to. +/// \param entity The entity to get the blueprints of. +/// \pre \li Must not be null. +/// \pre \li Must exist. +/// \param blueprints_out The buffer to write the blueprints to. +/// \pre \li Must not be null. +/// \param blueprints_out_size The size of the buffer. +/// \param error_out \see errors.h +/// \see ikarus_entity_get_linked_blueprint_count +IKA_API void ikarus_entity_get_linked_blueprints( + IkarusEntity const * entity, + struct IkarusBlueprint ** blueprints_out, + size_t blueprints_out_size, + IkarusErrorData * error_out +); + +/// \brief Gets the number of blueprints an entity is linked to. +/// \param entity The entity to get the number of blueprints of. +/// \pre \li Must not be null. +/// \pre \li Must exist. +/// \param error_out \see errors.h +/// \return The number of blueprints or undefined if an error occurs. +IKA_API size_t ikarus_entity_get_linked_blueprint_count(IkarusEntity const * entity, IkarusErrorData * error_out); + /// \brief Checks if an entity has a specific property. /// \param entity The entity to check. /// \pre \li Must not be null. @@ -97,17 +121,9 @@ IKA_API void ikarus_entity_unlink_from_blueprint(IkarusEntity * entity, struct I /// \pre \li Must not be null. /// \pre \li Must exist. /// \param error_out \see errors.h -/// \return True if the entity has the property, false otherwise. +/// \return False if an error occurs or the entity does not have the property, true otherwise. IKA_API bool ikarus_entity_has_property(IkarusEntity const * entity, struct IkarusProperty const * property, IkarusErrorData * error_out); -/// \brief Gets the number of properties of an entity. -/// \param entity The entity to get the number of properties of. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \param error_out \see errors.h -/// \return The number of properties or undefined if an error occurs. -IKA_API size_t ikarus_entity_get_property_count(IkarusEntity const * entity, IkarusErrorData * error_out); - /// \brief Gets the properties of an entity. /// \param entity The entity to get the properties of. /// \pre \li Must not be null. @@ -116,6 +132,7 @@ IKA_API size_t ikarus_entity_get_property_count(IkarusEntity const * entity, Ika /// \pre \li Must not be null. /// \param error_out \see errors.h /// \param properties_out_size The size of the buffer. +/// \see ikarus_entity_get_property_count IKA_API void ikarus_entity_get_properties( IkarusEntity const * entity, struct IkarusProperty ** properties_out, @@ -123,9 +140,16 @@ IKA_API void ikarus_entity_get_properties( IkarusErrorData * error_out ); +/// \brief Gets the number of properties of an entity. +/// \param entity The entity to get the number of properties of. +/// \pre \li Must not be null. +/// \pre \li Must exist. +/// \param error_out \see errors.h +/// \return The number of properties or undefined if an error occurs. +IKA_API size_t ikarus_entity_get_property_count(IkarusEntity const * entity, IkarusErrorData * error_out); + /// \brief Gets the value of a property of an entity. -/// \details If the entity has never set the value of the property, the default value is returned (which may be -/// undefined). +/// \details If the entity has never set the value of the property, the default value is returned (which may be undefined). /// \param entity The entity to get the value of. /// \pre \li Must not be null. /// \pre \li Must exist. @@ -134,9 +158,8 @@ IKA_API void ikarus_entity_get_properties( /// \pre \li Must exist. /// \param error_out \see errors.h /// \return The value of the property or null if the entity does not have the property or an error occurs. -/// \remark Must be freed using -/// #ikarus_free. -IKA_API struct IkarusEntityValue * +/// \remark Must be freed using #ikarus_free. +IKA_API struct IkarusEntityPropertyValue * ikarus_entity_get_value(IkarusEntity const * entity, struct IkarusProperty const * property, IkarusErrorData * error_out); /// \brief Sets the value of a property of an entity. diff --git a/include/ikarus/objects/object.h b/include/ikarus/objects/object.h index 2aa3d0c..9dd3b39 100644 --- a/include/ikarus/objects/object.h +++ b/include/ikarus/objects/object.h @@ -19,6 +19,13 @@ IKARUS_BEGIN_HEADER /// \brief A generic object. Wraps all types of objects, including folders. struct IkarusObject; +/// \brief Fetches the project of an object. +/// \param object The object to fetch the project from. +/// \pre \li Must not be null. +/// \param error_out \see errors.h +/// \return The project of the object or null if an error occurs. +IKA_API struct IkarusProject * ikarus_object_get_project(IkarusObject const * object, IkarusErrorData * error_out); + /// \brief Compares two objects for equality. /// \details This neither compares the pointers nor does a deep copy. Instead it figures out if the objects _are_ the /// same object. @@ -35,9 +42,6 @@ IKA_API bool ikarus_object_is_equal(IkarusObject const * lhs, IkarusObject const /// \param blueprint_visitor The function to call if the object is a blueprint. Skipped if null. /// \param property_visitor The function to call if the object is a property. Skipped if null. /// \param entity_visitor The function to call if the object is an entity. Skipped if null. -/// \param blueprint_folder_visitor The function to call if the object is a blueprint folder. Skipped if null. -/// \param property_folder_visitor The function to call if the object is a property folder. Skipped if null. -/// \param entity_folder_visitor The function to call if the object is an entity folder. Skipped if null. /// \param data The data passed to the visitor functions. /// \param error_out \see errors.h IKA_API void ikarus_object_visit( @@ -45,9 +49,6 @@ IKA_API void ikarus_object_visit( void (*blueprint_visitor)(struct IkarusBlueprint *, void *), void (*property_visitor)(struct IkarusProperty *, void *), void (*entity_visitor)(struct IkarusEntity *, void *), - void (*blueprint_folder_visitor)(struct IkarusBlueprintFolder *, void *), - void (*property_folder_visitor)(struct IkarusPropertyFolder *, void *), - void (*entity_folder_visitor)(struct IkarusEntityFolder *, void *), void * data, IkarusErrorData * error_out ); @@ -58,9 +59,6 @@ IKA_API void ikarus_object_visit_const( void (*blueprint_visitor)(struct IkarusBlueprint const *, void *), void (*property_visitor)(struct IkarusProperty const *, void *), void (*entity_visitor)(struct IkarusEntity const *, void *), - void (*blueprint_folder_visitor)(struct IkarusBlueprintFolder const *, void *), - void (*property_folder_visitor)(struct IkarusPropertyFolder const *, void *), - void (*entity_folder_visitor)(struct IkarusEntityFolder const *, void *), void * data, IkarusErrorData * error_out ); diff --git a/include/ikarus/objects/object_type.h b/include/ikarus/objects/object_type.h index 7dd3507..e30bb3c 100644 --- a/include/ikarus/objects/object_type.h +++ b/include/ikarus/objects/object_type.h @@ -25,10 +25,9 @@ enum IkarusObjectType { /// \brief Converts an IkarusObjectType to a string. /// \param type The type to convert. -/// \param error_out \see errors.h /// \return The string representation of the type. /// \remark The returned string must not be freed. -char const * ikarus_object_type_to_string(IkarusObjectType type, IkarusErrorData * error_out); +char const * ikarus_object_type_to_string(IkarusObjectType type); IKARUS_END_HEADER diff --git a/include/ikarus/objects/properties/number_property.h b/include/ikarus/objects/properties/number_property.h index 5d24f87..8ca4ffd 100644 --- a/include/ikarus/objects/properties/number_property.h +++ b/include/ikarus/objects/properties/number_property.h @@ -46,7 +46,7 @@ ikarus_number_property_get_default_value(struct IkarusNumberProperty * property, /// \param property The number property. /// \pre \li Must not be null. /// \pre \li Must exist. -/// \param default_value The default value. +/// \param new_default_value The default value. /// \pre \li Must not be null. /// \pre \li Must be a valid value for the property. /// \param error_out \see errors.h @@ -54,7 +54,7 @@ ikarus_number_property_get_default_value(struct IkarusNumberProperty * property, /// default values and other settings. IKA_API void ikarus_number_property_set_default_value( struct IkarusNumberProperty * property, - struct IkarusNumberValue * default_value, + struct IkarusNumberValue * new_default_value, IkarusErrorData * error_out ); diff --git a/include/ikarus/objects/properties/property.h b/include/ikarus/objects/properties/property.h index 34f2d9a..7a94079 100644 --- a/include/ikarus/objects/properties/property.h +++ b/include/ikarus/objects/properties/property.h @@ -89,7 +89,7 @@ IKA_API struct IkarusPropertySource const * ikarus_property_get_source(IkarusPro /// \param error_out \see errors.h /// \return The default value of the property or null if an error occurs. /// \remark Must be freed using #ikarus_free. -IKA_API struct IkarusValue const * ikarus_property_get_default_value(IkarusProperty const * property, IkarusErrorData * error_out); +IKA_API struct IkarusValue * ikarus_property_get_default_value(IkarusProperty const * property, IkarusErrorData * error_out); /// \brief Visits a property. Calling the appropriate function for the property's type. /// \param property The property to visit. diff --git a/include/ikarus/objects/properties/property_type.h b/include/ikarus/objects/properties/property_type.h index ee6ec19..893cdca 100644 --- a/include/ikarus/objects/properties/property_type.h +++ b/include/ikarus/objects/properties/property_type.h @@ -15,11 +15,11 @@ IKARUS_BEGIN_HEADER /// available. enum IkarusPropertyType { /// \brief A true/false boolean-esque value. - IkarusPropertyType_Toggle, + IkarusPropertyType_Toggle = 0x10000000, /// \brief A numeric value, limited to IEEE 80 bit floating point numbers. - IkarusPropertyType_Number, + IkarusPropertyType_Number = 0x20000000, /// \brief An arbitrary UTF-8 textual value. - IkarusPropertyType_Text, + IkarusPropertyType_Text = 0x30000000, }; IKARUS_END_HEADER diff --git a/include/ikarus/objects/properties/text_property.h b/include/ikarus/objects/properties/text_property.h index 0d240b2..0408614 100644 --- a/include/ikarus/objects/properties/text_property.h +++ b/include/ikarus/objects/properties/text_property.h @@ -45,7 +45,7 @@ IKA_API struct IkarusTextValue * ikarus_text_property_get_default_value(struct I /// \param property The text property. /// \pre \li Must not be null. /// \pre \li Must exist. -/// \param default_value The default value. +/// \param new_default_value The default value. /// \pre \li Must not be null. /// \pre \li Must be a valid value for the property. /// \param error_out \see errors.h @@ -53,7 +53,7 @@ IKA_API struct IkarusTextValue * ikarus_text_property_get_default_value(struct I /// default values and other settings. IKA_API void ikarus_text_property_set_default_value( struct IkarusTextProperty * property, - struct IkarusTextValue * default_value, + struct IkarusTextValue * new_default_value, IkarusErrorData * error_out ); diff --git a/include/ikarus/objects/properties/toggle_property.h b/include/ikarus/objects/properties/toggle_property.h index e084123..892d418 100644 --- a/include/ikarus/objects/properties/toggle_property.h +++ b/include/ikarus/objects/properties/toggle_property.h @@ -46,7 +46,7 @@ ikarus_toggle_property_get_default_value(struct IkarusToggleProperty * property, /// \param property The toggle property. /// \pre \li Must not be null. /// \pre \li Must exist. -/// \param default_value The default value. +/// \param new_default_value The default value. /// \pre \li Must not be null. /// \pre \li Must be a valid value for the property. /// \param error_out \see errors.h @@ -54,7 +54,7 @@ ikarus_toggle_property_get_default_value(struct IkarusToggleProperty * property, /// default values and other settings. IKA_API void ikarus_toggle_property_set_default_value( struct IkarusToggleProperty * property, - struct IkarusToggleValue * default_value, + struct IkarusToggleValue * new_default_value, IkarusErrorData * error_out ); diff --git a/include/ikarus/persistence/project.h b/include/ikarus/persistence/project.h index 93c4e11..c4406f8 100644 --- a/include/ikarus/persistence/project.h +++ b/include/ikarus/persistence/project.h @@ -51,39 +51,13 @@ IKA_API IkarusProject * ikarus_project_create_in_memory(char const * name, Ikaru /// ikarus_project_delete IKA_API IkarusProject * ikarus_project_open(char const * path, IkarusErrorData * error_out); -/// \brief Copies a project to a new location. -/// \details The new project is not opened. -/// \param project The project to copy. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \param target_path The new location of the project. -/// \pre \li Must not be null. -/// \pre \li Must point to a valid unused path on the system. -/// \param target_name The name of the new project. -/// \pre \li Must not be null. -/// \pre \li Must not be empty. -/// \param error_out \see errors.h -/// \remark If successful the project connection remains intact. The previous location will still exist. -IKA_API void -ikarus_project_copy(IkarusProject const * project, char const * target_path, char const * target_name, IkarusErrorData * error_out); - -/// \brief Deletes a project and all its associated data from the filesystem. -/// \param project The project to delete. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \param error_out \see errors.h -/// \remark also frees the project. -/// \remark In-Memory projects will just be freed. -/// \remark If deletion fails, the project pointer remains intact. -IKA_API void ikarus_project_delete(IkarusProject * project, IkarusErrorData * error_out); - /// \brief Gets the name of a project. /// \param project The project to get the name of. /// \pre \li Must not be null. /// \pre \li Must exist. /// \param error_out \see errors.h /// \return The name of the project. -/// \remark Must be freed using #ikarus_free. +/// \remark Ownership remains with libikarus, must not be freed. IKA_API char const * ikarus_project_get_name(IkarusProject const * project, IkarusErrorData * error_out); /// \brief Sets the name of a project. @@ -102,30 +76,9 @@ IKA_API void ikarus_project_set_name(IkarusProject * project, char const * new_n /// \pre \li Must exist. /// \param error_out \see errors.h /// \return The path of the project. -/// \remark Must be freed using #ikarus_free. +/// \remark Ownership remains with libikarus, must not be freed. IKA_API char const * ikarus_project_get_path(IkarusProject const * project, IkarusErrorData * error_out); -/// \brief Moves a project to a new location. -/// \param project The project to move. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \param target_path The new location of the project. -/// \pre \li Must not be null. -/// \pre \li Must point to a valid unused path on the system. -/// \param error_out \see errors.h -/// \remark If successful the project connection remains intact. The previous location will not exist anymore. -/// \remark Due to the nature of filesystems this function may not be atomic. -IKA_API void ikarus_project_move(IkarusProject * project, char const * target_path, IkarusErrorData * error_out); - -/// \brief Gets the blueprint root folder of a project. -/// \param project The project to get the blueprint root folder of. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \param error_out \see errors.h -/// \return The blueprint root folder of the project or null if an error occurs. -/// \remark Must be freed using #ikarus_free. -IKA_API struct IkarusBlueprintFolder * ikarus_project_get_blueprint_root_folder(IkarusProject const * project, IkarusErrorData * error_out); - /// \brief Gets the blueprints of a project. /// \param project The project to get the blueprints of. /// \pre \li Must not be null. @@ -135,7 +88,7 @@ IKA_API struct IkarusBlueprintFolder * ikarus_project_get_blueprint_root_folder( /// \param blueprints_out_size The size of the buffer. /// \param error_out \see errors.h IKA_API void ikarus_project_get_blueprints( - IkarusProject const * project, + IkarusProject * project, struct IkarusBlueprint ** blueprints_out, size_t blueprints_out_size, IkarusErrorData * error_out @@ -147,16 +100,7 @@ IKA_API void ikarus_project_get_blueprints( /// \pre \li Must exist. /// \param error_out \see errors.h /// \return The number of blueprints or undefined if an error occurs. -IKA_API size_t ikarus_project_get_blueprint_count(IkarusProject const * project, IkarusErrorData * error_out); - -/// \brief Gets the entity root folder of a project. -/// \param project The project to get the entity root folder of. -/// \pre \li Must not be null. -/// \pre \li Must exist. -/// \param error_out \see errors.h -/// \return The entity root folder of the project or null if an error occurs. -/// \remark Must be freed using #ikarus_free. -IKA_API struct IkarusEntityFolder * ikarus_project_get_entity_root_folder(IkarusProject const * project, IkarusErrorData * error_out); +IKA_API size_t ikarus_project_get_blueprint_count(IkarusProject * project, IkarusErrorData * error_out); /// \brief Gets the entities of a project. /// \param project The project to get the entities of. @@ -167,7 +111,7 @@ IKA_API struct IkarusEntityFolder * ikarus_project_get_entity_root_folder(Ikarus /// \param entities_out_size The size of the buffer. /// \param error_out \see errors.h IKA_API void ikarus_project_get_entities( - IkarusProject const * project, + IkarusProject * project, struct IkarusEntity ** entities_out, size_t entities_out_size, IkarusErrorData * error_out @@ -179,7 +123,7 @@ IKA_API void ikarus_project_get_entities( /// \pre \li Must exist. /// \param error_out \see errors.h /// \return The number of entities or undefined if an error occurs. -IKA_API size_t ikarus_project_get_entity_count(IkarusProject const * project, IkarusErrorData * error_out); +IKA_API size_t ikarus_project_get_entity_count(IkarusProject * project, IkarusErrorData * error_out); IKARUS_END_HEADER diff --git a/include/ikarus/values/entity_property_value.h b/include/ikarus/values/entity_property_value.h new file mode 100644 index 0000000..c408f00 --- /dev/null +++ b/include/ikarus/values/entity_property_value.h @@ -0,0 +1,46 @@ +#pragma once + +/// \file entity_property_value.h +/// \author Folling + +/// \defgroup entity_property_values EntityPropertyValue +/// \brief Values in relation to an entity and one of its properties. +/// @{ + +#include +#include +#include + +IKARUS_BEGIN_HEADER + +/// \brief Like an \ref value.h "IkarusValue", but in relation to an entity and one of its properties +struct IkarusEntityPropertyValue; + +/// \brief Fetches the entity of an entity property value. +/// \param value The entity property value. +/// \pre \li Must not be null. +/// \param error_out \see errors.h +/// \return The entity of the entity property value. +/// \remark This value is owned by the entity property value and must not be freed directly. +struct IkarusEntity const * ikarus_entity_property_value_get_entity(IkarusEntityPropertyValue const * value, IkarusErrorData * error_out); + +/// \brief Fetches the property of an entity property value. +/// \param value The entity property value. +/// \pre \li Must not be null. +/// \param error_out \see errors.h +/// \return The property of the entity property value. +/// \remark This value is owned by the entity property value and must not be freed directly. +struct IkarusProperty const * +ikarus_entity_property_value_get_property(IkarusEntityPropertyValue const * value, IkarusErrorData * error_out); + +/// \brief Fetches the value of an entity property value. +/// \param value The entity property value. +/// \pre \li Must not be null. +/// \param error_out \see errors.h +/// \return The value of the entity property value. +/// \remark This value is owned by the entity property value and must not be freed directly. +struct IkarusValue const * ikarus_entity_property_value_get_value(IkarusEntityPropertyValue const * value, IkarusErrorData * error_out); + +IKARUS_END_HEADER + +/// @} diff --git a/include/ikarus/values/value.h b/include/ikarus/values/value.h index 3fcaf63..fbc50e1 100644 --- a/include/ikarus/values/value.h +++ b/include/ikarus/values/value.h @@ -3,9 +3,6 @@ /// \file value.h /// \author Folling -#include -#include - /// \defgroup values Values /// \brief The values of properties. /// \details Each entity has a value for each property it is associated with. @@ -17,6 +14,9 @@ /// property's settings. \see PropertyType /// @{ +#include +#include + IKARUS_BEGIN_HEADER /// \brief The common type for all values. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6b00545..0963d2e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,6 +3,8 @@ file( FILES "*.hpp" "*.cpp" + "*.h" + "*.c" ) set(SOURCE_FILES ${FILES} PARENT_SCOPE) \ No newline at end of file diff --git a/src/errors.cpp b/src/errors.cpp index 32b2c99..4441fe9 100644 --- a/src/errors.cpp +++ b/src/errors.cpp @@ -11,61 +11,39 @@ char const * get_error_info_name(IkarusErrorInfo info) { switch (info) { case IkarusErrorInfo_None: return "None"; - case IkarusErrorInfo_Client: return "Client"; - case IkarusErrorInfo_Dependency: return "Dependency"; - case IkarusErrorInfo_Filesystem: return "Filesystem"; - case IkarusErrorInfo_Database: return "Database"; - case IkarusErrorInfo_OS: return "OS"; - case IkarusErrorInfo_LibIkarus: return "libikarus"; - case IkarusErrorInfo_Client_Misuse: return "Misuse"; - case IkarusErrorInfo_Client_InvalidInput: return "InvalidInput"; - case IkarusErrorInfo_Client_InvalidFormat: return "InvalidFormat"; - case IkarusErrorInfo_Client_ConstraintViolated: return "ConstraintViolated"; + case IkarusErrorInfo_Client_Misuse: return "Client::Misuse"; + case IkarusErrorInfo_Client_InvalidInput: return "Client::InvalidInput"; + case IkarusErrorInfo_Client_InvalidFormat: return "Client::InvalidFormat"; + case IkarusErrorInfo_Client_ConstraintViolated: return "Client::ConstraintViolated"; - case IkarusErrorInfo_Filesystem_NotFound: return "NotFound"; - case IkarusErrorInfo_Filesystem_AlreadyExists: return "AlreadyExists"; - case IkarusErrorInfo_Filesystem_MissingPermissions: return "MissingPermissions"; - case IkarusErrorInfo_Filesystem_InsufficientSpace: return "InsufficientSpace"; - case IkarusErrorInfo_Filesystem_InvalidPath: return "InvalidPath"; + case IkarusErrorInfo_Filesystem_NotFound: return "Filesystem::NotFound"; + case IkarusErrorInfo_Filesystem_AlreadyExists: return "Filesystem::AlreadyExists"; + case IkarusErrorInfo_Filesystem_MissingPermissions: return "Filesystem::MissingPermissions"; + case IkarusErrorInfo_Filesystem_InsufficientSpace: return "Filesystem::InsufficientSpace"; + case IkarusErrorInfo_Filesystem_InvalidPath: return "Filesystem::InvalidPath"; - case IkarusErrorInfo_Database_ConnectionFailed: return "ConnectionFailed"; - case IkarusErrorInfo_Database_QueryFailed: return "QueryFailed"; - case IkarusErrorInfo_Database_MigrationFailed: return "MigrationFailed"; - case IkarusErrorInfo_Database_InvalidState: return "InvalidState"; + case IkarusErrorInfo_Database_ConnectionFailed: return "Database::ConnectionFailed"; + case IkarusErrorInfo_Database_QueryFailed: return "Database::QueryFailed"; + case IkarusErrorInfo_Database_MigrationFailed: return "Database::MigrationFailed"; + case IkarusErrorInfo_Database_InvalidState: return "Database::InvalidState"; - case IkarusErrorInfo_OS_SystemCallFailed: return "SystemCallFailed"; - case IkarusErrorInfo_OS_InvalidReturnValue: return "InvalidReturnValue"; - case IkarusErrorInfo_OS_InsufficientMemory: return "InsufficientMemory"; + case IkarusErrorInfo_OS_SystemCallFailed: return "OS::SystemCallFailed"; + case IkarusErrorInfo_OS_InvalidReturnValue: return "OS::InvalidReturnValue"; + case IkarusErrorInfo_OS_InsufficientMemory: return "OS::InsufficientMemory"; - case IkarusErrorInfo_LibIkarus_InvalidState: return "InvalidState"; - case IkarusErrorInfo_LibIkarus_Timeout: return "Timeout"; + case IkarusErrorInfo_LibIkarus_InvalidState: return "LibIkarus::InvalidState"; + case IkarusErrorInfo_LibIkarus_CannotPerformOperation: return "LibIkarus::CannotPerformOperation"; + case IkarusErrorInfo_LibIkarus_Timeout: return "LibIkarus::Timeout"; default: return "Invalid"; } } bool ikarus_error_data_is_success(IkarusErrorData const * data) { - return data->infos[0] == IkarusErrorInfo_None; + return data->info == IkarusErrorInfo_None; } bool ikarus_error_data_is_error(IkarusErrorData const * data) { - return data->infos[0] != IkarusErrorInfo_None; -} - -char const * ikarus_error_data_pretty_format(IkarusErrorData const * data) { - if (ikarus_error_data_is_success(data)) { - return "Success"; - } - - auto const formatted = fmt::format( - "{} - {}", - fmt::join( - data->infos | std::views::take_while(cppbase::pred_ne(IkarusErrorInfo_None)) | std::views::transform(get_error_info_name), - "->" - ), - data->message - ); - - return strndup(formatted.data(), formatted.size()); + return data->info != IkarusErrorInfo_None; } diff --git a/src/errors.hpp b/src/errors.hpp new file mode 100644 index 0000000..0a9941f --- /dev/null +++ b/src/errors.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include + +#include + +inline void safe_strcpy(char * dest, std::string_view src, size_t dest_size) { + for (int i = 0; i < dest_size; ++i) { + if (src[i] == '\0') { + dest[i] = '\0'; + return; + } + + dest[i] = src[i]; + } +} + +#define IKARUS_SET_ERROR(msg, err_info) \ + if (error_out != nullptr) { \ + safe_strcpy(static_cast(error_out->message), msg, IKARUS_ERROR_DATA_MAX_MESSAGE_LIMIT); \ + error_out->info = err_info; \ + } + +#define IKARUS_FAIL(ret, msg, err_info) \ + IKARUS_SET_ERROR(msg, err_info); \ + return ret + +#define IKARUS_FAIL_IF(condition, ret, msg, err_info) \ + if (condition) { \ + IKARUS_SET_ERROR(msg, err_info) \ + return ret; \ + } + +#define IKARUS_FAIL_IF_ERROR(ret) \ + if (ikarus_error_data_is_error(error_out)) { \ + return ret; \ + } + +#define IKARUS_FAIL_IF_NULL(ptr, ret) IKARUS_FAIL_IF(((ptr) == nullptr), ret, #ptr " must not be null", IkarusErrorInfo_Client_InvalidNull) + +#define IKARUS_TRY_OR_FAIL_IMPL(var_name, msg, err_info, ...) \ + auto var_name = __VA_ARGS__; \ + if (var_name.is_error()) { \ + IKARUS_SET_ERROR(fmt::format(msg, var_name.unwrap_error()), err_info); \ + return var_name; \ + } + +#define IKARUS_TRY_OR_FAIL(msg, err_info, ...) IKARUS_TRY_OR_FAIL_IMPL(CPPBASE_UNIQUE_NAME(result), msg, err_info, __VA_ARGS__); + +#define IKARUS_TRYRV_OR_FAIL_IMPL(var_name, ret, msg, err_info, ...) \ + auto var_name = __VA_ARGS__; \ + if (var_name.is_error()) { \ + IKARUS_SET_ERROR(fmt::format(msg, var_name.unwrap_error()), err_info); \ + return ret; \ + } + +#define IKARUS_TRYRV_OR_FAIL(ret, msg, err_info, ...) \ + IKARUS_TRYRV_OR_FAIL_IMPL(CPPBASE_UNIQUE_NAME(result), ret, msg, err_info, __VA_ARGS__); + +#define IKARUS_VTRY_OR_FAIL_IMPL(var_name, value, msg, err_info, ...) \ + auto var_name = __VA_ARGS__; \ + if (var_name.is_error()) { \ + IKARUS_SET_ERROR(fmt::format(msg, var_name.unwrap_error()), err_info); \ + return var_name; \ + } \ + value = var_name.unwrap_value() + +#define IKARUS_VTRY_OR_FAIL(value, msg, err_info, ...) \ + IKARUS_VTRY_OR_FAIL_IMPL(CPPBASE_UNIQUE_NAME(result), value, msg, err_info, __VA_ARGS__); + +#define IKARUS_VTRYRV_OR_FAIL_IMPL(var_name, value, ret, msg, err_info, ...) \ + auto var_name = __VA_ARGS__; \ + if (var_name.is_error()) { \ + IKARUS_SET_ERROR(fmt::format(msg, var_name.unwrap_error()), err_info); \ + return ret; \ + } \ + value = var_name.unwrap_value() + +#define IKARUS_VTRYRV_OR_FAIL(value, ret, msg, err_info, ...) \ + IKARUS_VTRYRV_OR_FAIL_IMPL(CPPBASE_UNIQUE_NAME(result), value, ret, msg, err_info, __VA_ARGS__); + +#define IKARUS_FAIL_IF_OBJECT_MISSING_IMPL(var_name, obj, ret) \ + IKARUS_VTRYRV_OR_FAIL( \ + bool const var_name, \ + ret, \ + "unable to check whether object exists: {}", \ + IkarusErrorInfo_Database_QueryFailed, \ + (obj)->project->db->template query_one("SELECT EXISTS(SELECT 1 FROM `objects` WHERE `id` = ?)", (obj)->id) \ + ) \ + \ + IKARUS_FAIL_IF(!(var_name), ret, "object does not exist", IkarusErrorInfo_Client_Misuse); + +#define IKARUS_FAIL_IF_OBJECT_MISSING(obj, ret) IKARUS_FAIL_IF_OBJECT_MISSING_IMPL(CPPBASE_UNIQUE_NAME(exists), obj, ret); diff --git a/src/objects/blueprint.cpp b/src/objects/blueprint.cpp index 117cbfc..971bd13 100644 --- a/src/objects/blueprint.cpp +++ b/src/objects/blueprint.cpp @@ -1,11 +1,13 @@ #include "ikarus/objects/blueprint.h" +#include "ikarus/objects/properties/property.h" #include "objects/blueprint.hpp" #include #include #include +#include #include #include #include @@ -13,117 +15,149 @@ IkarusBlueprint::IkarusBlueprint(IkarusProject * project, IkarusId id): IkarusObject{project, id} {} -IkarusBlueprint * ikarus_blueprint_create(struct IkarusProject * project, char const * name) { - if (cppbase::is_empty_or_blank(name)) { - project->set_error("blueprint name must not be empty", true, IkarusErrorInfo_Source_Client, IkarusErrorInfo_Type_Client_Input); +IkarusBlueprint * ikarus_blueprint_create(struct IkarusProject * project, char const * name, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(project, nullptr); + IKARUS_FAIL_IF_NULL(name, nullptr); + IKARUS_FAIL_IF(cppbase::is_empty_or_blank(name), nullptr, "name must not be empty", IkarusErrorInfo_Client_InvalidInput); - return nullptr; - } - - project->db - ->transact([name](auto * db) { + IKARUS_VTRYRV_OR_FAIL( + IkarusId const id, + nullptr, + "failed to create blueprint: {}", + IkarusErrorInfo_Database_QueryFailed, + project->db->transact([name](auto * db) -> cppbase::Result { TRY(db->execute("INSERT INTO `objects`(`type`, `name`) VALUES(?, ?, ?)", IkarusObjectType_Blueprint, name)); auto id = ikarus_id_from_data_and_type(db->last_insert_rowid(), IkarusObjectType_Blueprint); TRY(db->execute("INSERT INTO `blueprints`(`id`) VALUES(?)", id)); - return cppbase::ok(); + return cppbase::ok(id); }) - .on_error([project](auto const & err) { - project->set_error( - fmt::format("failed to create blueprint: {}", err), - true, - IkarusErrorInfo_Source_SubSystem, - IkarusErrorInfo_Type_SubSystem_Database - ); - }); + ); + + return project->get_blueprint(id); } -void ikarus_blueprint_delete(IkarusBlueprint * blueprint) { - blueprint->project->db->execute("DELETE FROM `objects` WHERE `id` = ?", blueprint->id).on_error([blueprint](auto const & err) { - blueprint->project->set_error( - fmt::format("failed to delete blueprint from objects table: {}", err), - true, - IkarusErrorInfo_Source_SubSystem, - IkarusErrorInfo_Type_SubSystem_Database - ); - }); +void ikarus_blueprint_delete(IkarusBlueprint * blueprint, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(blueprint, ); + IKARUS_FAIL_IF_OBJECT_MISSING(blueprint, ); + + IKARUS_TRYRV_OR_FAIL( + , + "unable to delete blueprint: {}", + IkarusErrorInfo_Database_QueryFailed, + blueprint->project->db->execute("DELETE FROM `objects` WHERE `id` = ?", blueprint->id) + ); + + blueprint->project->uncache(blueprint); } void ikarus_blueprint_get_properties( IkarusBlueprint const * blueprint, struct IkarusProperty ** properties_out, - size_t properties_out_size + size_t properties_out_size, + IkarusErrorData * error_out ) { - IkarusId ids[properties_out_size]; + IKARUS_FAIL_IF_NULL(blueprint, ); + IKARUS_FAIL_IF_OBJECT_MISSING(blueprint, ); + IKARUS_FAIL_IF_NULL(properties_out, ); - TRYRV( + if (properties_out_size == 0) { + return; + } + + std::tuple ids_and_types[properties_out_size]; + + IKARUS_TRYRV_OR_FAIL( , - blueprint->project->db - ->query_many_buffered("SELECT `id` FROM `properties` WHERE `source` = ?", ids, properties_out_size, blueprint->id) - .on_error([&](auto const & err) { - blueprint->project->set_error( - fmt::format("failed to fetch blueprint properties from database: {}", err), - true, - IkarusErrorInfo_Source_SubSystem, - IkarusErrorInfo_Type_SubSystem_Database - ); - }) - ); + "unable to fetch blueprint properties from database: {}", + IkarusErrorInfo_Database_QueryFailed, + blueprint->project->db->query_many_buffered( + "SELECT `id` FROM `properties` WHERE `source` = ?", + ids_and_types, + properties_out_size, + blueprint->id + ) + ) - // not atomic, could be switched to two loops if necessary for (size_t i = 0; i < properties_out_size; ++i) { - IkarusId id = ids[i]; - - VTRYRV(auto const type, , IkarusProperty::get_property_type(blueprint->project, id)); + auto [id, type] = ids_and_types[i]; properties_out[i] = blueprint->project->get_property(id, type); } } -size_t ikarus_blueprint_get_property_count(IkarusBlueprint const * blueprint) { - return blueprint->project->db->query_one("SELECT COUNT(*) FROM `properties` WHERE `source` = ?", blueprint->id) - .unwrap_value_or(0); +size_t ikarus_blueprint_get_property_count(IkarusBlueprint const * blueprint, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(blueprint, 0); + IKARUS_FAIL_IF_OBJECT_MISSING(blueprint, 0); + + IKARUS_VTRYRV_OR_FAIL( + auto const ret, + 0, + "unable to fetch blueprint property count from database: {}", + IkarusErrorInfo_Database_QueryFailed, + blueprint->project->db->query_one("SELECT COUNT(*) FROM `properties` WHERE `source` = ?", blueprint->id) + ); + + return ret; } void ikarus_blueprint_get_linked_entities( IkarusBlueprint const * blueprint, struct IkarusEntity ** entities_out, - size_t entities_out_size + size_t entities_out_size, + IkarusErrorData * error_out ) { + IKARUS_FAIL_IF_NULL(blueprint, ); + IKARUS_FAIL_IF_OBJECT_MISSING(blueprint, ); + IKARUS_FAIL_IF_NULL(entities_out, ); + + if (entities_out_size == 0) { + return; + } + IkarusId ids[entities_out_size]; - TRYRV( + IKARUS_TRYRV_OR_FAIL( , - blueprint->project->db - ->query_many_buffered( - "SELECT `entity` FROM `entity_blueprint_links` WHERE `blueprint` = ?", - ids, - entities_out_size, - blueprint->id - ) - .on_error([&](auto const & err) { - blueprint->project->set_error( - fmt::format("failed to fetch linked entities from database: {}", err), - true, - IkarusErrorInfo_Source_SubSystem, - IkarusErrorInfo_Type_SubSystem_Database - ); - }) - ); + "unable to fetch blueprint linked entities from database: {}", + IkarusErrorInfo_Database_QueryFailed, + blueprint->project->db->query_many_buffered( + "SELECT `entity` FROM `entity_blueprint_links` WHERE `blueprint` = ?", + ids, + entities_out_size, + blueprint->id + ) + ) for (size_t i = 0; i < entities_out_size; ++i) { entities_out[i] = blueprint->project->get_entity(ids[i]); } } -size_t ikarus_blueprint_get_linked_entity_count(IkarusBlueprint const * blueprint) { - return blueprint->project->db->query_one("SELECT COUNT(*) FROM `entity_blueprint_links` WHERE `blueprint` = ?", blueprint->id) - .unwrap_value_or(0); +size_t ikarus_blueprint_get_linked_entity_count(IkarusBlueprint const * blueprint, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(blueprint, 0); + IKARUS_FAIL_IF_OBJECT_MISSING(blueprint, 0); + + IKARUS_VTRYRV_OR_FAIL( + auto const ret, + 0, + "unable to fetch blueprint linked entity count from database: {}", + IkarusErrorInfo_Database_QueryFailed, + blueprint->project->db->query_one("SELECT COUNT(*) FROM `entity_blueprint_links` WHERE `blueprint` = ?", blueprint->id) + ); + + return ret; } -IkarusObject * ikarus_blueprint_to_object(IkarusBlueprint * blueprint) { +// No existence checks here for performance reasons. All methods on IkarusObject perform this check anyway. + +IkarusObject * ikarus_blueprint_to_object(IkarusBlueprint * blueprint, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(blueprint, nullptr); + return static_cast(blueprint); } -IkarusObject const * ikarus_blueprint_to_object_const(IkarusBlueprint const * blueprint) { +IkarusObject const * ikarus_blueprint_to_object_const(IkarusBlueprint const * blueprint, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(blueprint, nullptr); + return static_cast(blueprint); } diff --git a/src/objects/entity.cpp b/src/objects/entity.cpp index af2395f..174123e 100644 --- a/src/objects/entity.cpp +++ b/src/objects/entity.cpp @@ -1,36 +1,289 @@ #include "entity.hpp" +#include "values/value.hpp" + #include +#include #include -#include +#include #include +#include -IkarusEntity * ikarus_entity_create(struct IkarusProject * project, char const * name) { - return ikarus::util::insert_object( - project, - IkarusObjectType_Entity, - name, - [](auto * db, IkarusId id) { return db->execute("INSERT INTO `entities`(`id`) VALUES(?)", id); }, - [project](IkarusId id) { return project->get_entity(id); } - ).unwrap_value_or(nullptr); +IkarusEntity * ikarus_entity_create(struct IkarusProject * project, char const * name, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(project, nullptr); + IKARUS_FAIL_IF_NULL(name, nullptr); + IKARUS_FAIL_IF(cppbase::is_empty_or_blank(name), nullptr, "name must not be empty", IkarusErrorInfo_Client_InvalidInput); + + IKARUS_VTRYRV_OR_FAIL( + IkarusId const id, + nullptr, + "failed to create entity: {}", + IkarusErrorInfo_Database_QueryFailed, + project->db->transact([name](auto * db) -> cppbase::Result { + TRY(db->execute("INSERT INTO `objects`(`type`, `name`) VALUES(?, ?, ?)", IkarusObjectType_Entity, name)); + auto id = ikarus_id_from_data_and_type(db->last_insert_rowid(), IkarusObjectType_Entity); + TRY(db->execute("INSERT INTO `entities`(`id`) VALUES(?)", id)); + return cppbase::ok(id); + }) + ); + + return project->get_entity(id); } -void ikarus_entity_delete(IkarusEntity * entity) { - ikarus::util::delete_object(entity); +void ikarus_entity_delete(IkarusEntity * entity, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(entity, ); + IKARUS_FAIL_IF_OBJECT_MISSING(entity, ); + + IKARUS_TRYRV_OR_FAIL( + , + "unable to delete entity: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->execute("DELETE FROM `objects` WHERE `id` == ?", entity->id) + ); + + entity->project->uncache(entity); } -bool ikarus_entity_is_linked_to_blueprint(IkarusEntity const * entity, struct IkarusBlueprint const * blueprint) { - return ikarus::util::check_exists( - entity, - ikarus::util::ExistsQueryData{ - .table_name = "entity_blueprint_links", - .where_field_name = "blueprint", - .where_field_value = blueprint->id, - .relation_desc = "linked blueprints" - } +bool ikarus_entity_is_linked_to_blueprint( + IkarusEntity const * entity, + struct IkarusBlueprint const * blueprint, + IkarusErrorData * error_out +) { + IKARUS_FAIL_IF_NULL(entity, false); + IKARUS_FAIL_IF_OBJECT_MISSING(entity, false); + IKARUS_FAIL_IF_NULL(blueprint, false); + IKARUS_FAIL_IF_OBJECT_MISSING(blueprint, false); + + IKARUS_VTRYRV_OR_FAIL( + auto const ret, + false, + "unable to check whether entity is linked to blueprint", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->query_one( + "SELECT EXISTS(SELECT 1 FROM `entity_blueprint_links` WHERE `entity` = ? AND `blueprint` = ?)", + entity->id, + blueprint->id + ) ) - .unwrap_value_or(false); + + return ret; } -bool ikarus_entity_link_to_blueprint(IkarusEntity * entity, struct IkarusBlueprint * blueprint) {} +void ikarus_entity_link_to_blueprint(IkarusEntity * entity, struct IkarusBlueprint * blueprint, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(entity, ); + IKARUS_FAIL_IF_OBJECT_MISSING(entity, ); + IKARUS_FAIL_IF_NULL(blueprint, ); + IKARUS_FAIL_IF_OBJECT_MISSING(blueprint, ); + + IKARUS_TRYRV_OR_FAIL( + , + "unable to link entity to blueprint: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->execute( + "INSERT INTO `entity_blueprint_links`(`entity`, `blueprint`) VALUES(?, ?) ON CONFLICT(`entity`, `blueprint`) DO NOTHING", + entity->id, + blueprint->id + ) + ); +} + +void ikarus_entity_unlink_from_blueprint(IkarusEntity * entity, struct IkarusBlueprint * blueprint, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(entity, ); + IKARUS_FAIL_IF_OBJECT_MISSING(entity, ); + IKARUS_FAIL_IF_NULL(blueprint, ); + IKARUS_FAIL_IF_OBJECT_MISSING(blueprint, ); + + IKARUS_TRYRV_OR_FAIL( + , + "unable to unlink entity from blueprint: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db + ->execute("DELETE FROM `entity_blueprint_links` WHERE `entity` = ? AND `blueprint` = ?", entity->id, blueprint->id) + ); +} + +void ikarus_entity_get_linked_blueprints( + IkarusEntity const * entity, + struct IkarusBlueprint ** blueprints_out, + size_t blueprints_out_size, + IkarusErrorData * error_out +) { + IKARUS_FAIL_IF_NULL(entity, ); + IKARUS_FAIL_IF_OBJECT_MISSING(entity, ); + IKARUS_FAIL_IF_NULL(blueprints_out, ); + + if (blueprints_out_size == 0) { + return; + } + + IkarusId ids[blueprints_out_size]; + + IKARUS_TRYRV_OR_FAIL( + , + "unable to fetch entity linked blueprints from database: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->query_many_buffered( + "SELECT `blueprint` FROM `entity_blueprint_links` WHERE `entity` = ?", + ids, + blueprints_out_size, + entity->id + ) + ) + + for (size_t i = 0; i < blueprints_out_size; ++i) { + blueprints_out[i] = entity->project->get_blueprint(ids[i]); + } +} + +size_t ikarus_entity_get_linked_blueprint_count(IkarusEntity const * entity, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(entity, 0); + IKARUS_FAIL_IF_OBJECT_MISSING(entity, 0); + + IKARUS_VTRYRV_OR_FAIL( + auto const ret, + 0, + "unable to fetch entity linked blueprint count from database: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->query_one("SELECT COUNT(*) FROM `entity_blueprint_links` WHERE `entity` = ?", entity->id) + ); + + return ret; +} + +bool ikarus_entity_has_property(IkarusEntity const * entity, struct IkarusProperty const * property, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(entity, false); + IKARUS_FAIL_IF_OBJECT_MISSING(entity, false); + IKARUS_FAIL_IF_NULL(property, false); + IKARUS_FAIL_IF_OBJECT_MISSING(property, false); + + IKARUS_VTRYRV_OR_FAIL( + auto const ret, + false, + "unable to check whether entity has property: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->query_one( + "SELECT EXISTS(SELECT 1 FROM `entity_properties` WHERE `entity` = ? AND `property` = ?)", + entity->id, + property->id + ) + ) + + return ret; +} + +void ikarus_entity_get_properties( + IkarusEntity const * entity, + struct IkarusProperty ** properties_out, + size_t properties_out_size, + IkarusErrorData * error_out +) { + IKARUS_FAIL_IF_NULL(entity, ); + IKARUS_FAIL_IF_OBJECT_MISSING(entity, ); + IKARUS_FAIL_IF_NULL(properties_out, ); + + if (properties_out_size == 0) { + return; + } + + std::tuple ids_and_types[properties_out_size]; + + IKARUS_TRYRV_OR_FAIL( + , + "unable to fetch entity properties from database: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->query_many_buffered( + "SELECT `property`, `type` FROM `properties` WHERE `source` = ?", + ids_and_types, + properties_out_size, + entity->id + ) + ) + + for (size_t i = 0; i < properties_out_size; ++i) { + auto [id, type] = ids_and_types[i]; + properties_out[i] = entity->project->get_property(id, type); + } +} + +size_t ikarus_entity_get_property_count(IkarusEntity const * entity, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(entity, 0); + IKARUS_FAIL_IF_OBJECT_MISSING(entity, 0); + + IKARUS_VTRYRV_OR_FAIL( + size_t const ret, + 0, + "unable to fetch entity property count from database: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->query_one("SELECT COUNT(*) FROM `properties` WHERE `source` = ?", entity->id) + ); + + return ret; +} + +struct IkarusEntityPropertyValue * +ikarus_entity_get_value(IkarusEntity const * entity, struct IkarusProperty const * property, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(entity, nullptr); + IKARUS_FAIL_IF_OBJECT_MISSING(entity, nullptr); + IKARUS_FAIL_IF_NULL(property, nullptr); + IKARUS_FAIL_IF_OBJECT_MISSING(property, nullptr); + + auto * value = fetch_value_from_db( + entity->project, + error_out, + "SELECT IFNULL((SELECT `value` FROM `values` WHERE `entity` = ? AND `property` = ?), (SELECT `default_value` FROM `properties` WHERE `id` = ?))", + entity->id, + property->id, + property->id + ); + + IKARUS_FAIL_IF_ERROR(nullptr); + + return new IkarusEntityPropertyValue{ + .entity = entity, + .property = property, + .value = value, + }; +} + +void ikarus_entity_set_value( + IkarusEntity * entity, + struct IkarusProperty const * property, + struct IkarusEntityPropertyValue * value, + IkarusErrorData * error_out +) { + IKARUS_FAIL_IF_NULL(entity, ); + IKARUS_FAIL_IF_OBJECT_MISSING(entity, ); + IKARUS_FAIL_IF_NULL(property, ); + IKARUS_FAIL_IF_OBJECT_MISSING(property, ); + IKARUS_FAIL_IF_NULL(value, ); + + auto value_json_str = boost::json::serialize(value->value->to_json()); + + IKARUS_TRYRV_OR_FAIL( + , + "unable to set entity property value: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->execute( + "INSERT INTO `values`(`entity`, `property`, `value`) VALUES(?, ?, ?) ON CONFLICT(`entity`, `property`) DO UPDATE SET `value` = ?", + entity->id, + property->id, + value_json_str, + value_json_str + ) + ); +} + +// No existence checks here for performance reasons. All methods on IkarusObject perform this check anyway. + +struct IkarusObject * ikarus_entity_to_object(IkarusEntity * entity, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(entity, nullptr); + + return static_cast(entity); +} + +struct IkarusObject const * ikarus_entity_to_object_const(IkarusEntity const * entity, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(entity, nullptr); + + return static_cast(entity); +} diff --git a/src/objects/object.cpp b/src/objects/object.cpp index 204c7a7..2f52b48 100644 --- a/src/objects/object.cpp +++ b/src/objects/object.cpp @@ -1,5 +1,109 @@ #include "object.hpp" +#include + +#include + +#include +#include +#include +#include +#include + IkarusObject::IkarusObject(IkarusProject * project, IkarusId id): project{project}, id{id} {} + +IkarusProject * ikarus_object_get_project(IkarusObject const * object, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(object, nullptr); + IKARUS_FAIL_IF_OBJECT_MISSING(object, nullptr); + + return object->project; +} + +bool ikarus_object_is_equal(IkarusObject const * lhs, IkarusObject const * rhs, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(lhs, false); + IKARUS_FAIL_IF_OBJECT_MISSING(lhs, false); + IKARUS_FAIL_IF_NULL(rhs, false); + IKARUS_FAIL_IF_OBJECT_MISSING(rhs, false); + + return lhs->id == rhs->id; +} + +void ikarus_object_visit( + IkarusObject * object, + void (*blueprint_visitor)(struct IkarusBlueprint *, void *), + void (*property_visitor)(struct IkarusProperty *, void *), + void (*entity_visitor)(struct IkarusEntity *, void *), + void * data, + IkarusErrorData * error_out +) { + struct Data { + void (*blueprint_visitor)(struct IkarusBlueprint *, void *); + void (*property_visitor)(struct IkarusProperty *, void *); + void (*entity_visitor)(struct IkarusEntity *, void *); + void * data; + }; + + Data passthru_data{ + blueprint_visitor, + property_visitor, + entity_visitor, + data, + }; + + ikarus_object_visit_const( + object, + [](IkarusBlueprint const * blueprint, void * data) { + auto const * passthru_data = static_cast(data); + passthru_data->blueprint_visitor(const_cast(blueprint), passthru_data->data); + }, + [](IkarusProperty const * property, void * data) { + auto const * passthru_data = static_cast(data); + passthru_data->property_visitor(const_cast(property), passthru_data->data); + }, + [](IkarusEntity const * entity, void * data) { + auto const * passthru_data = static_cast(data); + passthru_data->entity_visitor(const_cast(entity), passthru_data->data); + }, + &passthru_data, + error_out + ); +} + +void ikarus_object_visit_const( + IkarusObject const * object, + void (*blueprint_visitor)(struct IkarusBlueprint const *, void *), + void (*property_visitor)(struct IkarusProperty const *, void *), + void (*entity_visitor)(struct IkarusEntity const *, void *), + void * data, + IkarusErrorData * error_out +) { + IKARUS_FAIL_IF_NULL(object, ); + IKARUS_FAIL_IF_OBJECT_MISSING(object, ); + + switch (ikarus_id_get_object_type(object->id)) { + case IkarusObjectType_Entity: { + auto const * entity = dynamic_cast(object); + IKARUS_FAIL_IF(entity == nullptr, , "object with entity id wasn't a entity", IkarusErrorInfo_LibIkarus_InvalidState); + entity_visitor(entity, data); + } + case IkarusObjectType_Blueprint: { + auto const * blueprint = dynamic_cast(object); + IKARUS_FAIL_IF(blueprint == nullptr, , "object with blueprint id wasn't a blueprint", IkarusErrorInfo_LibIkarus_InvalidState); + blueprint_visitor(blueprint, data); + } + case IkarusObjectType_Property: { + auto const * property = dynamic_cast(object); + IKARUS_FAIL_IF(property == nullptr, , "object with property id wasn't a property", IkarusErrorInfo_LibIkarus_InvalidState); + property_visitor(property, data); + } + default: { + IKARUS_FAIL( + , + fmt::format("unknown object type: {}", ikarus_object_type_to_string(ikarus_id_get_object_type(object->id))), + IkarusErrorInfo_LibIkarus_InvalidState + ); + } + } +} diff --git a/src/objects/object.hpp b/src/objects/object.hpp index c51ddbb..56e141b 100644 --- a/src/objects/object.hpp +++ b/src/objects/object.hpp @@ -1,9 +1,5 @@ #pragma once -#include - -#include - #include struct IkarusObject { diff --git a/src/objects/properties/number_property.cpp b/src/objects/properties/number_property.cpp index d06cfb5..9d0fe13 100644 --- a/src/objects/properties/number_property.cpp +++ b/src/objects/properties/number_property.cpp @@ -1,4 +1,33 @@ #include "number_property.hpp" +#include + +#include + +#include +#include +#include + IkarusNumberProperty::IkarusNumberProperty(IkarusProject * project, IkarusId id): IkarusProperty{project, id, this} {} + +IkarusNumberProperty * ikarus_number_property_create( + struct IkarusProject * project, + char const * name, + struct IkarusPropertySource * property_source, + IkarusErrorData * error_out +) { + return ikarus::util::create_property(project, name, property_source, error_out); +} + +IkarusNumberValue * ikarus_number_property_get_default_value(IkarusNumberProperty * property, IkarusErrorData * error_out) { + return ikarus::util::get_default_value(property, error_out); +} + +void ikarus_number_property_set_default_value( + IkarusNumberProperty * property, + IkarusNumberValue * new_default_value, + IkarusErrorData * error_out +) { + ikarus::util::set_default_value(property, new_default_value, error_out); +} diff --git a/src/objects/properties/number_property.hpp b/src/objects/properties/number_property.hpp index df2ab7e..22ca3d3 100644 --- a/src/objects/properties/number_property.hpp +++ b/src/objects/properties/number_property.hpp @@ -1,8 +1,15 @@ #pragma once -#include +#include + +#include +#include + +struct IkarusNumberProperty : IkarusProperty { +public: + using value_type = IkarusNumberValue; + constexpr auto static PropertyType = IkarusPropertyType_Number; -struct IkarusNumberProperty final : IkarusProperty { public: IkarusNumberProperty(struct IkarusProject * project, IkarusId id); }; diff --git a/src/objects/properties/property.cpp b/src/objects/properties/property.cpp index 498e321..d3c27be 100644 --- a/src/objects/properties/property.cpp +++ b/src/objects/properties/property.cpp @@ -2,8 +2,10 @@ #include +#include #include +#include #include #include #include @@ -11,101 +13,66 @@ IkarusProperty::IkarusProperty(IkarusProject * project, IkarusId id, Data data): IkarusObject{project, id}, - _data{data} {} + data{data} {} -IkarusProperty::Data & IkarusProperty::get_data() { - return _data; -} +IKA_API void ikarus_property_delete(IkarusProperty * property, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(property, ); + IKARUS_FAIL_IF_OBJECT_MISSING(property, ); -IkarusProperty::Data const & IkarusProperty::get_data() const { - return _data; -} - -cppbase::Result IkarusProperty::get_property_type(IkarusProject * project, IkarusId id) { - VTRY( - auto const type, - project->db->query_one("SELECT `type` FROM `properties` WHERE `id` = ?", id).on_error([project](auto const & err) { - project->set_error( - fmt::format("failed to fetch unboxed property type: {}", err), - true, - IkarusErrorInfo_Source_SubSystem, - IkarusErrorInfo_Type_SubSystem_Database - ); - }) - ); - - return cppbase::ok(static_cast(type)); -} - -IKA_API void ikarus_property_delete(IkarusProperty * property) { - TRYRV( + IKARUS_TRYRV_OR_FAIL( , - property->project->db->execute("DELETE FROM `objects` WHERE `id` = ?", property->id).on_error([property](auto const & err) { - property->project->set_error( - fmt::format("failed to delete property from objects table: {}", err), - true, - IkarusErrorInfo_Source_SubSystem, - IkarusErrorInfo_Type_SubSystem_Database - ); - }) + "unable to delete property: {}", + IkarusErrorInfo_Database_QueryFailed, + property->project->db->execute("DELETE FROM `objects` WHERE `id` = ?", property->id) ); property->project->uncache(property); } -IkarusPropertyType ikarus_property_get_type(IkarusProperty const * property) { - return IkarusProperty::get_property_type(property->project, property->id).unwrap_value_or(IkarusPropertyType_Toggle); +IkarusPropertyType ikarus_property_get_type(IkarusProperty const * property, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(property, IkarusPropertyType_Toggle); + IKARUS_FAIL_IF_OBJECT_MISSING(property, IkarusPropertyType_Toggle); + + IKARUS_VTRYRV_OR_FAIL( + auto const ret, + IkarusPropertyType_Toggle, + "unable to fetch property type from database: {}", + IkarusErrorInfo_Database_QueryFailed, + property->project->db->query_one("SELECT `type` FROM `properties` WHERE `id` = ?", property->id) + ); + + return static_cast(ret); } -IkarusPropertySource const * ikarus_property_get_source(IkarusProperty const * property) { - VTRYRV( +IkarusPropertySource const * ikarus_property_get_source(IkarusProperty const * property, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(property, nullptr); + IKARUS_FAIL_IF_OBJECT_MISSING(property, nullptr); + + IKARUS_VTRYRV_OR_FAIL( auto const source, nullptr, - property->project->db - ->query_one("SELECT `source` FROM `properties` WHERE `id` = ?", property->id) - .on_error([property](auto const & err) { - property->project->set_error( - fmt::format("failed to fetch property's source: {}", err), - true, - IkarusErrorInfo_Source_SubSystem, - IkarusErrorInfo_Type_SubSystem_Database - ); - }) + "unable to fetch property source from database: {}", + IkarusErrorInfo_Database_QueryFailed, + property->project->db->query_one("SELECT `source` FROM `properties` WHERE `id` = ?", property->id) ); switch (ikarus_id_get_object_type(source)) { case IkarusObjectType_Blueprint: return new IkarusPropertySource{property->project->get_blueprint(source)}; case IkarusObjectType_Entity: return new IkarusPropertySource{property->project->get_entity(source)}; - default: { - property->project->set_error( - fmt::format("PropertySource is neither blueprint nor entity"), - true, - IkarusErrorInfo_Source_LibIkarus, - IkarusErrorInfo_Type_LibIkarus_InvalidState + default: + IKARUS_FAIL( + nullptr, + fmt::format("invalid property source type: {}", ikarus_object_type_to_string(ikarus_id_get_object_type(source))), + IkarusErrorInfo_LibIkarus_InvalidState ); - - return nullptr; - } } } -IkarusValue * ikarus_property_get_default_value(IkarusProperty const * property) { - VTRYRV( - auto const value, - nullptr, - property->project->db - ->query_one("SELECT `default_value` FROM `properties` WHERE `id` = ?", property->id) - .on_error([property](auto const & err) { - property->project->set_error( - fmt::format("failed to fetch property's default value: {}", err), - true, - IkarusErrorInfo_Source_SubSystem, - IkarusErrorInfo_Type_SubSystem_Database - ); - }) - ); +IkarusValue * ikarus_property_get_default_value(IkarusProperty const * property, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(property, nullptr); + IKARUS_FAIL_IF_OBJECT_MISSING(property, nullptr); - return IkarusValue::from_json(value).unwrap_value_or(nullptr); + return fetch_value_from_db(property->project, error_out, "SELECT `default_value` FROM `properties` WHERE `id` = ?", property->id); } void ikarus_property_visit( @@ -113,15 +80,19 @@ void ikarus_property_visit( void (*toggle_property_visitor)(struct IkarusToggleProperty *, void *), void (*number_property_visitor)(struct IkarusNumberProperty *, void *), void (*text_property_visitor)(struct IkarusTextProperty *, void *), - void * data + void * data, + IkarusErrorData * error_out ) { + IKARUS_FAIL_IF_NULL(property, ); + IKARUS_FAIL_IF_OBJECT_MISSING(property, ); + std::visit( cppbase::overloaded{ [toggle_property_visitor, data](IkarusToggleProperty * property) { toggle_property_visitor(property, data); }, [number_property_visitor, data](IkarusNumberProperty * property) { number_property_visitor(property, data); }, [text_property_visitor, data](IkarusTextProperty * property) { text_property_visitor(property, data); } }, - property->get_data() + property->data ); } @@ -130,18 +101,24 @@ void ikarus_property_visit_const( void (*toggle_property_visitor)(struct IkarusToggleProperty const *, void *), void (*number_property_visitor)(struct IkarusNumberProperty const *, void *), void (*text_property_visitor)(struct IkarusTextProperty const *, void *), - void * data + void * data, + IkarusErrorData * error_out ) { + IKARUS_FAIL_IF_NULL(property, ); + IKARUS_FAIL_IF_OBJECT_MISSING(property, ); + std::visit( cppbase::overloaded{ [toggle_property_visitor, data](IkarusToggleProperty const * property) { toggle_property_visitor(property, data); }, [number_property_visitor, data](IkarusNumberProperty const * property) { number_property_visitor(property, data); }, [text_property_visitor, data](IkarusTextProperty const * property) { text_property_visitor(property, data); } }, - property->get_data() + property->data ); } +// No existence checks here for performance reasons. All methods on IkarusObject perform this check anyway. + IkarusObject * ikarus_property_to_object(IkarusProperty * property) { return static_cast(property); } diff --git a/src/objects/properties/property.hpp b/src/objects/properties/property.hpp index efdb515..3a95d6b 100644 --- a/src/objects/properties/property.hpp +++ b/src/objects/properties/property.hpp @@ -2,23 +2,12 @@ #include -#include - -#include - -#include - #include struct IkarusProperty : IkarusObject { public: using Data = std::variant; -public: - /// \brief Helper to fetch a type for a property that isn't yet wrapped in an object - [[nodiscard]] static cppbase::Result - get_property_type(struct IkarusProject * project, IkarusId id); - public: IkarusProperty(struct IkarusProject * project, IkarusId id, Data data); @@ -31,9 +20,5 @@ public: ~IkarusProperty() override = default; public: - [[nodiscard]] Data & get_data(); - [[nodiscard]] Data const & get_data() const; - -private: - Data _data; + Data data; }; diff --git a/src/objects/properties/property_source.cpp b/src/objects/properties/property_source.cpp index 364ca84..3e31bb3 100644 --- a/src/objects/properties/property_source.cpp +++ b/src/objects/properties/property_source.cpp @@ -3,10 +3,22 @@ #include #include +#include +#include IkarusPropertySource::IkarusPropertySource(Data data): data{data} {} +IkarusId IkarusPropertySource::get_id() const { + return boost::variant2::visit( + cppbase::overloaded { + [](IkarusBlueprint const * blueprint) { return blueprint->id; }, + [](IkarusEntity const * entity) { return entity->id; } + }, + data + ); +} + IkarusPropertySource * ikarus_property_source_create_blueprint(IkarusBlueprint * blueprint) { return new IkarusPropertySource{blueprint}; } diff --git a/src/objects/properties/property_source.hpp b/src/objects/properties/property_source.hpp index a98d664..672021d 100644 --- a/src/objects/properties/property_source.hpp +++ b/src/objects/properties/property_source.hpp @@ -1,7 +1,7 @@ #pragma once #include - +#include #include struct IkarusPropertySource { @@ -19,6 +19,9 @@ public: virtual ~IkarusPropertySource() = default; +public: + [[nodiscard]] IkarusId get_id() const; + public: Data data; }; diff --git a/src/objects/properties/text_property.cpp b/src/objects/properties/text_property.cpp index fd64154..8b3b1a5 100644 --- a/src/objects/properties/text_property.cpp +++ b/src/objects/properties/text_property.cpp @@ -1,4 +1,33 @@ #include "text_property.hpp" +#include + +#include + +#include +#include +#include + IkarusTextProperty::IkarusTextProperty(IkarusProject * project, IkarusId id): IkarusProperty{project, id, this} {} + +IkarusTextProperty * ikarus_text_property_create( + struct IkarusProject * project, + char const * name, + struct IkarusPropertySource * property_source, + IkarusErrorData * error_out +) { + return ikarus::util::create_property(project, name, property_source, error_out); +} + +IkarusTextValue * ikarus_text_property_get_default_value(IkarusTextProperty * property, IkarusErrorData * error_out) { + return ikarus::util::get_default_value(property, error_out); +} + +void ikarus_text_property_set_default_value( + IkarusTextProperty * property, + IkarusTextValue * new_default_value, + IkarusErrorData * error_out +) { + ikarus::util::set_default_value(property, new_default_value, error_out); +} diff --git a/src/objects/properties/text_property.hpp b/src/objects/properties/text_property.hpp index 3a5d163..a3b7e97 100644 --- a/src/objects/properties/text_property.hpp +++ b/src/objects/properties/text_property.hpp @@ -1,8 +1,15 @@ #pragma once -#include +#include + +#include +#include + +struct IkarusTextProperty : IkarusProperty { +public: + using value_type = IkarusTextValue; + constexpr auto static PropertyType = IkarusPropertyType_Text; -struct IkarusTextProperty final : IkarusProperty { public: IkarusTextProperty(struct IkarusProject * project, IkarusId id); }; diff --git a/src/objects/properties/toggle_property.cpp b/src/objects/properties/toggle_property.cpp index db5105b..e0931fa 100644 --- a/src/objects/properties/toggle_property.cpp +++ b/src/objects/properties/toggle_property.cpp @@ -1,7 +1,33 @@ #include "toggle_property.hpp" +#include + +#include + +#include +#include +#include + IkarusToggleProperty::IkarusToggleProperty(IkarusProject * project, IkarusId id): IkarusProperty{project, id, this} {} -IkarusToggleProperty * -ikarus_toggle_property_create(struct IkarusProject * project, char const * name, struct IkarusPropertySource * property_source) {} +IkarusToggleProperty * ikarus_toggle_property_create( + struct IkarusProject * project, + char const * name, + struct IkarusPropertySource * property_source, + IkarusErrorData * error_out +) { + return ikarus::util::create_property(project, name, property_source, error_out); +} + +IkarusToggleValue * ikarus_toggle_property_get_default_value(struct IkarusToggleProperty * property, IkarusErrorData * error_out) { + return ikarus::util::get_default_value(property, error_out); +} + +void ikarus_toggle_property_set_default_value( + struct IkarusToggleProperty * property, + struct IkarusToggleValue * new_default_value, + IkarusErrorData * error_out +) { + ikarus::util::set_default_value(property, new_default_value, error_out); +} diff --git a/src/objects/properties/toggle_property.hpp b/src/objects/properties/toggle_property.hpp index 92bbbc4..247cbcb 100644 --- a/src/objects/properties/toggle_property.hpp +++ b/src/objects/properties/toggle_property.hpp @@ -1,8 +1,15 @@ #pragma once +#include + #include +#include struct IkarusToggleProperty : IkarusProperty { +public: + using value_type = IkarusToggleValue; + constexpr auto static PropertyType = IkarusPropertyType_Toggle; + public: IkarusToggleProperty(struct IkarusProject * project, IkarusId id); }; diff --git a/src/objects/properties/util.hpp b/src/objects/properties/util.hpp new file mode 100644 index 0000000..d44d14d --- /dev/null +++ b/src/objects/properties/util.hpp @@ -0,0 +1,100 @@ +#pragma once + +#include + +#include + +#include + +#include +#include +#include +#include +#include + +namespace ikarus::util { +template +T * create_property( + struct IkarusProject * project, + char const * name, + struct IkarusPropertySource * property_source, + IkarusErrorData * error_out +) { + IKARUS_FAIL_IF_NULL(project, nullptr); + IKARUS_FAIL_IF_NULL(name, nullptr); + IKARUS_FAIL_IF_NULL(property_source, nullptr); + IKARUS_FAIL_IF( + cppbase::is_empty_or_blank(name), + nullptr, + fmt::format("{} name cannot be empty or blank", boost::typeindex::type_id().pretty_name()), + IkarusErrorInfo_Client_InvalidInput + ) + + IKARUS_VTRYRV_OR_FAIL( + IkarusId const id, + nullptr, + "failed to create property: {}", + IkarusErrorInfo_Database_QueryFailed, + project->db->transact([name, property_source](auto * db) -> cppbase::Result { + TRY(db->execute("INSERT INTO `objects`(`type`, `name`) VALUES(?, ?, ?)", IkarusObjectType_Property, name)); + auto id = ikarus_id_from_data_and_type(db->last_insert_rowid(), IkarusObjectType_Property); + TRY(db->execute( + "INSERT INTO `properties`(`id`, `type`, `source`) VALUES(?, ?, ?)", + id, + T::PropertyType, + property_source->get_id() + )); + return cppbase::ok(id); + }) + ); + + auto * ret = dynamic_cast(project->get_property(id, T::PropertyType)); + + IKARUS_FAIL_IF( + ret == nullptr, + nullptr, + fmt::format("created {} cannot be casted down from IkarusProject", boost::typeindex::type_id().pretty_name()), + IkarusErrorInfo_LibIkarus_InvalidState + ); + + return ret; +} + +template +typename T::value_type * get_default_value(IkarusProperty const * property, IkarusErrorData * error_out) { + auto * value = ikarus_property_get_default_value(property, error_out); + IKARUS_FAIL_IF_ERROR(nullptr); + + auto * ret = boost::variant2::get_if(&value->data); + + IKARUS_FAIL_IF( + ret == nullptr, + nullptr, + fmt::format( + "{} default value is not a(n) {}", + boost::typeindex::type_id().pretty_name(), + boost::typeindex::type_id().pretty_name() + ), + IkarusErrorInfo_Database_InvalidState + ); + + return *ret; +} + +template +void set_default_value(T * property, typename T::value_type * new_default_value, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(property, ); + IKARUS_FAIL_IF_OBJECT_MISSING(property, ); + IKARUS_FAIL_IF_NULL(new_default_value, ); + + auto value_json_str = boost::json::serialize(new_default_value->to_json()); + + IKARUS_TRYRV_OR_FAIL( + , + "unable to set property default value: {}", + IkarusErrorInfo_Database_QueryFailed, + property->project->db->execute("UPDATE `properties` SET `default_value` = ? WHERE `id` = ?", value_json_str, property->id) + ); +} + +} // namespace ikarus::util diff --git a/src/persistence/migrations.hpp b/src/persistence/migrations.hpp new file mode 100644 index 0000000..03390d2 --- /dev/null +++ b/src/persistence/migrations.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include +#include + +#include + +namespace ikarus { +CPPBASE_ASSET(m1_initial_layout, "persistence/migrations/m1_initial_layout.sql"); + +class Migration : public sqlitecpp::Migration { +public: + Migration(char const * sql, size_t size): + sql{sql, size} {} + + ~Migration() override = default; + +public: + [[nodiscard]] inline auto get_content() const -> std::string_view override { + return sql; + } + +public: + std::string_view sql; +}; + +#define DECLARE_MIGRATION(name) std::make_unique(static_cast(name()), name##_size()) + +constexpr std::string_view DB_VERSION_KEY = "IKARUS_DB_VERSION"; +std::vector> const MIGRATIONS = []() { + std::vector> ret; + + ret.emplace_back(DECLARE_MIGRATION(m1_initial_layout)); + + return ret; +}(); + +} // namespace ikarus diff --git a/src/persistence/migrations/m0_genesis.sql b/src/persistence/migrations/m0_genesis.sql new file mode 100644 index 0000000..952645d --- /dev/null +++ b/src/persistence/migrations/m0_genesis.sql @@ -0,0 +1,7 @@ +CREATE TABLE `metadata` +( + `key` VARCHAR(255) NOT NULL, + `value` VARCHAR(255) NOT NULL, + + PRIMARY KEY (`key`) +) \ No newline at end of file diff --git a/src/persistence/project.cpp b/src/persistence/project.cpp index da56280..7ff2019 100644 --- a/src/persistence/project.cpp +++ b/src/persistence/project.cpp @@ -1,6 +1,12 @@ #include "project.hpp" -#include "ikarus/persistence/project.h" +#include "migrations.hpp" + +#include + +#include + +#include #include #include @@ -9,10 +15,10 @@ #include #include -IkarusProject::IkarusProject(std::string_view name, std::filesystem::path path): +IkarusProject::IkarusProject(std::string_view name, std::string_view path, std::unique_ptr && db): name{name}, - path{path}, - db{nullptr}, + path{std::move(path)}, + db{std::move(db)}, _blueprints{}, _properties{}, _entities{} {} @@ -52,3 +58,222 @@ auto IkarusProject::get_property(IkarusId id, IkarusPropertyType type) -> Ikarus auto IkarusProject::uncache(IkarusProperty * property) -> void { remove_cached_object(property, _properties); } + +IkarusProject * ikarus_project_create(char const * path, char const * name, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(path, nullptr); + IKARUS_FAIL_IF_NULL(name, nullptr); + IKARUS_FAIL_IF(cppbase::is_empty_or_blank(path), nullptr, "path must not be empty", IkarusErrorInfo_Client_InvalidInput); + IKARUS_FAIL_IF(cppbase::is_empty_or_blank(name), nullptr, "name must not be empty", IkarusErrorInfo_Client_InvalidInput); + + boost::filesystem::path fs_path{path}; + + { + boost::system::error_code ec; + bool const exists = fs::exists(fs_path, ec); + + IKARUS_FAIL_IF(ec, nullptr, "unable to check whether path is occupied", IkarusErrorInfo_Filesystem_AlreadyExists); + IKARUS_FAIL_IF(exists, nullptr, "path is already occupied", IkarusErrorInfo_Filesystem_AlreadyExists); + } + + IKARUS_VTRYRV_OR_FAIL( + auto db, + nullptr, + "failed to create project db: {}", + IkarusErrorInfo_Database_ConnectionFailed, + sqlitecpp::Connection::open(path) + ); + + IKARUS_TRYRV_OR_FAIL( + nullptr, + "failed to migrate project db: {}", + IkarusErrorInfo_Database_MigrationFailed, + db->migrate(ikarus::MIGRATIONS) + ); + + if (auto res = db->execute("INSERT INTO `metadata`(`key`, `value`) VALUES(?, ?)", DB_PROJECT_NAME_KEY, name); res.is_error()) { + boost::system::error_code ec; + fs::remove(fs_path, ec); + + IKARUS_FAIL_IF( + ec, + nullptr, + "failed to remove project db after being unable to insert name into metadata table: {}", + IkarusErrorInfo_Filesystem_AccessIssue + ); + + IKARUS_FAIL(nullptr, "failed to insert project name into metadata: {}", IkarusErrorInfo_Database_QueryFailed); + } + + return new IkarusProject{name, path, std::move(db)}; +} + +IkarusProject * ikarus_project_create_in_memory(char const * name, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(name, nullptr); + IKARUS_FAIL_IF(cppbase::is_empty_or_blank(name), nullptr, "name must not be empty", IkarusErrorInfo_Client_InvalidInput); + + IKARUS_VTRYRV_OR_FAIL( + auto db, + nullptr, + "failed to create project db in memory: {}", + IkarusErrorInfo_Database_ConnectionFailed, + sqlitecpp::Connection::open_in_memory() + ); + + IKARUS_TRYRV_OR_FAIL( + nullptr, + "failed to migrate project db: {}", + IkarusErrorInfo_Database_MigrationFailed, + db->migrate(ikarus::MIGRATIONS) + ); + + IKARUS_TRYRV_OR_FAIL( + nullptr, + "failed to insert project name into metadata: {}", + IkarusErrorInfo_Database_QueryFailed, + db->execute("INSERT INTO `metadata`(`key`, `value`) VALUES(?, ?)", DB_PROJECT_NAME_KEY, name) + ); + + return new IkarusProject{name, ":memory:", std::move(db)}; +} + +IkarusProject * ikarus_project_open(char const * path, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(path, nullptr); + IKARUS_FAIL_IF(cppbase::is_empty_or_blank(path), nullptr, "path must not be empty", IkarusErrorInfo_Client_InvalidInput); + + IKARUS_VTRYRV_OR_FAIL( + auto db, + nullptr, + "failed to open project db: {}", + IkarusErrorInfo_Database_ConnectionFailed, + sqlitecpp::Connection::open(path) + ); + + IKARUS_TRYRV_OR_FAIL( + nullptr, + "failed to migrate project db: {}", + IkarusErrorInfo_Database_MigrationFailed, + db->migrate(ikarus::MIGRATIONS) + ); + + IKARUS_VTRYRV_OR_FAIL( + auto const & name, + nullptr, + "failed to retrieve project name from metadata: {}", + IkarusErrorInfo_Database_QueryFailed, + db->query_one("SELECT `value` FROM `metadata` WHERE `key` = ?", DB_PROJECT_NAME_KEY) + ); + + return new IkarusProject{name, path, std::move(db)}; +} + +char const * ikarus_project_get_name(IkarusProject const * project, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(project, nullptr); + + return project->name.data(); +} + +void ikarus_project_set_name(IkarusProject * project, char const * new_name, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(project, ); + IKARUS_FAIL_IF_NULL(new_name, ); + + IKARUS_FAIL_IF(cppbase::is_empty_or_blank(new_name), , "name must not be empty", IkarusErrorInfo_Client_InvalidInput); + + IKARUS_TRYRV_OR_FAIL( + , + "failed to update project name: {}", + IkarusErrorInfo_Database_QueryFailed, + project->db->execute("UPDATE `metadata` SET `value` = ? WHERE `key` = ?", new_name, DB_PROJECT_NAME_KEY) + ); +} + +char const * ikarus_project_get_path(IkarusProject const * project, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(project, nullptr); + + return project->path.data(); +} + +// these take a mutable project right now because the get_cached-* function must be mutable +// since we insert a backreference to the project into the objects, not ideal, +// but fine for now since "mutability" is a vague concept for projects anyway + +void ikarus_project_get_blueprints( + IkarusProject * project, + struct IkarusBlueprint ** blueprints_out, + size_t blueprints_out_size, + IkarusErrorData * error_out +) { + IKARUS_FAIL_IF_NULL(project, ); + IKARUS_FAIL_IF_NULL(blueprints_out, ); + + if (blueprints_out_size == 0) { + return; + } + + IkarusId ids[blueprints_out_size]; + + IKARUS_TRYRV_OR_FAIL( + , + "unable to fetch project blueprints from database: {}", + IkarusErrorInfo_Database_QueryFailed, + project->db->query_many_buffered("SELECT `id` FROM `blueprints`", ids, blueprints_out_size) + ); + + for (size_t i = 0; i < blueprints_out_size; ++i) { + blueprints_out[i] = project->get_blueprint(ids[i]); + } +} + +size_t ikarus_project_get_blueprint_count(IkarusProject const * project, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(project, 0); + + IKARUS_VTRYRV_OR_FAIL( + auto const ret, + 0, + "unable to fetch project blueprint count from database: {}", + IkarusErrorInfo_Database_QueryFailed, + project->db->query_one("SELECT COUNT(*) FROM `blueprints`") + ); + + return ret; +} + +void ikarus_project_get_entities( + IkarusProject * project, + struct IkarusEntity ** entities_out, + size_t entities_out_size, + IkarusErrorData * error_out +) { + IKARUS_FAIL_IF_NULL(project, ); + IKARUS_FAIL_IF_NULL(entities_out, ); + + if (entities_out_size == 0) { + return; + } + + IkarusId ids[entities_out_size]; + + IKARUS_TRYRV_OR_FAIL( + , + "unable to fetch project entities from database: {}", + IkarusErrorInfo_Database_QueryFailed, + project->db->query_many_buffered("SELECT `id` FROM `entities`", ids, entities_out_size) + ); + + for (size_t i = 0; i < entities_out_size; ++i) { + entities_out[i] = project->get_entity(ids[i]); + } +} + +size_t ikarus_project_get_entity_count(IkarusProject * project, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(project, 0); + + IKARUS_VTRYRV_OR_FAIL( + auto const ret, + 0, + "unable to fetch project entity count from database: {}", + IkarusErrorInfo_Database_QueryFailed, + project->db->query_one("SELECT COUNT(*) FROM `entities`") + ); + + return ret; +} diff --git a/src/persistence/project.hpp b/src/persistence/project.hpp index f113f24..f5a00a1 100644 --- a/src/persistence/project.hpp +++ b/src/persistence/project.hpp @@ -1,20 +1,23 @@ #pragma once -#include #include #include #include +#include + #include #include #include #include +namespace fs = boost::filesystem; + /// \private struct IkarusProject { public: - IkarusProject(std::string_view name, std::filesystem::path path); + IkarusProject(std::string_view name, std::string_view path, std::unique_ptr && db); public: [[nodiscard]] auto get_blueprint(IkarusId id) -> struct IkarusBlueprint *; @@ -23,6 +26,7 @@ public: [[nodiscard]] auto get_entity(IkarusId id) -> struct IkarusEntity *; auto uncache(struct IkarusEntity * entity) -> void; + // TODO improve this to take a template param so that we don't have to cast in e.g. ikarus_toggle_property_create [[nodiscard]] auto get_property(IkarusId id, IkarusPropertyType type) -> struct IkarusProperty *; auto uncache(struct IkarusProperty * property) -> void; @@ -45,11 +49,13 @@ private: public: std::string name; - std::filesystem::path path; + std::string_view path; std::unique_ptr db; private: - std::unordered_map> _blueprints; - std::unordered_map> _properties; - std::unordered_map> _entities; + std::unordered_map> mutable _blueprints; + std::unordered_map> mutable _properties; + std::unordered_map> mutable _entities; }; + +constexpr std::string_view DB_PROJECT_NAME_KEY = "PROJECT_NAME"; diff --git a/src/values/entity_property_value.cpp b/src/values/entity_property_value.cpp new file mode 100644 index 0000000..d1580ea --- /dev/null +++ b/src/values/entity_property_value.cpp @@ -0,0 +1,21 @@ +#include "values/entity_property_value.hpp" + +#include + +IkarusEntity const * ikarus_entity_property_value_get_entity(IkarusEntityPropertyValue const * value, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(value, nullptr); + + return value->entity; +} + +IkarusProperty const * ikarus_entity_property_value_get_property(IkarusEntityPropertyValue const * value, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(value, nullptr); + + return value->property; +} + +IkarusValue const * ikarus_entity_property_value_get_value(IkarusEntityPropertyValue const * value, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(value, nullptr); + + return value->value; +} diff --git a/src/values/entity_property_value.hpp b/src/values/entity_property_value.hpp new file mode 100644 index 0000000..f75d6ba --- /dev/null +++ b/src/values/entity_property_value.hpp @@ -0,0 +1,7 @@ +#pragma once + +struct IkarusEntityPropertyValue { + struct IkarusEntity const * entity; + struct IkarusProperty const * property; + struct IkarusValue const * value; +}; diff --git a/src/values/value.cpp b/src/values/value.cpp index cf9a270..812b2c8 100644 --- a/src/values/value.cpp +++ b/src/values/value.cpp @@ -19,7 +19,7 @@ IkarusValue::IkarusValue(Data data): data(data) {} -cppbase::Result IkarusValue::from_json(boost::json::value const & json) { +cppbase::Result IkarusValue::from_json(boost::json::value json) { if (auto const * obj = json.if_object(); obj == nullptr) { return cppbase::err(FromJsonError{}); } else { diff --git a/src/values/value.hpp b/src/values/value.hpp index adc27ca..94b423d 100644 --- a/src/values/value.hpp +++ b/src/values/value.hpp @@ -5,6 +5,9 @@ #include +#include +#include + struct IkarusValue { public: using Data = boost::variant2::variant; @@ -24,9 +27,50 @@ public: public: struct FromJsonError {}; - [[nodiscard]] static cppbase::Result from_json(boost::json::value const & json); + [[nodiscard]] static cppbase::Result from_json(boost::json::value json); [[nodiscard]] boost::json::value to_json() const; public: Data data; }; + +template<> +struct fmt::formatter { + template + constexpr static auto parse(FormatParseContext & ctx) { + return ctx.end(); + } + + constexpr static auto format([[maybe_unused]]IkarusValue::FromJsonError const & error, fmt::format_context & ctx) { + return fmt::format_to(ctx.out(), "unable to parse ikarus value JSON"); + } +}; + +template +IkarusValue * fetch_value_from_db(IkarusProject * project, IkarusErrorData * error_out, std::string_view query, Args &&... args) { + IKARUS_VTRYRV_OR_FAIL( + auto const value_str, + nullptr, + "unable to fetch entity property value from database: {}", + IkarusErrorInfo_Database_QueryFailed, + project->db->query_one(query, std::forward(args)...) + ); + + boost::json::error_code ec{}; + boost::json::value json_value = boost::json::parse(value_str, ec); + + if (ec) { + IKARUS_SET_ERROR(fmt::format("invalid json is stored in database: {}", ec.message()), IkarusErrorInfo_Database_InvalidState); + return nullptr; + } + + IKARUS_VTRYRV_OR_FAIL( + auto const ret, + nullptr, + "unable to fetch entity property value: {}", + IkarusErrorInfo_LibIkarus_CannotPerformOperation, + IkarusValue::from_json(std::move(json_value)) + ); + + return ret; +} diff --git a/vendor/sqlitecpp b/vendor/sqlitecpp index 00a1afc..e837d64 160000 --- a/vendor/sqlitecpp +++ b/vendor/sqlitecpp @@ -1 +1 @@ -Subproject commit 00a1afcc5f564f562c436f1ddfa4f44bb6489b17 +Subproject commit e837d64405fb43adefe03994837f5ed5793138c5