From e6233cf3f2afdf2dd2342613e65d6f2eff4336de Mon Sep 17 00:00:00 2001 From: Folling Date: Thu, 2 Jan 2025 09:39:08 +0100 Subject: [PATCH] finalize entity functions --- .clang-format | 2 +- include/ikarus/objects/entity.h | 41 +- src/ikarus/errors.hpp | 11 +- src/ikarus/objects/blueprint.cpp | 4 + src/ikarus/objects/blueprint.hpp | 16 +- src/ikarus/objects/entity.cpp | 385 ++++++++++++++++-- src/ikarus/objects/entity.hpp | 10 +- src/ikarus/objects/property.cpp | 9 +- src/ikarus/objects/property.hpp | 16 +- .../migrations/m0_initial_layout.sql | 5 +- src/ikarus/persistence/project.cpp | 18 +- src/ikarus/values/data.cpp | 21 +- src/ikarus/values/data.hpp | 3 +- src/ikarus/values/errors.hpp | 81 ++++ src/ikarus/values/schema.cpp | 30 +- src/ikarus/values/schema.hpp | 3 +- src/ikarus/values/value.cpp | 37 +- src/ikarus/values/value.hpp | 12 +- 18 files changed, 523 insertions(+), 181 deletions(-) diff --git a/.clang-format b/.clang-format index 6d3560a..5456c6f 100644 --- a/.clang-format +++ b/.clang-format @@ -64,7 +64,7 @@ BreakBeforeInlineASMColon: OnlyMultiline BreakBeforeTernaryOperators: false BreakConstructorInitializers: AfterColon BreakInheritanceList: AfterColon -BreakStringLiterals: true +BreakStringLiterals: false ColumnLimit: 80 CommentPragmas: '^\\.+' diff --git a/include/ikarus/objects/entity.h b/include/ikarus/objects/entity.h index 0481da7..172bad1 100644 --- a/include/ikarus/objects/entity.h +++ b/include/ikarus/objects/entity.h @@ -181,8 +181,6 @@ IKA_API void ikarus_entity_link_blueprint( enum IkarusEntityUnlinkBlueprintFlags { /// \brief No flags. IkarusEntityUnlinkBlueprintFlags_None = 0, - /// \brief Keep the values associated with the blueprint, transforming them into entity values. - IkarusEntityUnlinkBlueprintFlags_KeepValues = 1, }; /// \brief Unlinks an entity from a blueprint. @@ -203,17 +201,27 @@ IKA_API void ikarus_entity_unlink_blueprint( IkarusErrorData * error_out ); +/// \brief Struct for an entity value. +struct IkarusEntityValue { + /// \brief The name of the value. + char const * name; + /// \brief The value in json format. \see value.h + char const * value; +}; + /// \brief Gets the values of an entity. /// \param entity The entity to get the values of. /// \pre \li Must not be null. /// \pre \li Must exist. +/// \param size_out An out parameter for the number of values in the returned array +/// or undefined if an error occurs. /// \param error_out \see errors.h -/// \return The values, in json format of or null if an error occurs. -/// The json representation is an array of objects with the following keys: -/// - `name`: The name of the value. -/// - `value`: The value itself. \see value.h -IKA_API char const * -ikarus_entity_get_values(IkarusEntity * entity, IkarusErrorData * error_out); +/// \return The values or null if an error occurs. +IKA_API IkarusEntityValue * ikarus_entity_get_values( + IkarusEntity * entity, + size_t * size_out, + IkarusErrorData * error_out +); /// \brief Gets a value of an entity. /// \param entity The entity to get the value of. @@ -279,16 +287,25 @@ IKA_API void ikarus_entity_delete_value( IkarusErrorData * error_out ); +/// \brief Struct for an entity property value. +struct IkarusEntityPropertyValue { + /// \brief The property. + struct IkarusProperty * property; + /// \brief The value in json format. \see value.h + char const * value; +}; + /// \brief Gets the property values of an entity. /// \param entity The entity to get the property values of. /// \pre \li Must not be null. /// \pre \li Must exist. +/// \param size_out An out parameter for the number of property values in the +/// returned array or undefined if an error occurs. /// \param error_out \see errors.h -/// \return The property values, in msgpack format of or null if an error occurs. -/// The format is a map of property pointers (as integers) to values. \see -/// value.h -IKA_API char const * ikarus_entity_get_property_values( +/// \return The property values, or null if an error occurs. +IKA_API IkarusEntityPropertyValue * ikarus_entity_get_property_values( IkarusEntity * entity, + size_t * size_out, IkarusErrorData * error_out ); diff --git a/src/ikarus/errors.hpp b/src/ikarus/errors.hpp index 55b3e8f..a9daa9d 100644 --- a/src/ikarus/errors.hpp +++ b/src/ikarus/errors.hpp @@ -11,6 +11,8 @@ void safe_strcpy( size_t const dest_size ); +#define IKARUS_VOID_RETURN + #define IKARUS_SET_ERROR(msg, err_info) \ if (error_out != nullptr) { \ safe_strcpy( \ @@ -140,9 +142,9 @@ void safe_strcpy( IkarusErrorInfo_Client_InvalidInput \ ); -#define IKARUS_FAIL_IF_NOT_EXIST(object, ret) \ +#define IKARUS_FAIL_IF_NOT_EXIST_IMPL(exists_name, object, ret) \ IKARUS_VTRYRV_OR_FAIL( \ - auto exists, \ + auto exists_name, \ ret, \ fmt::format( \ "failed to check if {} exists", \ @@ -159,7 +161,7 @@ void safe_strcpy( ); \ \ IKARUS_FAIL_IF( \ - !exists, \ + !exists_name, \ ret, \ fmt::format( \ "{} doesn't exist", \ @@ -167,3 +169,6 @@ void safe_strcpy( ), \ IkarusErrorInfo_Client_NonExistent \ ) + +#define IKARUS_FAIL_IF_NOT_EXIST(object, ret) \ + IKARUS_FAIL_IF_NOT_EXIST_IMPL(CPPBASE_UNIQUE_NAME(exists), object, ret) diff --git a/src/ikarus/objects/blueprint.cpp b/src/ikarus/objects/blueprint.cpp index c25961f..553bbe2 100644 --- a/src/ikarus/objects/blueprint.cpp +++ b/src/ikarus/objects/blueprint.cpp @@ -3,3 +3,7 @@ #include #include #include + +IkarusBlueprint::IkarusBlueprint(struct IkarusProject * project, int64_t id): + project{project}, + id{id} {} diff --git a/src/ikarus/objects/blueprint.hpp b/src/ikarus/objects/blueprint.hpp index b56143f..64cd9ea 100644 --- a/src/ikarus/objects/blueprint.hpp +++ b/src/ikarus/objects/blueprint.hpp @@ -3,21 +3,11 @@ #include struct IkarusBlueprint { - consteval static inline auto OBJECT_NAME() -> std::string_view { - return "blueprint"; - } + constinit static inline auto object_name = "blueprint"; + constinit static inline auto table_name = "blueprints"; - consteval static inline auto TABLE_NAME() -> std::string_view { - return "blueprints"; - } - - IkarusBlueprint( - struct IkarusProject * project, - int64_t id, - std::string_view name - ); + IkarusBlueprint(struct IkarusProject * project, int64_t id); struct IkarusProject * project; int64_t id; - std::string name; }; diff --git a/src/ikarus/objects/entity.cpp b/src/ikarus/objects/entity.cpp index 47f1f90..5abd085 100644 --- a/src/ikarus/objects/entity.cpp +++ b/src/ikarus/objects/entity.cpp @@ -1,18 +1,15 @@ #include "entity.hpp" +#include + #include #include #include #include -IkarusEntity::IkarusEntity( - struct IkarusProject * project, - int64_t id, - std::string_view name -): +IkarusEntity::IkarusEntity(struct IkarusProject * project, int64_t id): project{project}, - id{id}, - name{name} {} + id{id} {} IkarusEntity * ikarus_entity_create( struct IkarusProject * project, @@ -31,7 +28,7 @@ IkarusEntity * ikarus_entity_create( ); auto const id = project->db->last_insert_rowid(); - return new IkarusEntity{project, id, name}; + return new IkarusEntity{project, id}; } void ikarus_entity_delete( @@ -39,12 +36,11 @@ void ikarus_entity_delete( IkarusEntityDeleteFlags flags, IkarusErrorData * error_out ) { - IKARUS_FAIL_IF_NULL(entity, ); - IKARUS_FAIL_IF_NOT_EXIST(entity, ); - IKARUS_FAIL_IF_NULL(entity->project, ); + IKARUS_FAIL_IF_NULL(entity, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NOT_EXIST(entity, IKARUS_VOID_RETURN); IKARUS_TRYRV_OR_FAIL( - , + IKARUS_VOID_RETURN, "failed to delete entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db @@ -61,7 +57,6 @@ IkarusEntity * ikarus_entity_copy( ) { IKARUS_FAIL_IF_NULL(entity, nullptr); IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); - IKARUS_FAIL_IF_NULL(entity->project, nullptr); IKARUS_VTRYRV_OR_FAIL( auto id, @@ -72,34 +67,28 @@ IkarusEntity * ikarus_entity_copy( [entity](auto * db) -> cppbase::Result { TRY(entity->project->db->execute( - "INSERT INTO `entities`(`name`) VALUES(?)", - entity->name.data() + "INSERT INTO `entities`(`name`) " + "SELECT `name` FROM `entities` WHERE `id` = ?", + entity->id )); TRY(entity->project->db->execute( - "INSERT INTO `entity_values`(`entity`, `name`, `value`)" - " SELECT ?1, `name`, `value` FROM `entity_values` WHERE " + "INSERT INTO `entity_values`(`entity`, `name`, `value`) " + "SELECT ?1, `name`, `value` FROM `entity_values` WHERE " "`entity` = ?1", entity->id )) TRY(entity->project->db->execute( - "INSERT INTO `entity_property_values`(" - " `entity`, " - " `property`," - " `value`" - ") " - "SELECT ?1, `property`, `value` FROM " - "`entity_property_values` " + "INSERT INTO `entity_property_values`(`entity`, `property`, `value`) " + "SELECT ?1, `property`, `value` FROM `entity_property_values` " "WHERE `entity` = ?1", entity->id )) TRY(entity->project->db->execute( - "INSERT INTO `entity_blueprint_links`(`entity`, " - "`blueprint`)" - "SELECT ?1, `property`, `value` FROM " - "`entity_property_values` " + "INSERT INTO `entity_blueprint_links`(`entity`, `blueprint`) " + "SELECT ?1, `property`, `value` FROM `entity_property_values` " "WHERE `entity` = ?1", entity->id )) @@ -109,13 +98,13 @@ IkarusEntity * ikarus_entity_copy( ) ); - return new IkarusEntity{entity->project, id, entity->name}; + return new IkarusEntity{entity->project, id}; } IkarusProject * ikarus_entity_get_project(IkarusEntity * entity, IkarusErrorData * error_out) { IKARUS_FAIL_IF_NULL(entity, nullptr); - IKARUS_FAIL_IF_NULL(entity->project, nullptr); + IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); return entity->project; } @@ -123,8 +112,20 @@ ikarus_entity_get_project(IkarusEntity * entity, IkarusErrorData * error_out) { char const * ikarus_entity_get_name(IkarusEntity * entity, IkarusErrorData * error_out) { IKARUS_FAIL_IF_NULL(entity, nullptr); + IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); - return entity->name.data(); + IKARUS_VTRYRV_OR_FAIL( + auto name, + nullptr, + "failed to get name for entity: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->query_one( + "SELECT `name` FROM `entities` WHERE `id` = ?", + entity->id + ) + ); + + return name; } void ikarus_entity_set_name( @@ -133,11 +134,12 @@ void ikarus_entity_set_name( IkarusEntitySetNameFlags flags, IkarusErrorData * error_out ) { - IKARUS_FAIL_IF_NULL(entity, ); - IKARUS_FAIL_IF_NAME_INVALID(name, ); + IKARUS_FAIL_IF_NULL(entity, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NOT_EXIST(entity, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NAME_INVALID(name, IKARUS_VOID_RETURN); IKARUS_TRYRV_OR_FAIL( - , + IKARUS_VOID_RETURN, "failed to set name for entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->execute( @@ -146,8 +148,152 @@ void ikarus_entity_set_name( entity->id ) ); +} - entity->name = name; +struct IkarusBlueprint ** ikarus_entity_get_linked_blueprints( + IkarusEntity * entity, + size_t * size_out, + IkarusErrorData * error_out +) { + IKARUS_FAIL_IF_NULL(entity, nullptr); + IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); + IKARUS_FAIL_IF_NULL(size_out, nullptr); + + auto count = ikarus_entity_get_linked_blueprints_count(entity, error_out); + IKARUS_FAIL_IF_ERROR(nullptr); + + std::int64_t ids[count]; + + IKARUS_TRYRV_OR_FAIL( + nullptr, + "failed to get linked blueprints for entity: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->query_many_buffered( + "SELECT `blueprint` FROM `entity_blueprint_links` WHERE `entity` = ?", + ids, + count, + entity->id + ) + ); + + auto blueprints = new IkarusBlueprint *[count]; + + std::transform(ids, ids + count, blueprints, [entity](auto id) { + return new IkarusBlueprint{entity->project, id}; + }); + + if (size_out) { + *size_out = count; + } + + return blueprints; +} + +size_t ikarus_entity_get_linked_blueprints_count( + IkarusEntity * entity, + IkarusErrorData * error_out +) { + IKARUS_FAIL_IF_NULL(entity, 0); + IKARUS_FAIL_IF_NOT_EXIST(entity, 0); + + IKARUS_VTRYRV_OR_FAIL( + auto count, + 0, + "failed to get linked blueprints count for entity: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->query_one( + "SELECT COUNT(*) FROM `entity_blueprint_links` WHERE `entity` = ?", + entity->id + ) + ); + + return count; +} + +void ikarus_entity_link_blueprint( + IkarusEntity * entity, + struct IkarusBlueprint * blueprint, + IkarusEntityLinkBlueprintFlags flags, + IkarusErrorData * error_out +) { + IKARUS_FAIL_IF_NULL(entity, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NOT_EXIST(entity, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NULL(blueprint, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NOT_EXIST(blueprint, IKARUS_VOID_RETURN); + + IKARUS_TRYRV_OR_FAIL( + IKARUS_VOID_RETURN, + "failed to link blueprint to entity: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->execute( + "INSERT INTO `entity_blueprint_links`(`entity`, `blueprint`) VALUES(?, ?)", + entity->id, + blueprint->id + ) + ); +} + +void ikarus_entity_unlink_blueprint( + IkarusEntity * entity, + struct IkarusBlueprint * blueprint, + IkarusEntityUnlinkBlueprintFlags flags, + IkarusErrorData * error_out +) { + IKARUS_FAIL_IF_NULL(entity, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NOT_EXIST(entity, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NULL(blueprint, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NOT_EXIST(blueprint, IKARUS_VOID_RETURN); + + IKARUS_TRYRV_OR_FAIL( + IKARUS_VOID_RETURN, + "failed to unlink blueprint from entity: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->execute( + "DELETE FROM `entity_blueprint_links` WHERE `entity` = ? AND `blueprint` = ?", + entity->id, + blueprint->id + ) + ); +} + +IkarusEntityValue * ikarus_entity_get_values( + IkarusEntity * entity, + size_t * size_out, + IkarusErrorData * error_out +) { + IKARUS_FAIL_IF_NULL(entity, nullptr); + IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); + + IKARUS_VTRYRV_OR_FAIL( + auto values_plain, + nullptr, + "failed to get values for entity: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->query_many( + "SELECT `name`, `value` FROM `entity_values` WHERE `entity` = ?", + entity->id + ) + ); + + IkarusEntityValue * values = new IkarusEntityValue[values_plain.size()]; + + std::transform( + std::cbegin(values_plain), + std::cend(values_plain), + values, + [](auto const & tuple) { + return IkarusEntityValue{ + tuple.template get<0>(), + tuple.template get<1>() + }; + } + ); + + if (size_out) { + *size_out = values_plain.size(); + } + + return values; } char const * ikarus_entity_get_value( @@ -156,7 +302,22 @@ char const * ikarus_entity_get_value( IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, nullptr); + IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); IKARUS_FAIL_IF_NULL(name, nullptr); + + IKARUS_VTRYRV_OR_FAIL( + auto value, + nullptr, + "failed to get value for entity: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->query_one( + "SELECT `value` FROM `entity_values` WHERE `entity` = ? AND `name` = ?", + entity->id, + name + ) + ); + + return value; } void ikarus_entity_set_value( @@ -165,7 +326,155 @@ void ikarus_entity_set_value( char const * value, IkarusErrorData * error_out ) { - IKARUS_FAIL_IF_NULL(entity, ); - IKARUS_FAIL_IF_NULL(name, ); - IKARUS_FAIL_IF_NULL(value, ); + IKARUS_FAIL_IF_NULL(entity, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NOT_EXIST(entity, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NULL(name, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NULL(value, IKARUS_VOID_RETURN); + + // parsing from & to here to ensure values are valid JSON & formatted + // uniformly + IKARUS_VTRYRV_OR_FAIL( + auto value_parsed, + IKARUS_VOID_RETURN, + "cannot parse value as JSON: {}", + IkarusErrorInfo_Client_InvalidInput, + IkarusValue::from_json(value) + ); + + IKARUS_TRYRV_OR_FAIL( + IKARUS_VOID_RETURN, + "failed to set value for entity: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->execute( + "INSERT INTO `entity_values`(`entity`, `name`, `value`) VALUES(?, ?, ?) " + "ON CONFLICT(`entity`, `name`) DO UPDATE SET `value` = excluded.`value`", + entity->id, + name, + IkarusValue::to_json(value_parsed).dump() + ) + ); +} + +void ikarus_entity_delete_value( + IkarusEntity * entity, + char const * name, + IkarusEntityDeleteValueFlags flags, + IkarusErrorData * error_out +) { + IKARUS_FAIL_IF_NULL(entity, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NOT_EXIST(entity, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NULL(name, IKARUS_VOID_RETURN); + + IKARUS_TRYRV_OR_FAIL( + IKARUS_VOID_RETURN, + "failed to delete value for entity: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->execute( + "DELETE FROM `entity_values` WHERE `entity` = ? AND `name` = ?", + entity->id, + name + ) + ); +} + +IkarusEntityPropertyValue * ikarus_entity_get_property_values( + IkarusEntity * entity, + size_t * size_out, + IkarusErrorData * error_out +) { + IKARUS_FAIL_IF_NULL(entity, nullptr); + IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); + + IKARUS_VTRYRV_OR_FAIL( + auto values_plain, + nullptr, + "failed to get property values for entity: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->query_many( + "SELECT `property`, `value` FROM `entity_property_values` WHERE `entity` = ?", + entity->id + ) + ); + + IkarusEntityPropertyValue * values = + new IkarusEntityPropertyValue[values_plain.size()]; + std::transform( + std::cbegin(values_plain), + std::cend(values_plain), + values, + [entity](auto const & tuple) { + return IkarusEntityPropertyValue{ + new IkarusProperty{entity->project, tuple.template get<0>()}, + tuple.template get<1>() + }; + } + ); + + if (size_out) { + *size_out = values_plain.size(); + } + + return values; +} + +char const * ikarus_entity_get_property_value( + IkarusEntity * entity, + struct IkarusProperty * property, + IkarusErrorData * error_out +) { + IKARUS_FAIL_IF_NULL(entity, nullptr); + IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); + IKARUS_FAIL_IF_NULL(property, nullptr); + IKARUS_FAIL_IF_NOT_EXIST(property, nullptr); + + IKARUS_VTRYRV_OR_FAIL( + auto value, + nullptr, + "failed to get property value for entity: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->query_one( + "SELECT `value` FROM `entity_property_values` WHERE `entity` = ? AND `property` = ?", + entity->id, + property->id + ) + ); + + return value; +} + +void ikarus_entity_set_property_value( + IkarusEntity * entity, + struct IkarusProperty * property, + char const * value, + IkarusEntitySetPropertyValueFlags flags, + IkarusErrorData * error_out +) { + IKARUS_FAIL_IF_NULL(entity, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NOT_EXIST(entity, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NULL(property, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NOT_EXIST(property, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NULL(value, IKARUS_VOID_RETURN); + + // parsing from & to here to ensure values are valid JSON & formatted + // uniformly + IKARUS_VTRYRV_OR_FAIL( + auto value_parsed, + IKARUS_VOID_RETURN, + "cannot parse value as JSON: {}", + IkarusErrorInfo_Client_InvalidInput, + IkarusValue::from_json(value) + ); + + IKARUS_TRYRV_OR_FAIL( + IKARUS_VOID_RETURN, + "failed to set property value for entity: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->execute( + "INSERT INTO `entity_property_values`(`entity`, `property`, `value`) VALUES(?, ?, ?) " + "ON CONFLICT(`entity`, `property`) DO UPDATE SET `value` = excluded.`value`", + entity->id, + property->id, + IkarusValue::to_json(value_parsed).dump() + ) + ); } diff --git a/src/ikarus/objects/entity.hpp b/src/ikarus/objects/entity.hpp index 90a445e..449d69e 100644 --- a/src/ikarus/objects/entity.hpp +++ b/src/ikarus/objects/entity.hpp @@ -9,16 +9,8 @@ struct IkarusEntity { constinit static inline auto object_name = "entity"; constinit static inline auto table_name = "entities"; - IkarusEntity( - struct IkarusProject * project, - int64_t id, - std::string_view name - ); + IkarusEntity(struct IkarusProject * project, int64_t id); struct IkarusProject * project; int64_t id; - std::string name; - - std::vector> values_ordered; - std::unordered_map values; }; diff --git a/src/ikarus/objects/property.cpp b/src/ikarus/objects/property.cpp index d096b18..43fac96 100644 --- a/src/ikarus/objects/property.cpp +++ b/src/ikarus/objects/property.cpp @@ -5,11 +5,6 @@ #include #include -IkarusProperty::IkarusProperty( - struct IkarusProject * project, - int64_t id, - std::string_view name -): +IkarusProperty::IkarusProperty(struct IkarusProject * project, int64_t id): project{project}, - id{id}, - name{name} {} + id{id} {} diff --git a/src/ikarus/objects/property.hpp b/src/ikarus/objects/property.hpp index 29143fb..32bbbc9 100644 --- a/src/ikarus/objects/property.hpp +++ b/src/ikarus/objects/property.hpp @@ -3,21 +3,11 @@ #include struct IkarusProperty { - consteval static inline auto OBJECT_NAME() -> std::string_view { - return "property"; - } + constinit static inline auto object_name = "property"; + constinit static inline auto table_name = "properties"; - consteval static inline auto TABLE_NAME() -> std::string_view { - return "properties"; - } - - IkarusProperty( - struct IkarusProject * project, - int64_t id, - std::string_view name - ); + IkarusProperty(struct IkarusProject * project, int64_t id); struct IkarusProject * project; int64_t id; - std::string name; }; diff --git a/src/ikarus/persistence/migrations/m0_initial_layout.sql b/src/ikarus/persistence/migrations/m0_initial_layout.sql index 8b263d7..e83b41a 100644 --- a/src/ikarus/persistence/migrations/m0_initial_layout.sql +++ b/src/ikarus/persistence/migrations/m0_initial_layout.sql @@ -6,7 +6,6 @@ CREATE TABLE `entities` CREATE TABLE `entity_values` ( - `id` INTEGER PRIMARY KEY, `entity` INTEGER NOT NULL REFERENCES `entities` (`id`) ON DELETE CASCADE, `name` TEXT NOT NULL, `value` TEXT NOT NULL, @@ -36,12 +35,12 @@ CREATE TABLE `entity_blueprint_links` `entity` INTEGER NOT NULL REFERENCES `entities` (`id`) ON DELETE CASCADE, `blueprint` INTEGER NOT NULL REFERENCES `blueprints` (`id`) ON DELETE CASCADE, - PRIMARY KEY (`entity`, `blueprint`) + PRIMARY KEY (`entity`, `blueprint`), + FOREIGN KEY (`entity`, `blueprint`) REFERENCES `entity_blueprint_links` (`entity`, `blueprint`) ON DELETE CASCADE ) STRICT; CREATE TABLE `entity_property_values` ( - `id` INTEGER PRIMARY KEY, `entity` INTEGER NOT NULL REFERENCES `entities` (`id`) ON DELETE CASCADE, `property` INTEGER NOT NULL REFERENCES `properties` (`id`) ON DELETE CASCADE, `value` TEXT NOT NULL, diff --git a/src/ikarus/persistence/project.cpp b/src/ikarus/persistence/project.cpp index 300bf86..75c8825 100644 --- a/src/ikarus/persistence/project.cpp +++ b/src/ikarus/persistence/project.cpp @@ -232,18 +232,18 @@ void ikarus_project_set_name( char const * new_name, IkarusErrorData * error_out ) { - IKARUS_FAIL_IF_NULL(project, ); - IKARUS_FAIL_IF_NULL(new_name, ); + IKARUS_FAIL_IF_NULL(project, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NULL(new_name, IKARUS_VOID_RETURN); IKARUS_FAIL_IF( cppbase::is_empty_or_blank(new_name), - , + IKARUS_VOID_RETURN, "name must not be empty", IkarusErrorInfo_Client_InvalidInput ); IKARUS_TRYRV_OR_FAIL( - , + IKARUS_VOID_RETURN, "failed to update project name: {}", IkarusErrorInfo_Database_QueryFailed, project->db->execute( @@ -269,15 +269,15 @@ void ikarus_project_get_entities( uint64_t ids_out_size, IkarusErrorData * error_out ) { - IKARUS_FAIL_IF_NULL(project, ); - IKARUS_FAIL_IF_NULL(ids_out, ); + IKARUS_FAIL_IF_NULL(project, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NULL(ids_out, IKARUS_VOID_RETURN); if (ids_out == 0) { return; } IKARUS_TRYRV_OR_FAIL( - , + IKARUS_VOID_RETURN, "unable to fetch project entities from database: {}", IkarusErrorInfo_Database_QueryFailed, project->db->query_many_buffered( @@ -311,8 +311,8 @@ void ikarus_project_get_blueprints( uint64_t ids_out_size, IkarusErrorData * error_out ) { - IKARUS_FAIL_IF_NULL(project, ); - IKARUS_FAIL_IF_NULL(ids_out, ); + IKARUS_FAIL_IF_NULL(project, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NULL(ids_out, IKARUS_VOID_RETURN); if (ids_out == 0) { return; diff --git a/src/ikarus/values/data.cpp b/src/ikarus/values/data.cpp index ac893b2..f7f6845 100644 --- a/src/ikarus/values/data.cpp +++ b/src/ikarus/values/data.cpp @@ -152,10 +152,9 @@ auto IkarusValueData::from_json(nlohmann::json const & json) return cppbase::ok(value); } -auto IkarusValueData::to_json( - nlohmann::json & json, - IkarusValueData const & value -) -> void { +auto IkarusValueData::to_json(IkarusValueData const & value) -> nlohmann::json { + nlohmann::json json = nlohmann::json::object(); + std::visit( cppbase::overloaded{ [&](IkarusValueDataPrimitive const & primitive) { @@ -184,9 +183,7 @@ auto IkarusValueData::to_json( json["type"] = IkarusValueDataType_List; json["data"] = list.values | std::views::transform([](auto const & data) { - nlohmann::json j; - IkarusValueData::to_json(j, *data); - return j; + return IkarusValueData::to_json(*data); }) | std::ranges::to>(); }, @@ -195,8 +192,8 @@ auto IkarusValueData::to_json( json["data"] = map.values | std::views::transform([](auto const & pair) { nlohmann::json j; - IkarusValueData::to_json(j["key"], *pair.first); - IkarusValueData::to_json(j["value"], *pair.second); + j["key"] = IkarusValueData::to_json(*pair.first); + j["value"] = IkarusValueData::to_json(*pair.second); return j; }) | std::ranges::to>(); @@ -205,13 +202,13 @@ auto IkarusValueData::to_json( json["type"] = IkarusValueDataType_Tuple; json["data"] = tuple.values | std::views::transform([](auto const & data) { - nlohmann::json j; - IkarusValueData::to_json(j, *data); - return j; + return IkarusValueData::to_json(*data); }) | std::ranges::to>(); } }, value.variant ); + + return json; } diff --git a/src/ikarus/values/data.hpp b/src/ikarus/values/data.hpp index d4e92ad..1c9b3d8 100644 --- a/src/ikarus/values/data.hpp +++ b/src/ikarus/values/data.hpp @@ -55,8 +55,7 @@ struct IkarusValueData { static auto from_json(nlohmann::json const & json) -> cppbase::Result; - static auto to_json(nlohmann::json & json, IkarusValueData const & value) - -> void; + static auto to_json(IkarusValueData const & value) -> nlohmann::json; IkarusValueDataVariant variant; }; diff --git a/src/ikarus/values/errors.hpp b/src/ikarus/values/errors.hpp index ed752af..f472d83 100644 --- a/src/ikarus/values/errors.hpp +++ b/src/ikarus/values/errors.hpp @@ -2,6 +2,8 @@ #include +#include + struct IkarusJsonMissingKeyError {}; struct IkarusJsonInvalidTypeError {}; @@ -34,3 +36,82 @@ using IkarusValueParseError = std::variant< using IkarusValuesParseError = std::variant; + +template<> +struct fmt::formatter : formatter { + constexpr static auto format( + [[maybe_unused]] IkarusJsonMissingKeyError const & error, + fmt::format_context & ctx + ) { + return fmt::format_to(ctx.out(), "missing JSON key"); + } +}; + +template<> +struct fmt::formatter : formatter { + constexpr static auto format( + [[maybe_unused]] IkarusJsonInvalidTypeError const & error, + fmt::format_context & ctx + ) { + return fmt::format_to(ctx.out(), "invalid JSON type"); + } +}; + +template<> +struct fmt::formatter : formatter { + constexpr static auto format( + [[maybe_unused]] IkarusJsonEnumOutOfBoundsError const & error, + fmt::format_context & ctx + ) { + return fmt::format_to(ctx.out(), "JSON enum is out of bounds"); + } +}; + +template<> +struct fmt::formatter : formatter { + constexpr static auto format( + [[maybe_unused]] IkarusJsonUnknownError const & error, + fmt::format_context & ctx + ) { + return fmt::format_to(ctx.out(), "unknown JSON error"); + } +}; + +template<> +struct fmt::formatter : formatter { + constexpr static auto format( + [[maybe_unused]] IkarusValueSchemaParseError const & error, + fmt::format_context & ctx + ) { + return fmt::format_to( + ctx.out(), + "failed to parse value schema: {}", + error.error + ); + } +}; + +template<> +struct fmt::formatter : formatter { + constexpr static auto format( + [[maybe_unused]] IkarusValueDataParseError const & error, + fmt::format_context & ctx + ) { + return fmt::format_to( + ctx.out(), + "failed to parse value data: {}", + error.error + ); + } +}; + +template<> +struct fmt::formatter : + formatter { + constexpr static auto format( + [[maybe_unused]] IkarusValueParseErrorDataSchemaMismatch const & error, + fmt::format_context & ctx + ) { + return fmt::format_to(ctx.out(), "value data and schema mismatched"); + } +}; diff --git a/src/ikarus/values/schema.cpp b/src/ikarus/values/schema.cpp index e6cbd24..ea2e6dc 100644 --- a/src/ikarus/values/schema.cpp +++ b/src/ikarus/values/schema.cpp @@ -85,10 +85,10 @@ auto IkarusValueSchema::from_json(nlohmann::json const & json) return cppbase::ok(std::move(schema)); } -auto IkarusValueSchema::to_json( - nlohmann::json & json, - IkarusValueSchema const & schema -) -> void { +auto IkarusValueSchema::to_json(IkarusValueSchema const & schema) + -> nlohmann::json { + nlohmann::json json = nlohmann::json::object(); + std::visit( cppbase::overloaded{ [&json](IkarusValueSchemaPrimitive const & schema) { @@ -97,18 +97,14 @@ auto IkarusValueSchema::to_json( }, [&json](IkarusValueSchemaList const & schema) { json["type"] = IkarusValueSchemaType_List; - IkarusValueSchema::to_json(json["schema"], *schema.sub_schema); + json["schema"] = IkarusValueSchema::to_json(*schema.sub_schema); }, [&json](IkarusValueSchemaMap const & schema) { json["type"] = IkarusValueSchemaType_Map; - IkarusValueSchema::to_json( - json["key_schema"], - *schema.key_schema - ); - IkarusValueSchema::to_json( - json["value_schema"], - *schema.value_schema - ); + json["key_schema"] = + IkarusValueSchema::to_json(*schema.key_schema); + json["value_schema"] = + IkarusValueSchema::to_json(*schema.value_schema); }, [&json](IkarusValueSchemaTuple const & schema) { json["type"] = IkarusValueSchemaType_Tuple; @@ -117,9 +113,9 @@ auto IkarusValueSchema::to_json( sub_schemas.reserve(schema.sub_schemas.size()); for (auto const & sub_schema : schema.sub_schemas) { - nlohmann::json sub_schema_json{}; - IkarusValueSchema::to_json(sub_schema_json, *sub_schema); - sub_schemas.push_back(sub_schema_json); + sub_schemas.push_back( + IkarusValueSchema::to_json(*sub_schema) + ); } json["schemas"] = sub_schemas; @@ -127,6 +123,8 @@ auto IkarusValueSchema::to_json( }, schema.variant ); + + return json; } auto IkarusValueSchema::validate(IkarusValueData const & data) const -> bool { diff --git a/src/ikarus/values/schema.hpp b/src/ikarus/values/schema.hpp index 4f4256c..7fd07ab 100644 --- a/src/ikarus/values/schema.hpp +++ b/src/ikarus/values/schema.hpp @@ -42,8 +42,7 @@ struct IkarusValueSchema { static auto from_json(nlohmann::json const & json) -> cppbase::Result; - static auto to_json(nlohmann::json & json, IkarusValueSchema const & value) - -> void; + static auto to_json(IkarusValueSchema const & value) -> nlohmann::json; auto validate(IkarusValueData const & data) const -> bool; diff --git a/src/ikarus/values/value.cpp b/src/ikarus/values/value.cpp index 07c0c24..9a23a98 100644 --- a/src/ikarus/values/value.cpp +++ b/src/ikarus/values/value.cpp @@ -25,34 +25,11 @@ auto IkarusValue::from_json(nlohmann::json const & json) return cppbase::ok(std::move(value)); } -auto IkarusValue::to_json(nlohmann::json & json, IkarusValue const & value) - -> void { - IkarusValueSchema::to_json(json["schema"], value.schema); - IkarusValueData::to_json(json["data"], value.data); -} - -auto IkarusValues::from_json(nlohmann::json const & json) - -> cppbase::Result { - IkarusValues values{}; - - for (auto const & json_entry : json) { - VTRY(auto name_json, get_key(json_entry, "name")); - - if (!name_json->is_string()) { - return cppbase::err(IkarusJsonInvalidTypeError{}); - } - - VTRY(auto value_json, get_key(json_entry, "value")); - - VTRY(auto value, IkarusValue::from_json(*value_json)); - - values.values.emplace_back( - std::make_pair( - std::move(name_json->get()), - std::move(value) - ) - ); - } - - return cppbase::ok(std::move(values)); +auto IkarusValue::to_json(IkarusValue const & value) -> nlohmann::json { + nlohmann::json json = nlohmann::json::object(); + + json["schema"] = IkarusValueSchema::to_json(value.schema); + json["data"] = IkarusValueData::to_json(value.data); + + return json; } diff --git a/src/ikarus/values/value.hpp b/src/ikarus/values/value.hpp index 47b3600..74db914 100644 --- a/src/ikarus/values/value.hpp +++ b/src/ikarus/values/value.hpp @@ -14,15 +14,5 @@ struct IkarusValue { static auto from_json(nlohmann::json const & json) -> cppbase::Result; - static auto to_json(nlohmann::json & json, IkarusValue const & value) - -> void; -}; - -struct IkarusValues { - static auto from_json(nlohmann::json const & json) - -> cppbase::Result; - static auto to_json(nlohmann::json & json, IkarusValues const & values) - -> void; - - std::vector> values; + static auto to_json(IkarusValue const & value) -> nlohmann::json; };