From f6022fe730070bf6ae07a2f6b81d2f3659493ff3 Mon Sep 17 00:00:00 2001 From: MarzellT Date: Fri, 30 Aug 2024 16:57:04 +0200 Subject: [PATCH] adds schema for validating the mapping files Signed-off-by: MarzellT --- modules/OCPPConfiguration/CMakeLists.txt | 1 + .../OCPPConfiguration/OCPPConfiguration.cpp | 9 ++-- .../OCPPConfiguration/OCPPConfiguration.hpp | 1 + modules/OCPPConfiguration/doc.rst | 13 +++-- modules/OCPPConfiguration/docs/index.rst | 23 -------- .../OCPPConfiguration/main/event_handler.cpp | 5 +- .../OCPPConfiguration/main/event_handler.hpp | 4 +- .../OCPPConfiguration/main/mapping_reader.cpp | 22 ++++++-- .../OCPPConfiguration/main/mapping_reader.hpp | 8 ++- modules/OCPPConfiguration/manifest.yaml | 4 ++ .../mapping_schemas/CMakeLists.txt | 10 ++++ .../mapping_schemas/mapping_schema.yaml | 52 +++++++++++++++++++ 12 files changed, 114 insertions(+), 38 deletions(-) delete mode 100644 modules/OCPPConfiguration/docs/index.rst create mode 100644 modules/OCPPConfiguration/mapping_schemas/CMakeLists.txt create mode 100644 modules/OCPPConfiguration/mapping_schemas/mapping_schema.yaml diff --git a/modules/OCPPConfiguration/CMakeLists.txt b/modules/OCPPConfiguration/CMakeLists.txt index 29003b8283..f3da2e051e 100644 --- a/modules/OCPPConfiguration/CMakeLists.txt +++ b/modules/OCPPConfiguration/CMakeLists.txt @@ -42,4 +42,5 @@ target_compile_definitions( ) add_subdirectory(mappings) +add_subdirectory(mapping_schemas) # ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 diff --git a/modules/OCPPConfiguration/OCPPConfiguration.cpp b/modules/OCPPConfiguration/OCPPConfiguration.cpp index 19cd1e2dbd..fc1f679433 100644 --- a/modules/OCPPConfiguration/OCPPConfiguration.cpp +++ b/modules/OCPPConfiguration/OCPPConfiguration.cpp @@ -13,12 +13,13 @@ void OCPPConfiguration::init() { void OCPPConfiguration::ready() { invoke_ready(*p_main); - const auto mapping_file_path = std::filesystem::path{config.mapping_file_name}; - try { - event_handler = std::make_unique(mapping_file_path); + event_handler = std::make_unique(config.mapping_file_name, config.schema_file_name); } catch (const std::runtime_error& e) { - EVLOG_warning << "Failed to create event handler: " << e.what() << ".\nNo events will be handled!"; + EVLOG_warning << "Failed to create event handler: " << e.what() << "No events will be handled!"; + return; + } catch (const std::exception& e) { + EVLOG_warning << "Failed to create event handler: " << e.what() << "No events will be handled!"; return; } diff --git a/modules/OCPPConfiguration/OCPPConfiguration.hpp b/modules/OCPPConfiguration/OCPPConfiguration.hpp index 303119e3c2..f6bc0ad464 100644 --- a/modules/OCPPConfiguration/OCPPConfiguration.hpp +++ b/modules/OCPPConfiguration/OCPPConfiguration.hpp @@ -26,6 +26,7 @@ namespace module { struct Conf { std::string user_config_file_name; std::string mapping_file_name; + std::string schema_file_name; }; class OCPPConfiguration : public Everest::ModuleBase { diff --git a/modules/OCPPConfiguration/doc.rst b/modules/OCPPConfiguration/doc.rst index 701b1d6bd8..0c0e1810ea 100644 --- a/modules/OCPPConfiguration/doc.rst +++ b/modules/OCPPConfiguration/doc.rst @@ -12,7 +12,7 @@ Configuration Options The following configuration options are available: -.. _user_config_path: +.. _user_config_name: **user_config_file_name** - **Description**: File name of the user configuration file. @@ -21,8 +21,15 @@ The following configuration options are available: parameters into this file in the installation directory. If this file shouldn't exist it will be automatically created. -.. _mapping_file_path: +.. _mapping_file_name: **mapping_file_name** - **Description**: Name of the mapping file. - This file has to be in the `mappings` directory of this module which will be installed. \ No newline at end of file + This file has to be in the `mappings` directory of this module which will be installed. + +.. _schema_file_name: + +**schema_file_name** + - **Description**: Name of the schema file. + This file has to be in the `mapping_schemas` directory of this module which will be installed. + This should probably be set to the default. diff --git a/modules/OCPPConfiguration/docs/index.rst b/modules/OCPPConfiguration/docs/index.rst deleted file mode 100644 index 33fd62b5c6..0000000000 --- a/modules/OCPPConfiguration/docs/index.rst +++ /dev/null @@ -1,23 +0,0 @@ -.. _everest_modules_handwritten_OCPPConfiguration: - -.. This file is a placeholder for optional multiple files -handwritten documentation for the OCPPConfiguration module. -Please decide whether you want to use the doc.rst file -or a set of files in the doc/ directory. -In the latter case, you can delete the doc.rst file. -In the former case, you can delete the doc/ directory. - -.. This handwritten documentation is optional. In case -you do not want to write it, you can delete this file -and the doc/ directory. - -.. The documentation can be written in reStructuredText, -and will be converted to HTML and PDF by Sphinx. -This index.rst file is the entry point for the module documentation. - -******************************************* -OCPPConfiguration -******************************************* - -:ref:`Link ` to the module's reference. -This module reacts to OCPP configuration changes. diff --git a/modules/OCPPConfiguration/main/event_handler.cpp b/modules/OCPPConfiguration/main/event_handler.cpp index ecf1f1a475..39ab641605 100644 --- a/modules/OCPPConfiguration/main/event_handler.cpp +++ b/modules/OCPPConfiguration/main/event_handler.cpp @@ -9,8 +9,9 @@ namespace module { -EventHandler::EventHandler(const std::filesystem::path& config_mapping_file_name) : - config_mapping(mapping_reader::read_mapping(config_mapping_file_name)) { +EventHandler::EventHandler(const std::filesystem::path& config_mapping_file_name, + const std::filesystem::path& mapping_schema_file_name) : + config_mapping(mapping_reader::read_mapping(config_mapping_file_name, mapping_schema_file_name)) { } void EventHandler::try_handle_event(const types::ocpp::EventData& event_data, diff --git a/modules/OCPPConfiguration/main/event_handler.hpp b/modules/OCPPConfiguration/main/event_handler.hpp index c0ffcbd118..b1b51b2a75 100644 --- a/modules/OCPPConfiguration/main/event_handler.hpp +++ b/modules/OCPPConfiguration/main/event_handler.hpp @@ -20,8 +20,10 @@ class EventHandler { * Create an EventHandler with the given mapping file. * * @param config_mapping_file_name Name of the mapping file. + * @param mapping_schema_file_name Name of the schema file. */ - explicit EventHandler(const std::filesystem::path& config_mapping_file_name); + EventHandler(const std::filesystem::path& config_mapping_file_name, + const std::filesystem::path& mapping_schema_file_name); /** * Try to handle the given event. If successful, the event will be written to the user configuration file given. diff --git a/modules/OCPPConfiguration/main/mapping_reader.cpp b/modules/OCPPConfiguration/main/mapping_reader.cpp index 7c6e29ce9e..579167af69 100644 --- a/modules/OCPPConfiguration/main/mapping_reader.cpp +++ b/modules/OCPPConfiguration/main/mapping_reader.cpp @@ -4,12 +4,14 @@ #include "mapping_reader.hpp" #include "everest/logging.hpp" #include "util.hpp" +#include +#include #include namespace module { namespace { -OcppToEverestConfigMapping parse_mapping(const c4::yml::NodeRef& root); +void validate_mapping_schema(const std::filesystem::path& mapping_path, const std::filesystem::path& schema_path); std::pair parse_mapping_node(const c4::yml::NodeRef& mapping_node); EverestConfigMapping parse_maps_to_node(const ryml::NodeRef& node); types::ocpp::Component parse_component_node(const c4::yml::NodeRef& node); @@ -27,6 +29,16 @@ OcppToEverestConfigMapping parse_mapping(const c4::yml::NodeRef& root) { return mapping; } +void validate_mapping_schema(const std::filesystem::path& mapping_path, const std::filesystem::path& schema_path) { + const auto config_as_json = Everest::load_yaml(mapping_path); + const auto schema_as_json = Everest::load_yaml(schema_path); + + // validate the mapping file with the schema + auto validator = nlohmann::json_schema::json_validator{}; + validator.set_root_schema(schema_as_json); + validator.validate(config_as_json); +} + std::pair parse_mapping_node(const c4::yml::NodeRef& mapping_node) { auto component_variable = parse_component_variable_node(mapping_node["ocpp_definition"]); auto module_mapping = parse_maps_to_node(mapping_node["everest_definition"]); @@ -111,8 +123,12 @@ EverestConfigMapping parse_maps_to_node(const ryml::NodeRef& node) { } // namespace -OcppToEverestConfigMapping mapping_reader::read_mapping(const std::filesystem::path& file_name) { - const auto mapping_file_path = MAPPING_INSTALL_DIRECTORY / file_name; +OcppToEverestConfigMapping mapping_reader::read_mapping(const std::filesystem::path& mapping_file_name, + const std::filesystem::path& schema_file_name) { + const auto mapping_file_path = MAPPING_INSTALL_DIRECTORY / mapping_file_name; + const auto mapping_schema_file_path = MAPPING_SCHEMA_INSTALL_DIRECTORY / schema_file_name; + + validate_mapping_schema(mapping_file_path, mapping_schema_file_path); const auto tree = util::load_yaml_file(mapping_file_path); const auto root = tree.rootref(); diff --git a/modules/OCPPConfiguration/main/mapping_reader.hpp b/modules/OCPPConfiguration/main/mapping_reader.hpp index 86bb2b8b97..5c46f21ae9 100644 --- a/modules/OCPPConfiguration/main/mapping_reader.hpp +++ b/modules/OCPPConfiguration/main/mapping_reader.hpp @@ -29,10 +29,14 @@ namespace mapping_reader { /** * Reads the mapping file and returns the mapping. * - * @param file_name The file name of the mapping file. + * @param mapping_file_name The file name of the mapping file. + * @param schema_file_name The file name of the schema file. * @return The mapping. + * @throws std::runtime_error if the files can't be opened. + * @throws std::exception if the schema validation fails. */ -OcppToEverestConfigMapping read_mapping(const std::filesystem::path& file_name); +OcppToEverestConfigMapping read_mapping(const std::filesystem::path& mapping_file_name, + const std::filesystem::path& schema_file_name); }; // namespace mapping_reader diff --git a/modules/OCPPConfiguration/manifest.yaml b/modules/OCPPConfiguration/manifest.yaml index d2517f13ea..26f6c51086 100644 --- a/modules/OCPPConfiguration/manifest.yaml +++ b/modules/OCPPConfiguration/manifest.yaml @@ -7,6 +7,10 @@ config: description: Name of the mapping file (in the `mappings` directory). type: string default: "example-config-mapping.yaml" + schema_file_name: + description: Name of the schema file (in the `mapping_schemas` directory). + type: string + default: "mapping_schema.yaml" provides: main: interface: empty diff --git a/modules/OCPPConfiguration/mapping_schemas/CMakeLists.txt b/modules/OCPPConfiguration/mapping_schemas/CMakeLists.txt new file mode 100644 index 0000000000..451534d370 --- /dev/null +++ b/modules/OCPPConfiguration/mapping_schemas/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copy all schema files (yaml) to the install directory +# and add a compile definition to it so the path can be used in code. + +set("${PROJECT_NAME}_${MODULE_NAME}_MAPPING_SCHEMA_DIR" "${CMAKE_INSTALL_PREFIX}/etc/everest/ocpp-configuration/mapping_schemas") + +install(DIRECTORY "." + DESTINATION "${${PROJECT_NAME}_${MODULE_NAME}_MAPPING_SCHEMA_DIR}" + FILES_MATCHING PATTERN "*.yaml") + +target_compile_definitions(${MODULE_NAME} PRIVATE MAPPING_SCHEMA_INSTALL_DIRECTORY="${${PROJECT_NAME}_${MODULE_NAME}_MAPPING_SCHEMA_DIR}") diff --git a/modules/OCPPConfiguration/mapping_schemas/mapping_schema.yaml b/modules/OCPPConfiguration/mapping_schemas/mapping_schema.yaml new file mode 100644 index 0000000000..c02bca7b5a --- /dev/null +++ b/modules/OCPPConfiguration/mapping_schemas/mapping_schema.yaml @@ -0,0 +1,52 @@ +$schema: http://json-schema.org/draft-07/schema# +description: Json schema for OCPP to Everest configuration mapping +type: array +items: + type: object + required: + - ocpp_definition + - everest_definition + properties: + ocpp_definition: + type: object + required: + - variable + properties: + component: + type: object + required: + - name + properties: + name: + type: string + instance: + type: string + evse: + type: object + required: + - id + properties: + id: + type: integer + connector_id: + type: integer + variable: + type: object + required: + - name + properties: + name: + type: string + instance: + type: string + everest_definition: + type: object + required: + - module_id + - config_param + properties: + module_id: + type: string + config_param: + type: string + additionalProperties: false \ No newline at end of file