From bfac86b8a1f94ac2b04c1e754d7fed6370d8b6ad Mon Sep 17 00:00:00 2001 From: Folling Date: Thu, 2 Jan 2025 09:39:58 +0100 Subject: [PATCH] update dependencies Signed-off-by: Folling --- .clang-format | 3 +- include/ikarus/errors.h | 13 +- include/ikarus/objects/blueprint.h | 38 +- include/ikarus/objects/entity.h | 80 ++- include/ikarus/objects/property.h | 29 +- src/ikarus/errors.cpp | 4 +- src/ikarus/errors.hpp | 200 +++---- src/ikarus/objects/blueprint.cpp | 64 +++ src/ikarus/objects/entity.cpp | 508 ++++++++++++------ .../migrations/m0_initial_layout.sql | 3 +- src/ikarus/persistence/project.cpp | 43 ++ src/ikarus/values/data.cpp | 53 +- src/ikarus/values/data.hpp | 8 +- src/ikarus/values/errors.hpp | 13 + src/ikarus/values/schema.cpp | 80 ++- src/ikarus/values/schema.hpp | 4 + src/ikarus/values/shared.hpp | 4 +- src/ikarus/values/value.cpp | 17 +- src/ikarus/values/value.hpp | 2 + vendor/cppbase | 2 +- vendor/sqlitecpp | 2 +- 21 files changed, 800 insertions(+), 370 deletions(-) diff --git a/.clang-format b/.clang-format index 5456c6f..f88191b 100644 --- a/.clang-format +++ b/.clang-format @@ -66,7 +66,7 @@ BreakConstructorInitializers: AfterColon BreakInheritanceList: AfterColon BreakStringLiterals: false -ColumnLimit: 80 +ColumnLimit: 120 CommentPragmas: '^\\.+' CompactNamespaces: false ConstructorInitializerIndentWidth: 4 @@ -148,6 +148,7 @@ PPIndentWidth: -1 PackConstructorInitializers: Never PointerAlignment: Middle + QualifierAlignment: Right # QualifierOrder: [ 'friend', 'constexpr', 'inline', 'static', 'type', 'const', 'volatile' ] ReferenceAlignment: Middle diff --git a/include/ikarus/errors.h b/include/ikarus/errors.h index a41c291..eb123b0 100644 --- a/include/ikarus/errors.h +++ b/include/ikarus/errors.h @@ -31,24 +31,27 @@ enum IkarusErrorInfo { /// \brief The client provided a non-existent resource. /// Example: Passing an entity to a function after it has been deleted. IkarusErrorInfo_Client_NonExistent = 0x01000003, + /// \brief The client provided a resource which exists but is not linked to the current context. + /// Example: Passing a property that isn't linked to the current entity. + IkarusErrorInfo_Client_NotLinked = 0x01000004, /// \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 = 0x01000004, + IkarusErrorInfo_Client_IndexOutOfBounds = 0x01000005, /// \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 = 0x01000005, + IkarusErrorInfo_Client_ValueOutOfBounds = 0x01000006, /// \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 = 0x01000006, + IkarusErrorInfo_Client_InvalidInput = 0x01000007, /// \brief The client provided valid data in an invalid format. /// Example: Passing a malformed JSON string. - IkarusErrorInfo_Client_InvalidFormat = 0x01000007, + IkarusErrorInfo_Client_InvalidFormat = 0x01000008, /// \brief The client violated a constraint. /// \details This error is most likely caused by clients. /// Example: A user tries to set the age of a character to a value outside /// its specified range. - IkarusErrorInfo_Client_ConstraintViolated = 0x10000008, + IkarusErrorInfo_Client_ConstraintViolated = 0x10000009, // 0x02 reserved for dependency errors diff --git a/include/ikarus/objects/blueprint.h b/include/ikarus/objects/blueprint.h index adc97e9..7df09a8 100644 --- a/include/ikarus/objects/blueprint.h +++ b/include/ikarus/objects/blueprint.h @@ -20,6 +20,13 @@ IKARUS_BEGIN_HEADER /// in all linked entities. struct IkarusBlueprint; +/// \brief Checks whether a blueprint exists. +/// \param blueprint The blueprint to check. +/// \pre \li Must not be null. +/// \param error_out \see errors.h +/// \return True if the blueprint exists, false otherwise or if an error occurs. +IKA_API bool ikarus_blueprint_exists(IkarusBlueprint * blueprint, IkarusErrorData * error_out); + /// \brief Flags for creating a blueprint. enum IkarusBlueprintCreateFlags { /// \brief No flags. @@ -48,14 +55,10 @@ IKA_API IkarusBlueprint * ikarus_blueprint_create( enum IkarusBlueprintCreateFromEntityFlags { /// \brief No flags. IkarusBlueprintCreateFromEntityFlags_None = 0, - /// \brief The default values of the properties will be set to the values of the source entity. - IkarusBlueprintCreateFromEntityFlags_AdoptDefaultValues = 1 << 0, - /// \brief The entity will be linked to the blueprint, and all values will be turned into properties. - IkarusBlueprintCreateFromEntityFlags_LinkEntity = 1 << 1, }; /// \brief Creates a new blueprint from an entity. -/// \details Each value of the entity will be copied into the blueprint as a property. +/// \details Each value of the entity will be copied into the blueprint as a blueprint. /// \param entity The entity to create the blueprint from. /// \pre \li Must not be null. /// \pre \li Must exist. @@ -117,10 +120,8 @@ IKA_API void ikarus_blueprint_delete( /// \param error_out \see errors.h /// \return The project the blueprint belongs to. /// \remark Ownership remains with libikarus. -IKA_API struct IkarusProject * ikarus_blueprint_get_project( - struct IkarusBlueprint * blueprint, - struct IkarusErrorData * error_out -); +IKA_API struct IkarusProject * +ikarus_blueprint_get_project(struct IkarusBlueprint * blueprint, struct IkarusErrorData * error_out); /// \brief Gets the name of a blueprint. /// \param blueprint The blueprint to get the name of. @@ -129,10 +130,7 @@ IKA_API struct IkarusProject * ikarus_blueprint_get_project( /// \param error_out \see errors.h /// \return The name of the blueprint. /// \remark Ownership remains with libikarus. -IKA_API char const * ikarus_blueprint_get_name( - struct IkarusBlueprint * blueprint, - struct IkarusErrorData * error_out -); +IKA_API char const * ikarus_blueprint_get_name(struct IkarusBlueprint * blueprint, struct IkarusErrorData * error_out); /// \brief Flags for setting the name of a blueprint. enum IkarusBlueprintSetNameFlags { @@ -163,7 +161,7 @@ IKA_API void ikarus_blueprint_set_name( /// \param size_out An out parameter for the number of items in the returned array or undefined if an error occurs. /// \param error_out \see errors.h /// \return The properties of the blueprint or null if an error occurs. -IKA_API struct IkarusProperty ** ikarus_blueprint_get_properties( +IKA_API struct IkarusBlueprint ** ikarus_blueprint_get_properties( struct IkarusBlueprint * blueprint, size_t * size_out, struct IkarusErrorData * error_out @@ -175,10 +173,8 @@ IKA_API struct IkarusProperty ** ikarus_blueprint_get_properties( /// \pre \li Must exist. /// \param error_out \see errors.h /// \return The number of properties of the blueprint or 0 if an error occurs. -IKA_API size_t ikarus_blueprint_get_properties_count( - struct IkarusBlueprint * blueprint, - struct IkarusErrorData * error_out -); +IKA_API size_t +ikarus_blueprint_get_properties_count(struct IkarusBlueprint * blueprint, struct IkarusErrorData * error_out); /// \brief Gets all entities linked to a blueprint. /// \param blueprint The blueprint to get the entities of. @@ -200,10 +196,8 @@ IKA_API struct IkarusEntity ** ikarus_blueprint_get_entities( /// \pre \li Must exist. /// \param error_out \see errors.h /// \return The number of entities linked to the blueprint or 0 if an error occurs. -IKA_API size_t ikarus_blueprint_get_entities_count( - struct IkarusBlueprint * blueprint, - struct IkarusErrorData * error_out -); +IKA_API size_t +ikarus_blueprint_get_entities_count(struct IkarusBlueprint * blueprint, struct IkarusErrorData * error_out); IKARUS_END_HEADER diff --git a/include/ikarus/objects/entity.h b/include/ikarus/objects/entity.h index 172bad1..990be61 100644 --- a/include/ikarus/objects/entity.h +++ b/include/ikarus/objects/entity.h @@ -33,6 +33,14 @@ enum IkarusEntityCreateFlags { IkarusEntityCreateFlags_None = 0, }; +/// \brief Checks whether an entity exists. +/// \param entity The entity to check. +/// \pre \li Must not be null. +/// \param error_out \see errors.h +/// \return True if the entity exists, false otherwise or if an error occurs. +IKA_API bool +ikarus_entity_exists(IkarusEntity * entity, IkarusErrorData * error_out); + /// \brief Creates a new entity. /// \param project The project to create the entity in. /// \pre \li Must not be null. @@ -127,6 +135,22 @@ IKA_API void ikarus_entity_set_name( IkarusErrorData * error_out ); +/// \brief Gets whether an entity is linked to a blueprint. +/// \param entity The entity to check the blueprint of. +/// \pre \li Must not be null. +/// \pre \li Must exist. +/// \param blueprint The blueprint to check the entity's link to. +/// \pre \li Must not be null. +/// \pre \li Must exist. +/// \pre \li Must be in the same project as the entity. +/// \param error_out \see errors.h +/// \return True if the entity is linked to the blueprint, false otherwise or if an error occurs. +IKA_API bool ikarus_entity_is_linked_to_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. @@ -191,6 +215,7 @@ enum IkarusEntityUnlinkBlueprintFlags { /// \param blueprint The blueprint to unlink from. /// \pre \li Must not be null. /// \pre \li Must exist. +/// \pre \li Must be in the same project as the entity. /// \remark If the entity is not linked to the blueprint, nothing happens. /// \param flags Flags for unlinking the entity from the blueprint. /// \param error_out \see errors.h @@ -201,6 +226,20 @@ IKA_API void ikarus_entity_unlink_blueprint( IkarusErrorData * error_out ); +/// \brief Gets whether an entity has a value. +/// \param entity The entity to check the value of. +/// \pre \li Must not be null. +/// \pre \li Must exist. +/// \param name The value's name. +/// \pre \li Must not be null. +/// \param error_out \see errors.h +/// \return True if the entity has a value with the name, false otherwise or if an error occurs. +IKA_API bool ikarus_entity_has_value( + IkarusEntity * entity, + char const * name, + IkarusErrorData * error_out +); + /// \brief Struct for an entity value. struct IkarusEntityValue { /// \brief The name of the value. @@ -229,7 +268,7 @@ IKA_API IkarusEntityValue * ikarus_entity_get_values( /// \pre \li Must exist. /// \param name The value's name. /// \pre \li Must not be null. -/// \remark Ownership remains with the client. +/// \pre \li Must exist. /// \param error_out \see errors.h /// \return The value, in json format of or null if an error occurs. \see value.h IKA_API char const * ikarus_entity_get_value( @@ -287,6 +326,22 @@ IKA_API void ikarus_entity_delete_value( IkarusErrorData * error_out ); +/// \brief Gets whether an entity has a property value. +/// \param entity The entity to check the property value of. +/// \pre \li Must not be null. +/// \pre \li Must exist. +/// \param property The property to check the value of. +/// \pre \li Must not be null. +/// \pre \li Must exist. +/// \pre \li Must be in the same project as the entity. +/// \param error_out \see errors.h +/// \return True if the entity has a value for the property, false otherwise or if an error occurs. +IKA_API bool ikarus_entity_has_property_value( + IkarusEntity * entity, + struct IkarusProperty * property, + IkarusErrorData * error_out +); + /// \brief Struct for an entity property value. struct IkarusEntityPropertyValue { /// \brief The property. @@ -355,6 +410,29 @@ IKA_API void ikarus_entity_set_property_value( IkarusErrorData * error_out ); +/// \brief Flags for clearing the value of a property of an entity. +enum IkarusEntityClearPropertyValueFlags { + /// \brief No flags. + IkarusEntityClearPropertyValueFlags_None = 0, +}; + +/// \brief Clears the value of a property of an entity. +/// \param entity The entity to clear the value of. +/// \pre \li Must not be null. +/// \pre \li Must exist. +/// \param property The property to clear the value of. +/// \pre \li Must not be null. +/// \pre \li Must exist. +/// \pre \li Must be linked to the entity. +/// \param flags Flags for clearing the property value. +/// \param error_out \see errors.h +IKA_API void ikarus_entity_clear_property_value( + IkarusEntity * entity, + struct IkarusProperty * property, + IkarusEntitySetPropertyValueFlags flags, + IkarusErrorData * error_out +); + IKARUS_END_HEADER /// @} diff --git a/include/ikarus/objects/property.h b/include/ikarus/objects/property.h index 5bff42c..2124423 100644 --- a/include/ikarus/objects/property.h +++ b/include/ikarus/objects/property.h @@ -36,6 +36,13 @@ IKARUS_BEGIN_HEADER /// property's default value if none is specified. struct IkarusProperty; +/// \brief Checks whether a property exists. +/// \param property The property to check. +/// \pre \li Must not be null. +/// \param error_out \see errors.h +/// \return True if the property exists, false otherwise or if an error occurs. +IKA_API bool ikarus_property_exists(IkarusProperty * property, IkarusErrorData * error_out); + /// \brief Flags for creating a property. enum IkarusPropertyCreateFlags { /// \brief No flags. @@ -73,11 +80,8 @@ enum IkarusPropertyDeleteFlags { /// \param property The property to delete. /// \param flags Flags for deleting the property. /// \param error_out \see errors.h -IKA_API void ikarus_property_delete( - IkarusProperty * property, - IkarusPropertyDeleteFlags flags, - IkarusErrorData * error_out -); +IKA_API void +ikarus_property_delete(IkarusProperty * property, IkarusPropertyDeleteFlags flags, IkarusErrorData * error_out); /// \brief Get the project a property belongs to. /// \param property The property to get the project of. @@ -86,10 +90,7 @@ IKA_API void ikarus_property_delete( /// \param error_out \see errors.h /// \return The project the property belongs to or null if an error occurred. /// \remark Ownership remains with libikarus. -IKA_API struct IkarusProject * ikarus_property_get_project( - IkarusProperty * property, - IkarusErrorData * error_out -); +IKA_API struct IkarusProject * ikarus_property_get_project(IkarusProperty * property, IkarusErrorData * error_out); /// \brief Get the name of a property. /// \param property The property to get the name of. @@ -98,10 +99,7 @@ IKA_API struct IkarusProject * ikarus_property_get_project( /// \param error_out \see errors.h /// \return The name of the property or null if an error occurred. /// \remark Ownership remains with libikarus. -IKA_API char const * ikarus_property_get_name( - IkarusProperty * property, - IkarusErrorData * error_out -); +IKA_API char const * ikarus_property_get_name(IkarusProperty * property, IkarusErrorData * error_out); /// \brief Get the schema of a property. /// \param property The property to get the schema of. @@ -110,10 +108,7 @@ IKA_API char const * ikarus_property_get_name( /// \param error_out \see errors.h /// \return The schema of the property or null if an error occurred. /// \remark Ownership remains with libikarus. -IKA_API struct IkarusValueSchema * ikarus_property_get_schema( - IkarusProperty * property, - IkarusErrorData * error_out -); +IKA_API struct IkarusValueSchema * ikarus_property_get_schema(IkarusProperty * property, IkarusErrorData * error_out); /// \brief Flags for setting the name of a property. enum IkarusPropertySetNameFlags { diff --git a/src/ikarus/errors.cpp b/src/ikarus/errors.cpp index b3e4ae2..07f7a15 100644 --- a/src/ikarus/errors.cpp +++ b/src/ikarus/errors.cpp @@ -64,9 +64,9 @@ char const * ikarus_get_error_info_name(IkarusErrorInfo info) { } bool ikarus_error_data_is_success(IkarusErrorData const * data) { - return data->info == IkarusErrorInfo_None; + return data && data->info == IkarusErrorInfo_None; } bool ikarus_error_data_is_error(IkarusErrorData const * data) { - return data->info != IkarusErrorInfo_None; + return data && data->info != IkarusErrorInfo_None; } diff --git a/src/ikarus/errors.hpp b/src/ikarus/errors.hpp index a9daa9d..d4294ec 100644 --- a/src/ikarus/errors.hpp +++ b/src/ikarus/errors.hpp @@ -3,24 +3,33 @@ #include #include +#include +#include +#include + #include -void safe_strcpy( - std::string_view const src, - char * dest, - size_t const dest_size -); +void safe_strcpy(std::string_view const src, char * dest, size_t const dest_size); + +template<> +struct fmt::formatter : formatter { + constexpr static auto format([[maybe_unused]] IkarusErrorData const & error, fmt::format_context & ctx) { + return fmt::format_to( + ctx.out(), + "ERROR {}({}): {}", + ikarus_error_info_get_name(error.info), + static_cast>(error.info), + error.message + ); + } +}; #define IKARUS_VOID_RETURN -#define IKARUS_SET_ERROR(msg, err_info) \ - if (error_out != nullptr) { \ - safe_strcpy( \ - msg, \ - static_cast(error_out->message), \ - IKARUS_ERROR_DATA_MAX_MESSAGE_LIMIT \ - ); \ - error_out->info = err_info; \ +#define IKARUS_SET_ERROR(msg, err_info) \ + if (error_out != nullptr) { \ + safe_strcpy(msg, static_cast(error_out->message), IKARUS_ERROR_DATA_MAX_MESSAGE_LIMIT); \ + error_out->info = err_info; \ } #define IKARUS_FAIL(ret, msg, err_info) \ @@ -33,142 +42,75 @@ void safe_strcpy( return ret; \ } -#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( \ - fmt::runtime(msg), \ - std::move(var_name).unwrap_error() \ - ), \ - err_info \ - ); \ - return var_name; \ +#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(fmt::runtime(msg), std::move(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__ \ - ); + 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( \ - fmt::runtime(msg), \ - std::move(var_name).unwrap_error() \ - ), \ - err_info \ - ); \ - return ret; \ +#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(fmt::runtime(msg), std::move(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__ \ - ); + 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( \ - fmt::runtime(msg), \ - std::move(var_name).unwrap_error() \ - ), \ - err_info \ - ); \ - return var_name; \ - } \ +#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(fmt::runtime(msg), std::move(var_name).unwrap_error()), err_info); \ + return var_name; \ + } \ value = std::move(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__ \ - ); + 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(fmt::runtime(msg), var_name.unwrap_error()), \ - err_info \ - ); \ - return ret; \ - } \ +#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(fmt::runtime(msg), var_name.unwrap_error()), err_info); \ + return ret; \ + } \ value = std::move(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__ \ - ); + IKARUS_VTRYRV_OR_FAIL_IMPL(CPPBASE_UNIQUE_NAME(result), value, ret, msg, err_info, __VA_ARGS__); -#define IKARUS_FAIL_IF_ERROR(ret) \ - if (ikarus_error_data_is_error(error_out)) { \ - return ret; \ +#define IKARUS_FAIL_IF_ERROR(ret, error) \ + if (ikarus_error_data_is_error(error)) { \ + if (error_out) { \ + *error_out = *error; \ + } \ + 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_FAIL_IF_NULL(ptr, ret) \ + IKARUS_FAIL_IF(((ptr) == nullptr), ret, #ptr " must not be null", IkarusErrorInfo_Client_InvalidNull) #define IKARUS_FAIL_IF_NAME_INVALID(name, ret) \ IKARUS_FAIL_IF_NULL(name, ret); \ - IKARUS_FAIL_IF( \ - cppbase::is_empty_or_blank(name), \ - ret, \ - #name " must not be empty", \ - IkarusErrorInfo_Client_InvalidInput \ - ); + IKARUS_FAIL_IF(cppbase::is_empty_or_blank(name), ret, "name must not be empty", IkarusErrorInfo_Client_InvalidInput) -#define IKARUS_FAIL_IF_NOT_EXIST_IMPL(exists_name, object, ret) \ - IKARUS_VTRYRV_OR_FAIL( \ - auto exists_name, \ - ret, \ - fmt::format( \ - "failed to check if {} exists", \ - std::remove_cvref_t::object_name \ - ), \ - IkarusErrorInfo_Database_QueryFailed, \ - object->project->db->query_one( \ - fmt::format( \ - "SELECT EXISTS(SELECT 1 FROM `{}` WHERE `id` = ?)", \ - std::remove_cvref_t::table_name \ - ), \ - object->id \ - ) \ - ); \ - \ - IKARUS_FAIL_IF( \ - !exists_name, \ - ret, \ - fmt::format( \ - "{} doesn't exist", \ - std::remove_cvref_t::object_name \ - ), \ - IkarusErrorInfo_Client_NonExistent \ - ) +#define IKARUS_ASCERTAIN_IMPL(cond_name, ret, msg, err_info, func, ...) \ + auto cond_name = std::invoke(func, __VA_ARGS__, error_out); \ + IKARUS_FAIL_IF_ERROR(ret, error_out) \ + IKARUS_FAIL_IF(!cond_name, ret, msg, err_info) -#define IKARUS_FAIL_IF_NOT_EXIST(object, ret) \ - IKARUS_FAIL_IF_NOT_EXIST_IMPL(CPPBASE_UNIQUE_NAME(exists), object, ret) +#define IKARUS_ASCERTAIN(ret, msg, err_info, func, ...) \ + IKARUS_ASCERTAIN_IMPL(CPPBASE_UNIQUE_NAME(cond), ret, msg, err_info, func, __VA_ARGS__); + +#define IKARUS_CALL(ret, func, ...) \ + std::invoke(func, __VA_ARGS__, error_out); \ + IKARUS_FAIL_IF_ERROR(ret, error_out); + +#define IKARUS_VCALL(value, ret, func, ...) \ + value = std::invoke(func, __VA_ARGS__, error_out); \ + IKARUS_FAIL_IF_ERROR(ret, error_out); diff --git a/src/ikarus/objects/blueprint.cpp b/src/ikarus/objects/blueprint.cpp index 553bbe2..ba9e7b1 100644 --- a/src/ikarus/objects/blueprint.cpp +++ b/src/ikarus/objects/blueprint.cpp @@ -2,8 +2,72 @@ #include #include +#include #include IkarusBlueprint::IkarusBlueprint(struct IkarusProject * project, int64_t id): project{project}, id{id} {} + +IkarusBlueprint * ikarus_blueprint_create( + struct IkarusProject * project, + char const * name, + IkarusBlueprintCreateFlags flags, + struct IkarusErrorData * error_out +) { + IKARUS_FAIL_IF_NULL(project, nullptr); + IKARUS_FAIL_IF_NAME_INVALID(name, nullptr); + + IKARUS_TRYRV_OR_FAIL( + nullptr, + "failed to create entity: {}", + IkarusErrorInfo_Database_QueryFailed, + project->db->execute("INSERT INTO `blueprints`(`name`) VALUES(?)", name) + ); + + auto const id = project->db->last_insert_rowid(); + return new IkarusBlueprint{project, id}; +} + +IkarusBlueprint * ikarus_blueprint_create_from_entity( + struct IkarusEntity * entity, + char const * name, + IkarusBlueprintCreateFromEntityFlags flags, + struct IkarusErrorData * error_out +) { + IKARUS_TRYRV_OR_FAIL( + nullptr, + "{}", + IkarusErrorInfo_Database_QueryFailed, + ikarus_libikarus_func_call_to_result( + error_out, + ikarus_must_return_true(ikarus_entity_exists, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent), + entity + ) + ); + IKARUS_FAIL_IF_NAME_INVALID(name, nullptr); + + IKARUS_VTRYRV_OR_FAIL( + auto id, + nullptr, + "failed to create blueprint from entity: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->transact([&](auto * db) -> cppbase::Result { + CPPBASE_TRY(db->execute("INSERT INTO `blueprints`(`name`) VALUES(?)", name)); + + auto const id = db->last_insert_rowid(); + + CPPBASE_TRY(entity->project->db->execute( + "INSERT INTO `properties`(`blueprint`, `name`, `schema`) " + "SELECT ?, `name`, json_extract(`value`, '$.schema') FROM `entity_values` " + "WHERE `entity` = ?", + id, + entity->id + )) + + return cppbase::ok(entity->project->db->last_insert_rowid()); + }) + ); + + return new IkarusBlueprint{entity->project, id}; +} diff --git a/src/ikarus/objects/entity.cpp b/src/ikarus/objects/entity.cpp index 5abd085..aae7564 100644 --- a/src/ikarus/objects/entity.cpp +++ b/src/ikarus/objects/entity.cpp @@ -4,13 +4,28 @@ #include #include +#include #include +#include #include IkarusEntity::IkarusEntity(struct IkarusProject * project, int64_t id): project{project}, id{id} {} +bool ikarus_entity_exists(IkarusEntity * entity, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(entity, false); + IKARUS_VTRYRV_OR_FAIL( + auto exists, + false, + "failed to check whether entity exists: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->query_one("SELECT EXISTS(SELECT 1 FROM `entities` WHERE `id` = ?)", entity->id) + ); + + return exists; +} + IkarusEntity * ikarus_entity_create( struct IkarusProject * project, char const * name, @@ -31,98 +46,83 @@ IkarusEntity * ikarus_entity_create( return new IkarusEntity{project, id}; } -void ikarus_entity_delete( - IkarusEntity * entity, - IkarusEntityDeleteFlags flags, - IkarusErrorData * error_out -) { - IKARUS_FAIL_IF_NULL(entity, IKARUS_VOID_RETURN); - IKARUS_FAIL_IF_NOT_EXIST(entity, IKARUS_VOID_RETURN); +void ikarus_entity_delete(IkarusEntity * entity, IkarusEntityDeleteFlags flags, IkarusErrorData * error_out) { + IKARUS_ASCERTAIN( + IKARUS_VOID_RETURN, + "entity doesn't exist", + IkarusErrorInfo_Client_NonExistent, + ikarus_entity_exists, + entity + ); IKARUS_TRYRV_OR_FAIL( IKARUS_VOID_RETURN, "failed to delete entity: {}", IkarusErrorInfo_Database_QueryFailed, - entity->project->db - ->execute("DELETE FROM `entities` WHERE `id` = ?", entity->id) + entity->project->db->execute("DELETE FROM `entities` WHERE `id` = ?", entity->id) ); delete entity; } -IkarusEntity * ikarus_entity_copy( - IkarusEntity * entity, - IkarusEntityCopyFlags flags, - IkarusErrorData * error_out -) { - IKARUS_FAIL_IF_NULL(entity, nullptr); - IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); +IkarusEntity * ikarus_entity_copy(IkarusEntity * entity, IkarusEntityCopyFlags flags, IkarusErrorData * error_out) { + IKARUS_ASCERTAIN(nullptr, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_entity_exists, entity); IKARUS_VTRYRV_OR_FAIL( auto id, nullptr, "failed to copy entity: {}", IkarusErrorInfo_Database_QueryFailed, - entity->project->db->transact( - [entity](auto * db) - -> cppbase::Result { - TRY(entity->project->db->execute( - "INSERT INTO `entities`(`name`) " - "SELECT `name` FROM `entities` WHERE `id` = ?", - entity->id - )); + entity->project->db->transact([entity](auto * db) -> cppbase::Result { + CPPBASE_TRY(entity->project->db->execute( + "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 " - "`entity` = ?1", - entity->id - )) + CPPBASE_TRY(entity->project->db->execute( + "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` " - "WHERE `entity` = ?1", - entity->id - )) + CPPBASE_TRY(entity->project->db->execute( + "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` " - "WHERE `entity` = ?1", - entity->id - )) + CPPBASE_TRY(entity->project->db->execute( + "INSERT INTO `entity_blueprint_links`(`entity`, `blueprint`) " + "SELECT ?1, `property`, `value` FROM `entity_property_values` " + "WHERE `entity` = ?1", + entity->id + )) - return cppbase::ok(entity->project->db->last_insert_rowid()); - } - ) + return cppbase::ok(entity->project->db->last_insert_rowid()); + }) ); 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_NOT_EXIST(entity, nullptr); +IkarusProject * ikarus_entity_get_project(IkarusEntity * entity, IkarusErrorData * error_out) { + IKARUS_ASCERTAIN(nullptr, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_entity_exists, entity); return entity->project; } -char const * -ikarus_entity_get_name(IkarusEntity * entity, IkarusErrorData * error_out) { - IKARUS_FAIL_IF_NULL(entity, nullptr); - IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); +char const * ikarus_entity_get_name(IkarusEntity * entity, IkarusErrorData * error_out) { + IKARUS_ASCERTAIN(nullptr, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_entity_exists, entity); 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 - ) + entity->project->db->query_one("SELECT `name` FROM `entities` WHERE `id` = ?", entity->id) ); return name; @@ -134,33 +134,63 @@ void ikarus_entity_set_name( IkarusEntitySetNameFlags flags, IkarusErrorData * error_out ) { - IKARUS_FAIL_IF_NULL(entity, IKARUS_VOID_RETURN); - IKARUS_FAIL_IF_NOT_EXIST(entity, IKARUS_VOID_RETURN); + IKARUS_ASCERTAIN( + IKARUS_VOID_RETURN, + "entity doesn't exist", + IkarusErrorInfo_Client_NonExistent, + ikarus_entity_exists, + entity + ); 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( - "UPDATE `entities` SET `name` = ? WHERE `id` = ?", - name, - entity->id - ) + entity->project->db->execute("UPDATE `entities` SET `name` = ? WHERE `id` = ?", name, entity->id) ); } -struct IkarusBlueprint ** ikarus_entity_get_linked_blueprints( +bool ikarus_entity_is_linked_to_blueprint( IkarusEntity * entity, - size_t * size_out, + struct IkarusBlueprint * blueprint, IkarusErrorData * error_out ) { - IKARUS_FAIL_IF_NULL(entity, nullptr); - IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); - IKARUS_FAIL_IF_NULL(size_out, nullptr); + IKARUS_ASCERTAIN(false, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_entity_exists, entity); + IKARUS_ASCERTAIN( + false, + "blueprint doesn't exist", + IkarusErrorInfo_Client_NonExistent, + ikarus_blueprint_exists, + blueprint + ); - auto count = ikarus_entity_get_linked_blueprints_count(entity, error_out); - IKARUS_FAIL_IF_ERROR(nullptr); + IKARUS_FAIL_IF( + entity->project != blueprint->project, + false, + "blueprint does not belong to entity's project", + IkarusErrorInfo_Client_NotLinked + ); + + IKARUS_VTRYRV_OR_FAIL( + auto exists, + false, + "failed to check if 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 + ) + ); + + return exists; +} + +struct IkarusBlueprint ** +ikarus_entity_get_linked_blueprints(IkarusEntity * entity, size_t * size_out, IkarusErrorData * error_out) { + IKARUS_ASCERTAIN(false, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_entity_exists, entity); + IKARUS_VCALL(auto count, false, ikarus_entity_get_linked_blueprints_count, entity); std::int64_t ids[count]; @@ -189,22 +219,16 @@ struct IkarusBlueprint ** ikarus_entity_get_linked_blueprints( 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); +size_t ikarus_entity_get_linked_blueprints_count(IkarusEntity * entity, IkarusErrorData * error_out) { + IKARUS_ASCERTAIN(false, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_entity_exists, entity); 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 - ) + entity->project->db + ->query_one("SELECT COUNT(*) FROM `entity_blueprint_links` WHERE `entity` = ?", entity->id) ); return count; @@ -216,10 +240,28 @@ void ikarus_entity_link_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_ASCERTAIN( + IKARUS_VOID_RETURN, + "entity doesn't exist", + IkarusErrorInfo_Client_NonExistent, + ikarus_entity_exists, + entity + ); + + IKARUS_ASCERTAIN( + IKARUS_VOID_RETURN, + "blueprint doesn't exist", + IkarusErrorInfo_Client_NonExistent, + ikarus_blueprint_exists, + blueprint + ); + + IKARUS_FAIL_IF( + entity->project != blueprint->project, + IKARUS_VOID_RETURN, + "blueprint does not belong to entity's project", + IkarusErrorInfo_Client_NotLinked + ); IKARUS_TRYRV_OR_FAIL( IKARUS_VOID_RETURN, @@ -239,10 +281,28 @@ void ikarus_entity_unlink_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_ASCERTAIN( + IKARUS_VOID_RETURN, + "entity doesn't exist", + IkarusErrorInfo_Client_NonExistent, + ikarus_entity_exists, + entity + ); + + IKARUS_ASCERTAIN( + IKARUS_VOID_RETURN, + "blueprint doesn't exist", + IkarusErrorInfo_Client_NonExistent, + ikarus_blueprint_exists, + blueprint + ); + + IKARUS_FAIL_IF( + entity->project != blueprint->project, + IKARUS_VOID_RETURN, + "blueprint does not belong to entity's project", + IkarusErrorInfo_Client_NotLinked + ); IKARUS_TRYRV_OR_FAIL( IKARUS_VOID_RETURN, @@ -256,13 +316,28 @@ void ikarus_entity_unlink_blueprint( ); } -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); +bool ikarus_entity_has_value(IkarusEntity * entity, char const * name, IkarusErrorData * error_out) { + IKARUS_FAIL_IF_NULL(name, false); + + IKARUS_ASCERTAIN(false, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_entity_exists, entity); + + IKARUS_VTRYRV_OR_FAIL( + auto exists, + false, + "failed to check if entity has value: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->query_one( + "SELECT EXISTS(SELECT 1 FROM `entity_values` WHERE `entity` = ? AND `name` = ?)", + entity->id, + name + ) + ); + + return exists; +} + +IkarusEntityValue * ikarus_entity_get_values(IkarusEntity * entity, size_t * size_out, IkarusErrorData * error_out) { + IKARUS_ASCERTAIN(nullptr, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_entity_exists, entity); IKARUS_VTRYRV_OR_FAIL( auto values_plain, @@ -277,17 +352,9 @@ IkarusEntityValue * ikarus_entity_get_values( 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>() - }; - } - ); + 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(); @@ -296,15 +363,19 @@ IkarusEntityValue * ikarus_entity_get_values( return values; } -char const * ikarus_entity_get_value( - IkarusEntity * entity, - char const * name, - IkarusErrorData * error_out -) { - IKARUS_FAIL_IF_NULL(entity, nullptr); - IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); +char const * ikarus_entity_get_value(IkarusEntity * entity, char const * name, IkarusErrorData * error_out) { IKARUS_FAIL_IF_NULL(name, nullptr); + IKARUS_ASCERTAIN(nullptr, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_entity_exists, entity); + IKARUS_ASCERTAIN( + nullptr, + "entity doesn't have value", + IkarusErrorInfo_Client_NotLinked, + ikarus_entity_has_value, + entity, + name + ); + IKARUS_VTRYRV_OR_FAIL( auto value, nullptr, @@ -326,10 +397,16 @@ void ikarus_entity_set_value( char const * value, 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_FAIL_IF_NULL(value, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF_NAME_INVALID(name, IKARUS_VOID_RETURN); + + IKARUS_ASCERTAIN( + IKARUS_VOID_RETURN, + "entity doesn't exist", + IkarusErrorInfo_Client_NonExistent, + ikarus_entity_exists, + entity + ); // parsing from & to here to ensure values are valid JSON & formatted // uniformly @@ -338,7 +415,7 @@ void ikarus_entity_set_value( IKARUS_VOID_RETURN, "cannot parse value as JSON: {}", IkarusErrorInfo_Client_InvalidInput, - IkarusValue::from_json(value) + IkarusValue::from_json_str(value) ); IKARUS_TRYRV_OR_FAIL( @@ -361,29 +438,64 @@ void ikarus_entity_delete_value( 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_ASCERTAIN( + IKARUS_VOID_RETURN, + "entity doesn't exist", + IkarusErrorInfo_Client_NonExistent, + ikarus_entity_exists, + entity + ); + 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 - ) + entity->project->db->execute("DELETE FROM `entity_values` WHERE `entity` = ? AND `name` = ?", entity->id, name) ); } -IkarusEntityPropertyValue * ikarus_entity_get_property_values( +bool ikarus_entity_has_property_value( IkarusEntity * entity, - size_t * size_out, + struct IkarusProperty * property, IkarusErrorData * error_out ) { - IKARUS_FAIL_IF_NULL(entity, nullptr); - IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); + IKARUS_ASCERTAIN(false, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_entity_exists, entity); + + IKARUS_ASCERTAIN( + false, + "property doesn't exist", + IkarusErrorInfo_Client_NonExistent, + ikarus_property_exists, + property + ); + + IKARUS_FAIL_IF( + entity->project != property->project, + false, + "property does not belong to entity's project", + IkarusErrorInfo_Client_NotLinked + ); + + IKARUS_VTRYRV_OR_FAIL( + auto exists, + false, + "failed to check if entity has property value: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->query_one( + "SELECT EXISTS(SELECT 1 FROM `entity_property_values` WHERE `entity` = ? AND `property` = ?)", + entity->id, + property->id + ) + ); + + return exists; +} + +IkarusEntityPropertyValue * +ikarus_entity_get_property_values(IkarusEntity * entity, size_t * size_out, IkarusErrorData * error_out) { + IKARUS_ASCERTAIN(false, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_entity_exists, entity); IKARUS_VTRYRV_OR_FAIL( auto values_plain, @@ -391,24 +503,20 @@ IkarusEntityPropertyValue * ikarus_entity_get_property_values( "failed to get property values for entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->query_many( - "SELECT `property`, `value` FROM `entity_property_values` WHERE `entity` = ?", + "SELECT `e`.`property`, IFNULL(`e`.`value`, ikarus_default_value(`p`.`schema`)) FROM `entity_property_values` AS `e` " + "INNER JOIN `properties` AS `p` ON `p`.`id` = `e`.`property` " + " WHERE `e`.`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>() - }; - } - ); + 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(); @@ -417,15 +525,23 @@ IkarusEntityPropertyValue * ikarus_entity_get_property_values( 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); +char const * +ikarus_entity_get_property_value(IkarusEntity * entity, struct IkarusProperty * property, IkarusErrorData * error_out) { + IKARUS_ASCERTAIN(false, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_entity_exists, entity); + IKARUS_ASCERTAIN( + false, + "property doesn't exist", + IkarusErrorInfo_Client_NonExistent, + ikarus_property_exists, + property + ); + + IKARUS_FAIL_IF( + entity->project != property->project, + nullptr, + "property does not belong to entity's project", + IkarusErrorInfo_Client_NotLinked + ); IKARUS_VTRYRV_OR_FAIL( auto value, @@ -433,7 +549,9 @@ char const * ikarus_entity_get_property_value( "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` = ?", + "SELECT IFNULL(`e`.`value`, ikarus_default_value(`p`.`schema`)) FROM `entity_property_values` AS `e` " + "INNER JOIN `properties` AS `p` ON `p`.`id` = `e`.`property` " + "WHERE `e`.`entity` = ? AND `e`.`property` = ?", entity->id, property->id ) @@ -449,12 +567,38 @@ void ikarus_entity_set_property_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_ASCERTAIN( + IKARUS_VOID_RETURN, + "entity doesn't exist", + IkarusErrorInfo_Client_NonExistent, + ikarus_entity_exists, + entity + ); + IKARUS_ASCERTAIN( + IKARUS_VOID_RETURN, + "property doesn't exist", + IkarusErrorInfo_Client_NonExistent, + ikarus_property_exists, + property + ); IKARUS_FAIL_IF_NULL(value, IKARUS_VOID_RETURN); + IKARUS_FAIL_IF( + entity->project != property->project, + IKARUS_VOID_RETURN, + "property does not belong to entity's project", + IkarusErrorInfo_Client_NotLinked + ); + + IKARUS_ASCERTAIN( + IKARUS_VOID_RETURN, + "entity does not have property value", + IkarusErrorInfo_Client_NotLinked, + ikarus_entity_has_property_value, + entity, + property + ); + // parsing from & to here to ensure values are valid JSON & formatted // uniformly IKARUS_VTRYRV_OR_FAIL( @@ -462,7 +606,7 @@ void ikarus_entity_set_property_value( IKARUS_VOID_RETURN, "cannot parse value as JSON: {}", IkarusErrorInfo_Client_InvalidInput, - IkarusValue::from_json(value) + IkarusValue::from_json_str(value) ); IKARUS_TRYRV_OR_FAIL( @@ -478,3 +622,51 @@ void ikarus_entity_set_property_value( ) ); } + +void ikarus_entity_clear_property_value( + IkarusEntity * entity, + struct IkarusProperty * property, + IkarusEntitySetPropertyValueFlags flags, + IkarusErrorData * error_out +) { + IKARUS_ASCERTAIN( + IKARUS_VOID_RETURN, + "entity doesn't exist", + IkarusErrorInfo_Client_NonExistent, + ikarus_entity_exists, + entity + ); + IKARUS_ASCERTAIN( + IKARUS_VOID_RETURN, + "property doesn't exist", + IkarusErrorInfo_Client_NonExistent, + ikarus_property_exists, + property + ); + + IKARUS_FAIL_IF( + entity->project != property->project, + IKARUS_VOID_RETURN, + "property does not belong to entity's project", + IkarusErrorInfo_Client_NotLinked + ); + IKARUS_ASCERTAIN( + IKARUS_VOID_RETURN, + "entity doesn't have property", + IkarusErrorInfo_Client_NotLinked, + ikarus_entity_has_property_value, + entity, + property + ); + + IKARUS_TRYRV_OR_FAIL( + IKARUS_VOID_RETURN, + "failed to clear property value for entity: {}", + IkarusErrorInfo_Database_QueryFailed, + entity->project->db->execute( + "DELETE FROM `entity_property_values` WHERE `entity` = ? AND `property` = ?", + entity->id, + property->id + ) + ); +} diff --git a/src/ikarus/persistence/migrations/m0_initial_layout.sql b/src/ikarus/persistence/migrations/m0_initial_layout.sql index e83b41a..50fb7f0 100644 --- a/src/ikarus/persistence/migrations/m0_initial_layout.sql +++ b/src/ikarus/persistence/migrations/m0_initial_layout.sql @@ -26,8 +26,7 @@ CREATE TABLE `properties` `blueprint` INTEGER NOT NULL REFERENCES `blueprints` (`id`) ON DELETE CASCADE, `name` TEXT NOT NULL, `schema` TEXT NOT NULL, - `default_value` TEXT NOT NULL, - `settings` TEXT NOT NULL + `default_value` TEXT ) STRICT; CREATE TABLE `entity_blueprint_links` diff --git a/src/ikarus/persistence/project.cpp b/src/ikarus/persistence/project.cpp index 75c8825..0028486 100644 --- a/src/ikarus/persistence/project.cpp +++ b/src/ikarus/persistence/project.cpp @@ -73,6 +73,49 @@ auto create_impl( .on_error(close_db) ); + db->create_function( + "ikarus_default_value", + 1, + SQLITE_UTF8 | SQLITE_DETERMINISTIC, + nullptr, + [](sqlite3_context * ctx, int argc, sqlite3_value ** argv) { + if (sqlite3_value_type(argv[0]) != SQLITE_TEXT) { + sqlite3_result_error( + ctx, + "expected the 'schema' parameter to be of type text", + SQLITE_MISMATCH + ); + return; + } + + auto const schema_json = + reinterpret_cast(sqlite3_value_text(argv[0])); + + auto schema_res = IkarusValueSchema::from_json_str(schema_json); + + if (schema_res.is_error()) { + sqlite3_result_error( + ctx, + "failed to parse schema", + SQLITE_ERROR + ); + return; + } + + auto schema = std::move(schema_res).unwrap_value(); + + auto default_value_json = + IkarusValue::to_json(schema.default_value()).dump(); + + sqlite3_result_text( + ctx, + default_value_json.data(), + default_value_json.size(), + SQLITE_TRANSIENT + ); + } + ); + return std::move(db); } diff --git a/src/ikarus/values/data.cpp b/src/ikarus/values/data.cpp index f7f6845..af9a1fc 100644 --- a/src/ikarus/values/data.cpp +++ b/src/ikarus/values/data.cpp @@ -20,7 +20,7 @@ auto get_primitive_type(IkarusValueDataPrimitive const & primitive) [](IkarusValueDataPrimitiveNumber const &) { return IkarusValuePrimitiveType_Number; }, - [](IkarusValueDataPrimitiveString const &) { + [](IkarusValueDataPrimitiveText const &) { return IkarusValuePrimitiveType_Text; } }, @@ -36,7 +36,7 @@ auto IkarusValueData::from_json(nlohmann::json const & json) IkarusValueData value{}; - VTRY( + CPPBASE_VTRY( auto type, deserialize_enum( json, @@ -48,7 +48,7 @@ auto IkarusValueData::from_json(nlohmann::json const & json) switch (type) { case IkarusValueDataType_Primitive: { - VTRY( + CPPBASE_VTRY( auto primitive, deserialize_enum( json, @@ -60,21 +60,21 @@ auto IkarusValueData::from_json(nlohmann::json const & json) switch (primitive) { case IkarusValuePrimitiveType_Toggle: { - VTRY(auto data, deserialize_any(json, "data")); + CPPBASE_VTRY(auto data, deserialize_any(json, "data")); value.variant = IkarusValueDataPrimitiveToggle{data}; break; } case IkarusValuePrimitiveType_Number: { - VTRY(auto data, deserialize_any(json, "data")); + CPPBASE_VTRY(auto data, deserialize_any(json, "data")); value.variant = IkarusValueDataPrimitiveNumber{data}; break; } case IkarusValuePrimitiveType_Text: { - VTRY(auto data, deserialize_any(json, "data")); + CPPBASE_VTRY(auto data, deserialize_any(json, "data")); - value.variant = IkarusValueDataPrimitiveString{data}; + value.variant = IkarusValueDataPrimitiveText{data}; break; } } @@ -83,14 +83,17 @@ auto IkarusValueData::from_json(nlohmann::json const & json) } case IkarusValueDataType_List: { std::vector> values_data{}; - VTRY( + CPPBASE_VTRY( auto data_json, deserialize_any>(json, "data") ); values_data.reserve(data_json.size()); for (auto const & data_json : data_json) { - VTRY(auto value_data, IkarusValueData::from_json(data_json)); + CPPBASE_VTRY( + auto value_data, + IkarusValueData::from_json(data_json) + ); values_data.emplace_back( cppbase::make_owning(std::move(value_data)) ); @@ -105,7 +108,7 @@ auto IkarusValueData::from_json(nlohmann::json const & json) cppbase::owning_ptr>> map_data{}; - VTRY( + CPPBASE_VTRY( auto map_data_json, deserialize_any>(json, "data") ); @@ -113,10 +116,10 @@ auto IkarusValueData::from_json(nlohmann::json const & json) map_data.reserve(map_data_json.size()); for (auto const & pair_json : map_data_json) { - VTRY(auto key_json, get_key(pair_json, "key")); - VTRY(auto value_json, get_key(pair_json, "value")); - VTRY(auto key, IkarusValueData::from_json(*key_json)); - VTRY(auto value, IkarusValueData::from_json(*value_json)); + CPPBASE_VTRY(auto key_json, get_key(pair_json, "key")); + CPPBASE_VTRY(auto value_json, get_key(pair_json, "value")); + CPPBASE_VTRY(auto key, IkarusValueData::from_json(*key_json)); + CPPBASE_VTRY(auto value, IkarusValueData::from_json(*value_json)); map_data.emplace_back( cppbase::make_owning(key), @@ -130,7 +133,7 @@ auto IkarusValueData::from_json(nlohmann::json const & json) case IkarusValueDataType_Tuple: { std::vector> values_data{}; - VTRY( + CPPBASE_VTRY( auto values_json, deserialize_any>(json, "data") ); @@ -138,7 +141,10 @@ auto IkarusValueData::from_json(nlohmann::json const & json) values_data.reserve(values_json.size()); for (auto const & value_json : values_json) { - VTRY(auto value_data, IkarusValueData::from_json(value_json)); + CPPBASE_VTRY( + auto value_data, + IkarusValueData::from_json(value_json) + ); values_data.emplace_back( cppbase::make_owning(value_data) ); @@ -152,6 +158,19 @@ auto IkarusValueData::from_json(nlohmann::json const & json) return cppbase::ok(value); } +auto IkarusValueData::from_json_str(std::string_view json_str) + -> cppbase::Result { + auto json = nlohmann::json::parse(json_str, nullptr, false); + + if (json.is_discarded()) { + return cppbase::err( + IkarusValueDataParseError{IkarusJsonError{IkarusJsonParseError{}}} + ); + } + + return IkarusValueData::from_json(json); +} + auto IkarusValueData::to_json(IkarusValueData const & value) -> nlohmann::json { nlohmann::json json = nlohmann::json::object(); @@ -170,7 +189,7 @@ auto IkarusValueData::to_json(IkarusValueData const & value) -> nlohmann::json { json["primitive"] = IkarusValuePrimitiveType_Number; json["data"] = number.value; }, - [&](IkarusValueDataPrimitiveString const & string) { + [&](IkarusValueDataPrimitiveText const & string) { json["type"] = IkarusValueDataType_Primitive; json["primitive"] = IkarusValuePrimitiveType_Text; json["data"] = string.value; diff --git a/src/ikarus/values/data.hpp b/src/ikarus/values/data.hpp index 1c9b3d8..2e68949 100644 --- a/src/ikarus/values/data.hpp +++ b/src/ikarus/values/data.hpp @@ -22,14 +22,14 @@ struct IkarusValueDataPrimitiveNumber { double value; }; -struct IkarusValueDataPrimitiveString { +struct IkarusValueDataPrimitiveText { std::string value; }; using IkarusValueDataPrimitive = std::variant< IkarusValueDataPrimitiveToggle, IkarusValueDataPrimitiveNumber, - IkarusValueDataPrimitiveString>; + IkarusValueDataPrimitiveText>; struct IkarusValueDataList { std::vector> values; @@ -53,7 +53,9 @@ struct IkarusValueData { IkarusValueDataMap, IkarusValueDataTuple>; - static auto from_json(nlohmann::json const & json) + static auto from_json(nlohmann::json const & json_str) + -> cppbase::Result; + static auto from_json_str(std::string_view json_str) -> cppbase::Result; static auto to_json(IkarusValueData const & value) -> nlohmann::json; diff --git a/src/ikarus/values/errors.hpp b/src/ikarus/values/errors.hpp index f472d83..1b8a880 100644 --- a/src/ikarus/values/errors.hpp +++ b/src/ikarus/values/errors.hpp @@ -10,12 +10,15 @@ struct IkarusJsonInvalidTypeError {}; struct IkarusJsonEnumOutOfBoundsError {}; +struct IkarusJsonParseError {}; + struct IkarusJsonUnknownError {}; using IkarusJsonError = std::variant< IkarusJsonMissingKeyError, IkarusJsonInvalidTypeError, IkarusJsonEnumOutOfBoundsError, + IkarusJsonParseError, IkarusJsonUnknownError>; struct IkarusValueSchemaParseError { @@ -57,6 +60,16 @@ struct fmt::formatter : formatter { } }; +template<> +struct fmt::formatter : formatter { + constexpr static auto format( + [[maybe_unused]] IkarusJsonParseError const & error, + fmt::format_context & ctx + ) { + return fmt::format_to(ctx.out(), "buffer isn't valid JSON"); + } +}; + template<> struct fmt::formatter : formatter { constexpr static auto format( diff --git a/src/ikarus/values/schema.cpp b/src/ikarus/values/schema.cpp index ea2e6dc..3c53fe3 100644 --- a/src/ikarus/values/schema.cpp +++ b/src/ikarus/values/schema.cpp @@ -5,6 +5,7 @@ #include #include #include +#include auto IkarusValueSchema::from_json(nlohmann::json const & json) -> cppbase::Result { @@ -18,7 +19,7 @@ auto IkarusValueSchema::from_json(nlohmann::json const & json) IkarusValueSchema schema{}; - VTRY( + CPPBASE_VTRY( auto type, deserialize_enum( json, @@ -30,7 +31,7 @@ auto IkarusValueSchema::from_json(nlohmann::json const & json) switch (type) { case IkarusValueSchemaType_Primitive: { - VTRY( + CPPBASE_VTRY( auto primitive, deserialize_enum( json, @@ -43,15 +44,21 @@ auto IkarusValueSchema::from_json(nlohmann::json const & json) break; } case IkarusValueSchemaType_List: { - VTRY(auto sub_schema, IkarusValueSchema::from_json(json["schema"])); + CPPBASE_VTRY( + auto sub_schema, + IkarusValueSchema::from_json(json["schema"]) + ); schema.variant = IkarusValueSchemaList{ cppbase::make_owning(std::move(sub_schema)) }; break; } case IkarusValueSchemaType_Map: { - VTRY(auto key_schema, IkarusValueSchema::from_json(json["key_schema"])); - VTRY( + CPPBASE_VTRY( + auto key_schema, + IkarusValueSchema::from_json(json["key_schema"]) + ); + CPPBASE_VTRY( auto value_schema, IkarusValueSchema::from_json(json["value_schema"]) ); @@ -62,7 +69,7 @@ auto IkarusValueSchema::from_json(nlohmann::json const & json) break; } case IkarusValueSchemaType_Tuple: { - VTRY( + CPPBASE_VTRY( auto sub_schemas_json, deserialize_any>(json, "schemas") ); @@ -71,7 +78,10 @@ auto IkarusValueSchema::from_json(nlohmann::json const & json) sub_schemas.reserve(sub_schemas_json.size()); for (auto const & sub_schema_json : sub_schemas_json) { - VTRY(auto schema, IkarusValueSchema::from_json(sub_schema_json)); + CPPBASE_VTRY( + auto schema, + IkarusValueSchema::from_json(sub_schema_json) + ); sub_schemas.emplace_back( cppbase::make_owning(std::move(schema)) ); @@ -85,6 +95,19 @@ auto IkarusValueSchema::from_json(nlohmann::json const & json) return cppbase::ok(std::move(schema)); } +auto IkarusValueSchema::from_json_str(std::string_view json_str) + -> cppbase::Result { + auto json = nlohmann::json::parse(json_str, nullptr, false); + + if (json.is_discarded()) { + return cppbase::err( + IkarusValueSchemaParseError{IkarusJsonError{IkarusJsonParseError{}}} + ); + } + + return IkarusValueSchema::from_json(json); +} + auto IkarusValueSchema::to_json(IkarusValueSchema const & schema) -> nlohmann::json { nlohmann::json json = nlohmann::json::object(); @@ -173,3 +196,46 @@ auto IkarusValueSchema::validate(IkarusValueData const & data) const -> bool { data.variant ); } + +auto IkarusValueSchema::default_value_data() const -> IkarusValueData { + return std::visit( + cppbase::overloaded{ + [](IkarusValueSchemaPrimitive const & schema) -> IkarusValueData { + switch (schema.type) { + case IkarusValuePrimitiveType_Toggle: { + return {{IkarusValueDataPrimitiveToggle{false}}}; + } + case IkarusValuePrimitiveType_Number: + return {{IkarusValueDataPrimitiveNumber{0.0}}}; + case IkarusValuePrimitiveType_Text: + return {{IkarusValueDataPrimitiveText{""}}}; + } + }, + [](IkarusValueSchemaList const & schema) -> IkarusValueData { + return {IkarusValueDataList{{}}}; + }, + [](IkarusValueSchemaMap const & schema) -> IkarusValueData { + return {IkarusValueDataMap{{}}}; + }, + [](IkarusValueSchemaTuple const & schema) -> IkarusValueData { + IkarusValueDataTuple data{}; + data.values.reserve(schema.sub_schemas.size()); + + for (auto const & sub_schema : schema.sub_schemas) { + data.values.emplace_back( + cppbase::make_owning( + sub_schema->default_value().data + ) + ); + } + + return {data}; + } + }, + variant + ); +} + +auto IkarusValueSchema::default_value() const -> IkarusValue { + return IkarusValue{*this, default_value_data()}; +} diff --git a/src/ikarus/values/schema.hpp b/src/ikarus/values/schema.hpp index 7fd07ab..e8df649 100644 --- a/src/ikarus/values/schema.hpp +++ b/src/ikarus/values/schema.hpp @@ -42,9 +42,13 @@ struct IkarusValueSchema { static auto from_json(nlohmann::json const & json) -> cppbase::Result; + static auto from_json_str(std::string_view json_str) + -> cppbase::Result; static auto to_json(IkarusValueSchema const & value) -> nlohmann::json; auto validate(IkarusValueData const & data) const -> bool; + auto default_value_data() const -> IkarusValueData; + auto default_value() const -> IkarusValue; IkarusValueSchemaVariant variant; }; diff --git a/src/ikarus/values/shared.hpp b/src/ikarus/values/shared.hpp index a4d50ac..15e6753 100644 --- a/src/ikarus/values/shared.hpp +++ b/src/ikarus/values/shared.hpp @@ -32,7 +32,7 @@ auto deserialize_enum( E min, E max ) -> cppbase::Result { - VTRY(auto iter, get_key(json, key)); + CPPBASE_VTRY(auto iter, get_key(json, key)); if (!iter->is_number_integer()) { return cppbase::err(IkarusJsonError{}); @@ -51,7 +51,7 @@ auto deserialize_enum( template auto deserialize_any(nlohmann::json const & json, std::string_view key) -> cppbase::Result { - VTRY(auto iter, get_key(json, key)); + CPPBASE_VTRY(auto iter, get_key(json, key)); try { return cppbase::ok(iter->get()); diff --git a/src/ikarus/values/value.cpp b/src/ikarus/values/value.cpp index 9a23a98..304e656 100644 --- a/src/ikarus/values/value.cpp +++ b/src/ikarus/values/value.cpp @@ -15,8 +15,8 @@ auto IkarusValue::from_json(nlohmann::json const & json) IkarusValue value{}; - VTRY(value.schema, IkarusValueSchema::from_json(json["schema"])); - VTRY(value.data, IkarusValueData::from_json(json["data"])); + CPPBASE_VTRY(value.schema, IkarusValueSchema::from_json(json["schema"])); + CPPBASE_VTRY(value.data, IkarusValueData::from_json(json["data"])); if (!value.schema.validate(value.data)) { return cppbase::err(IkarusValueParseErrorDataSchemaMismatch{}); @@ -25,6 +25,19 @@ auto IkarusValue::from_json(nlohmann::json const & json) return cppbase::ok(std::move(value)); } +auto IkarusValue::from_json_str(std::string_view json_str) + -> cppbase::Result { + auto json = nlohmann::json::parse(json_str, nullptr, false); + + if (json.is_discarded()) { + return cppbase::err( + IkarusValueDataParseError{IkarusJsonError{IkarusJsonParseError{}}} + ); + } + + return IkarusValue::from_json(json); +} + auto IkarusValue::to_json(IkarusValue const & value) -> nlohmann::json { nlohmann::json json = nlohmann::json::object(); diff --git a/src/ikarus/values/value.hpp b/src/ikarus/values/value.hpp index 74db914..cb70b1f 100644 --- a/src/ikarus/values/value.hpp +++ b/src/ikarus/values/value.hpp @@ -14,5 +14,7 @@ struct IkarusValue { static auto from_json(nlohmann::json const & json) -> cppbase::Result; + static auto from_json_str(std::string_view json_str) + -> cppbase::Result; static auto to_json(IkarusValue const & value) -> nlohmann::json; }; diff --git a/vendor/cppbase b/vendor/cppbase index e7741e0..82da677 160000 --- a/vendor/cppbase +++ b/vendor/cppbase @@ -1 +1 @@ -Subproject commit e7741e0e60f380f8b473c0881f3d8b4f23d0df91 +Subproject commit 82da6775fc56649cff6e611d6826a8e8fd321baa diff --git a/vendor/sqlitecpp b/vendor/sqlitecpp index 5bfd32f..5fd5731 160000 --- a/vendor/sqlitecpp +++ b/vendor/sqlitecpp @@ -1 +1 @@ -Subproject commit 5bfd32f66164ed3d2c28003139fc5f76ef5e206d +Subproject commit 5fd5731c98d6e260e35d05a59c99822e32fe7191