diff --git a/CMakeLists.txt b/CMakeLists.txt index 997fc2fb2..95ae5c876 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,10 @@ cmake_minimum_required(VERSION 3.13) project("SilKit") +# Root of the SIL Kit tree; used by components (e.g. SilKitYaml) to locate +# vendored sources independently of the current subdirectory. +set(SILKIT_TOP_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + option(SILKIT_BUILD_DEMOS "Build the SIL Kit Demos" ON) option(SILKIT_BUILD_STATIC "Compile the SIL Kit as a static library" OFF) option(SILKIT_BUILD_TESTS "Enable unit and integration tests for the SIL Kit" ON) @@ -133,6 +137,10 @@ if(MINGW) endif() endif() +# Internal, self-contained YAML helper library (header-only). Must come before +# SilKit, which depends on it, and after ThirdParty, which provides rapidyaml. +add_subdirectory(SilKitYaml) + # Have both SIL Kit library and demo project in a single solution add_subdirectory(SilKit) if(SILKIT_BUILD_UTILITIES) @@ -158,6 +166,7 @@ if(SILKIT_INSTALL_SOURCE) ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${CMAKE_CURRENT_SOURCE_DIR}/Demos ${CMAKE_CURRENT_SOURCE_DIR}/SilKit + ${CMAKE_CURRENT_SOURCE_DIR}/SilKitYaml ${CMAKE_CURRENT_SOURCE_DIR}/Utilities DESTINATION ${INSTALL_SOURCE_DIR} COMPONENT source diff --git a/SilKit/source/CMakeLists.txt b/SilKit/source/CMakeLists.txt index 930af61ad..ebe1e1155 100644 --- a/SilKit/source/CMakeLists.txt +++ b/SilKit/source/CMakeLists.txt @@ -115,6 +115,14 @@ if (TARGET rapidyaml) ) endif() +if (TARGET SilKitYaml) + # Internal, header-only YAML helper. BUILD_INTERFACE only -- it is not + # installed and its symbols are not exported from the SIL Kit library. + target_link_libraries(I_SilKit + INTERFACE $ + ) +endif() + add_library(O_SilKit_CreateParticipantImpl OBJECT CreateParticipantImpl.hpp diff --git a/SilKit/source/config/CMakeLists.txt b/SilKit/source/config/CMakeLists.txt index 1a877a489..29f25c517 100644 --- a/SilKit/source/config/CMakeLists.txt +++ b/SilKit/source/config/CMakeLists.txt @@ -19,8 +19,8 @@ add_library(O_SilKit_Config OBJECT YamlWriter.hpp YamlWriter.cpp - YamlParserUtils.hpp - YamlParserUtils.cpp + CapabilitiesParser.hpp + CapabilitiesParser.cpp YamlValidator.hpp YamlValidator.cpp diff --git a/SilKit/source/config/CapabilitiesParser.cpp b/SilKit/source/config/CapabilitiesParser.cpp new file mode 100644 index 000000000..33ff1293c --- /dev/null +++ b/SilKit/source/config/CapabilitiesParser.cpp @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2026 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include "config/CapabilitiesParser.hpp" + +#include "silkit/participant/exception.hpp" + +#include "SilKitYaml/BasicYamlReader.hpp" +#include "SilKitYaml/YamlSerdes.hpp" + +namespace VSilKit { + +using ValueT = std::vector>; + +namespace { +struct CapabilityReader: SilKitYaml::BasicYamlReader +{ + using BasicYamlReader::BasicYamlReader; + void Read(ValueT& value) + { + if(!IsSequence()) + { + throw SilKitYaml::YamlError{"First element in Capabilities string is not a sequence"}; + } + + if(_node.has_children()) + { + for(auto&& i: _node.cchildren()) + { + auto&& parser = MakeImpl(i); + if(!parser.IsMap()) + { + throw SilKitYaml::YamlError{"Capabilities should be a sequence of map objects."}; + } + std::map element; + static_cast&>(parser).Read(element); + value.emplace_back(std::move(element)); + } + } + } +}; + +} // end namespace +auto ParseCapabilities(const std::string& input) -> std::vector> +{ + try { + return SilKitYaml::Deserialize(input); + } catch(const SilKitYaml::YamlError& ex) + { + throw SilKit::ConfigurationError{ex.what()}; + } +} + +} // namespace VSilKit diff --git a/SilKit/source/config/YamlParserUtils.hpp b/SilKit/source/config/CapabilitiesParser.hpp similarity index 60% rename from SilKit/source/config/YamlParserUtils.hpp rename to SilKit/source/config/CapabilitiesParser.hpp index 9adba1d4f..424eb9558 100644 --- a/SilKit/source/config/YamlParserUtils.hpp +++ b/SilKit/source/config/CapabilitiesParser.hpp @@ -8,17 +8,9 @@ #include #include -#include "silkit/participant/exception.hpp" - -#include "rapidyaml.hpp" - namespace VSilKit { // Utility for parsing key-value lists for protocol capabilities auto ParseCapabilities(const std::string& input) -> std::vector>; -auto MakeConfigurationError(ryml::Location location, const std::string_view message) -> SilKit::ConfigurationError; - -auto GetRapidyamlCallbacks() -> ryml::Callbacks; - } // namespace VSilKit diff --git a/SilKit/source/config/YamlParser.hpp b/SilKit/source/config/YamlParser.hpp index 7ccfee96c..b3db0a63d 100644 --- a/SilKit/source/config/YamlParser.hpp +++ b/SilKit/source/config/YamlParser.hpp @@ -5,9 +5,12 @@ #include +#include "silkit/participant/exception.hpp" + #include "config/YamlReader.hpp" #include "config/YamlWriter.hpp" -#include "config/YamlParserUtils.hpp" + +#include "SilKitYaml/YamlSerdes.hpp" #include "rapidyaml.hpp" @@ -17,67 +20,50 @@ namespace Config { ////////////////////////////////////////////////////////////////////// // Configuration Parsing +// +// Thin wrappers around the generic SilKitYaml serdes templates, defaulting to the +// SilKit concrete reader/writer for the in-tree configuration types. They form +// the boundary that translates SilKitYaml::YamlError into the public +// SilKit::ConfigurationError, preserving SIL Kit's exception contract. ////////////////////////////////////////////////////////////////////// template auto Deserialize(const std::string& input) -> T { - if (input.empty()) - { - return {}; - } - - const auto rapidyamlCallbacks = VSilKit::GetRapidyamlCallbacks(); - - ryml::ParserOptions options{}; - options.locations(true); - - ryml::EventHandlerTree eventHandler{}; - auto parser = ryml::Parser(&eventHandler, options); - parser.reserve_locations(100u); - auto&& cinput = ryml::to_csubstr(input); try { - auto tree = ryml::parse_in_arena(&parser, cinput); - - // Install the error-handling callbacks. This will nicely format errors and throw an exception. - tree.callbacks(rapidyamlCallbacks); - - // Extract a reference to the root node of the document tree. - auto root = tree.crootref(); - - R reader{parser, root}; - T result{}; - reader.Read(result); - return result; + return SilKitYaml::Deserialize(input); } catch (const std::exception& ex) { throw SilKit::ConfigurationError{ex.what()}; } - catch (...) - { - throw; - } } - template auto Serialize(const T& input) -> std::string { - ryml::Tree t; - W writer{t.rootref()}; - writer.Write(input); - return ryml::emitrs_yaml(t); + try + { + return SilKitYaml::Serialize(input); + } + catch (const std::exception& ex) + { + throw SilKit::ConfigurationError{ex.what()}; + } } template auto SerializeAsJson(const T& input) -> std::string { - ryml::Tree t; - W writer{t.rootref()}; - writer.Write(input); - return ryml::emitrs_json(t); + try + { + return SilKitYaml::SerializeAsJson(input); + } + catch (const std::exception& ex) + { + throw SilKit::ConfigurationError{ex.what()}; + } } } // namespace Config diff --git a/SilKit/source/config/YamlParserUtils.cpp b/SilKit/source/config/YamlParserUtils.cpp deleted file mode 100644 index 60a462622..000000000 --- a/SilKit/source/config/YamlParserUtils.cpp +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Vector Informatik GmbH -// -// SPDX-License-Identifier: MIT - -#include "config/YamlParserUtils.hpp" - -#include "silkit/participant/exception.hpp" - -#include - - -namespace { - -auto RapidyamlAllocate(size_t length, void* hint, void* userData) -> void*; -void RapidyamlFree(void* ptr, size_t length, void* userData); -void RapidyamlError(const char* message, size_t length, ryml::Location location, void* userData); - -} // namespace - - -namespace VSilKit { - -auto ParseCapabilities(const std::string& input) -> std::vector> -{ - std::vector> result; - auto&& cinput = ryml::to_csubstr(input); - auto t = ryml::parse_in_arena(cinput); - - auto root = t.crootref(); - if (!root.is_seq()) - { - throw SilKit::ConfigurationError{"First element in Capabilities string is not a sequence"}; - } - if (root.has_children()) - { - for (auto&& child : root.children()) - { - if (!child.is_map()) - { - throw SilKit::ConfigurationError{"Capabilities should be a sequence of map objects."}; - } - } - } - root >> result; - return result; -} - -auto MakeConfigurationError(ryml::Location location, const std::string_view message) -> SilKit::ConfigurationError -{ - std::ostringstream s; - - s << "error parsing configuration"; - if (location.name.empty()) - { - s << " string: "; - } - else - { - s << " file " << location.name << ": "; - } - - s << "line " << (location.line + 1) << " column " << location.col << ": " << message; - - return SilKit::ConfigurationError{s.str()}; -} - -auto GetRapidyamlCallbacks() -> ryml::Callbacks -{ - return ryml::Callbacks{nullptr, RapidyamlAllocate, RapidyamlFree, RapidyamlError}; -} - -} // namespace VSilKit - - -namespace { - -auto RapidyamlAllocate(const size_t length, void* /*hint*/, void* /*userData*/) -> void* -{ - return std::malloc(length); -} - -void RapidyamlFree(void* ptr, size_t /*length*/, void* /*userData*/) -{ - std::free(ptr); -} - -void RapidyamlError(const char* message, const size_t length, ryml::Location location, void* /*userData*/) -{ - const std::string_view rapidyamlMessage{message, length}; - throw VSilKit::MakeConfigurationError(location, message); -} - -} // namespace diff --git a/SilKit/source/config/YamlReader.cpp b/SilKit/source/config/YamlReader.cpp index d85639a11..ced45a07d 100644 --- a/SilKit/source/config/YamlReader.cpp +++ b/SilKit/source/config/YamlReader.cpp @@ -23,7 +23,7 @@ void YamlReader::Read(SilKit::Services::MatchingLabel::Kind& value) else if (numericValue == 1) value = SilKit::Services::MatchingLabel::Kind::Optional; else - throw MakeConfigurationError("MatchingLabel::Kind should be an integer of Mandatory(2)|Optional(1)."); + throw MakeError("MatchingLabel::Kind should be an integer of Mandatory(2)|Optional(1)."); } @@ -45,7 +45,7 @@ void YamlReader::Read(SilKit::Services::Logging::Level& obj) obj = SilKit::Services::Logging::Level::Off; else { - throw MakeConfigurationError("Unknown SilKit::Services::Logging::Level."); + throw MakeError("Unknown SilKit::Services::Logging::Level."); } } @@ -53,7 +53,7 @@ void YamlReader::Read(SilKit::Services::Logging::Topic& obj) { if (!IsScalar() ) { - throw MakeConfigurationError("Topic should be a string."); + throw MakeError("Topic should be a string."); } std::string value; @@ -63,7 +63,7 @@ void YamlReader::Read(SilKit::Services::Logging::Topic& obj) if (topic == SilKit::Services::Logging::Topic::Invalid) { - throw MakeConfigurationError("Unknown SilKit::Services::Logging::Topic"); + throw MakeError("Unknown SilKit::Services::Logging::Topic"); } obj = topic; } @@ -145,7 +145,7 @@ void YamlReader::Read(SilKit::Services::Flexray::FlexrayChannel& obj) obj = SilKit::Services::Flexray::FlexrayChannel::None; else { - throw MakeConfigurationError("Unknown Services::Flexray::FlexrayChannel"); + throw MakeError("Unknown Services::Flexray::FlexrayChannel"); } } @@ -159,7 +159,7 @@ void YamlReader::Read(SilKit::Services::Flexray::FlexrayClockPeriod& obj) obj = SilKit::Services::Flexray::FlexrayClockPeriod::T50NS; else { - throw MakeConfigurationError("Unknown Services::Flexray::FlexrayClockPeriod"); + throw MakeError("Unknown Services::Flexray::FlexrayClockPeriod"); } } @@ -171,7 +171,7 @@ void YamlReader::Read(SilKit::Services::Flexray::FlexrayTransmissionMode& obj) obj = SilKit::Services::Flexray::FlexrayTransmissionMode::SingleShot; else { - throw MakeConfigurationError("Unknown Services::Flexray::FlexrayTransmissionMode."); + throw MakeError("Unknown Services::Flexray::FlexrayTransmissionMode."); } } @@ -191,7 +191,7 @@ void YamlReader::Read(SilKit::Config::Sink::Type& obj) } else { - throw MakeConfigurationError("Unknown Sink::Type"); + throw MakeError("Unknown Sink::Type"); } } @@ -207,7 +207,7 @@ void YamlReader::Read(SilKit::Config::Sink::Format& obj) } else { - throw MakeConfigurationError("Unknown Sink::Format:"); + throw MakeError("Unknown Sink::Format:"); } } @@ -228,7 +228,7 @@ void YamlReader::Read(SilKit::Config::Sink& obj) { if (!HasKey("LogName")) { - throw MakeConfigurationError("Sink of type Sink::Type::File requires a LogName"); + throw MakeError("Sink of type Sink::Type::File requires a LogName"); } ReadKeyValue(obj.logName, "LogName"); @@ -258,7 +258,7 @@ void YamlReader::Read(SilKit::Config::MetricsSink::Type& obj) } else { - throw MakeConfigurationError("Unknown MetricsSink::Type"); + throw MakeError("Unknown MetricsSink::Type"); } } @@ -278,7 +278,7 @@ void YamlReader::Read(SilKit::Config::MdfChannel& obj) { if (!IsMap()) { - throw MakeConfigurationError("MdfChannel should be a Map"); + throw MakeError("MdfChannel should be a Map"); } OptionalRead(obj.channelName, "ChannelName"); OptionalRead(obj.channelPath, "ChannelPath"); @@ -307,7 +307,7 @@ void YamlReader::Read(SilKit::Config::Replay::Direction& obj) obj = SilKit::Config::Replay::Direction::Both; else { - throw MakeConfigurationError("Unknown Replay::Direction"); + throw MakeError("Unknown Replay::Direction"); } } void YamlReader::Read(SilKit::Config::CanController& obj) @@ -341,7 +341,7 @@ void YamlReader::Read(SilKit::Config::Label::Kind& obj) obj = SilKit::Config::Label::Kind::Optional; else { - throw MakeConfigurationError("Unknown Label::Kind"); + throw MakeError("Unknown Label::Kind"); } } @@ -415,7 +415,7 @@ void YamlReader::Read(SilKit::Config::TraceSink::Type& obj) obj = SilKit::Config::TraceSink::Type::PcapPipe; else { - throw MakeConfigurationError("Unknown TraceSink::Type"); + throw MakeError("Unknown TraceSink::Type"); } } void YamlReader::Read(SilKit::Config::TraceSource& obj) @@ -435,7 +435,7 @@ void YamlReader::Read(SilKit::Config::TraceSource::Type& obj) obj = SilKit::Config::TraceSource::Type::PcapFile; else { - throw MakeConfigurationError("Unknown TraceSource::Type:"); + throw MakeError("Unknown TraceSource::Type:"); } } @@ -475,7 +475,7 @@ void YamlReader::Read(SilKit::Config::Aggregation& obj) obj = SilKit::Config::Aggregation::Auto; else { - throw MakeConfigurationError("Unknown Aggregation"); + throw MakeError("Unknown Aggregation"); } } diff --git a/SilKit/source/config/YamlReader.hpp b/SilKit/source/config/YamlReader.hpp index 415fb23b7..b5ce2b5a6 100644 --- a/SilKit/source/config/YamlReader.hpp +++ b/SilKit/source/config/YamlReader.hpp @@ -13,257 +13,13 @@ #include "rapidyaml.hpp" +#include "SilKitYaml/BasicYamlReader.hpp" + #include "config/ParticipantConfiguration.hpp" -#include "config/YamlParserUtils.hpp" namespace VSilKit { -template -class BasicYamlReader -{ -protected: - ryml::Parser& _parser; - ryml::ConstNodeRef _node; - -public: - BasicYamlReader(ryml::Parser& parser_, ryml::ConstNodeRef node_) - : _parser(parser_) - , _node(node_) - { - } - -public: - template , bool> = true> - void OptionalRead(T& val, const std::string& name) - { - auto&& child = GetChildSafe(name); - if (child.IsValid()) - { - child.Read(val); - } - } - - void OptionalRead(bool& val, const std::string& name) - { - auto&& child = GetChildSafe(name); - if (child.IsValid()) - { - child.Read(val); - } - } - - template - void OptionalRead(std::optional& val, const std::string& name) - { - auto&& child = GetChildSafe(name); - if (child.IsValid()) - { - T tmp{}; - child.Read(tmp); - val = std::move(tmp); // needs a proper setter to set "has_value" - } - } - - - template , bool> = true> - void OptionalRead(T& val, const std::string& name) - { - auto tmp = ryml::fmt::overflow_checked(val); - (void)_node.get_if(ryml::to_csubstr(name), &tmp); - } - - template - void OptionalRead_deprecated_alternative(ConfigT& value, const std::string& fieldName, - std::initializer_list deprecatedFieldNames) - { - if (!(IsMap() || IsSequence())) - { - return; - } - - auto hasChild = [this](auto&& name) { - auto&& c = GetChildSafe(name); - return c.IsValid(); - }; - - std::vector presentDeprecatedFieldNames; - std::copy_if(deprecatedFieldNames.begin(), deprecatedFieldNames.end(), - std::back_inserter(presentDeprecatedFieldNames), hasChild); - - if (HasKey(fieldName) && presentDeprecatedFieldNames.size() >= 1) - { - std::stringstream ss; - ss << "The key \"" << fieldName << "\" and the deprecated alternatives"; - for (const auto& deprecatedFieldName : presentDeprecatedFieldNames) - { - ss << " \"" << deprecatedFieldName << "\""; - } - ss << " are present."; - throw SilKit::ConfigurationError{ss.str()}; - } - - if (presentDeprecatedFieldNames.size() >= 2) - { - std::stringstream ss; - ss << "The deprecated keys"; - for (const auto& deprecatedFieldName : presentDeprecatedFieldNames) - { - ss << " \"" << deprecatedFieldName << "\""; - } - ss << " are present."; - throw SilKit::ConfigurationError{ss.str()}; - } - - OptionalRead(value, fieldName); - for (const auto& deprecatedFieldName : deprecatedFieldNames) - { - OptionalRead(value, deprecatedFieldName); - } - } - -public: - template - void ReadKeyValue(T& value, const std::string& name) - { - auto&& child = GetChildSafe(name); - - if (!child.IsValid()) - { - std::ostringstream s; - s << "missing key: " << name; - throw MakeConfigurationError(s.str()); - } - - child.Read(value); - } - -public: - template - void Read(T& value) - { - _node >> value; - } - - template - void Read(std::chrono::duration& obj) - { - Rep value{}; - auto tmp = ryml::fmt::overflow_checked(value); - Read(tmp); - obj = std::chrono::milliseconds{value}; - } - - template - void Read(std::vector& val) - { - if (!IsSequence()) - { - throw MakeConfigurationError("Expected a sequence."); - } - - for (auto&& i : _node.cchildren()) - { - T element{}; - MakeImpl(i).Read(element); - val.emplace_back(std::move(element)); - } - } - -protected: - bool IsValid() const - { - return !_node.invalid(); - } - - bool IsMap() const - { - return _node.is_map(); - } - - bool IsScalar() const - { - return _node.is_val() || _node.is_keyval(); - } - - bool IsSequence() const - { - return _node.is_seq(); - } - - bool IsExistingString(const char* str) const - { - if (!_node.is_val()) - { - return false; - } - return _node.val() == ryml::to_csubstr(str); - } - - bool IsEmpty() const - { - return _node.empty(); - } - - bool IsString(const char* string) const - { - return IsScalar() && (_node.val() == ryml::to_csubstr(string)); - } - - bool HasKey(const std::string& name) const - { - return HasKey(_node, name); - } - - static bool HasKey(ryml::ConstNodeRef node, const std::string& name) - { - return node.is_map() && !node.find_child(ryml::to_csubstr(name)).invalid(); - } - - auto MakeConfigurationError(const std::string_view message) const -> SilKit::ConfigurationError - { - const auto location = _parser.location(_node); - return VSilKit::MakeConfigurationError(location, message); - } - - auto GetChildSafe(const std::string& name) const -> Impl - { - if (HasKey(name)) - { - return MakeImpl(_node.find_child(ryml::to_csubstr(name))); - } - - if (IsSequence()) - { - for (const auto& child : _node.cchildren()) - { - if (child.is_container() && HasKey(child, name)) - { - return MakeImpl(child.find_child(ryml::to_csubstr(name))); - } - } - } - - return MakeImpl({}); - } - - auto MakeImpl(ryml::ConstNodeRef node_) const -> Impl - { - return Impl{_parser, node_}; - } - -private: - auto AsImpl() -> Impl& - { - return static_cast(*this); - } - - auto AsImpl() const -> const Impl& - { - return static_cast(*this); - } -}; - -struct YamlReader : BasicYamlReader +struct YamlReader : SilKitYaml::BasicYamlReader { using BasicYamlReader::BasicYamlReader; using BasicYamlReader::Read; diff --git a/SilKit/source/config/YamlWriter.hpp b/SilKit/source/config/YamlWriter.hpp index 0fb05c8ff..a3786d6e7 100644 --- a/SilKit/source/config/YamlWriter.hpp +++ b/SilKit/source/config/YamlWriter.hpp @@ -12,120 +12,13 @@ #include "rapidyaml.hpp" +#include "SilKitYaml/BasicYamlWriter.hpp" + #include "config/ParticipantConfiguration.hpp" namespace VSilKit { -template -struct BasicYamlWriter -{ - ryml::NodeRef node; - -public: - BasicYamlWriter(ryml::NodeRef node_) - : node(node_) - { - } - -public: - template - void OptionalWrite(const std::optional& val, const std::string& name) - { - if (val.has_value()) - { - WriteKeyValue(name, val.value()); - } - } - - template - void OptionalWrite(const std::vector& val, const std::string& name) - { - if (!val.empty()) - { - WriteKeyValue(name, val); - } - } - - void OptionalWrite(const std::string& val, const std::string& name) - { - if (!val.empty()) - { - WriteKeyValue(name, val); - } - } - - template - void NonDefaultWrite(const T& val, const std::string& name, const T& defaultValue) - { - if (!(val == defaultValue)) - { - WriteKeyValue(name, val); - } - } - - template - void WriteKeyValue(const std::string& name, const T& val) - { - if (!node.is_map()) - { - throw SilKit::ConfigurationError("Parse error: trying to access child of something not a map"); - } - - auto writer = MakeImpl(node.append_child() << ryml::key(name)); - writer.Write(val); - } - - template - void Write(const T& val) - { - node << val; - } - - template - void Write(const std::vector& val) - { - node |= ryml::SEQ; - for (auto&& el : val) - { - auto writer = MakeImpl(node.append_child()); - writer.Write(el); - } - } - -protected: - void MakeMap() - { - node |= ryml::MAP; - } - - auto MakeConfigurationError(const char* message) const -> SilKit::ConfigurationError - { - std::ostringstream s; - - s << "error writing configuration: " << message; - - return SilKit::ConfigurationError{s.str()}; - } - -protected: - auto MakeImpl(ryml::NodeRef node_) const -> Impl - { - return Impl{node_}; - } - -private: - auto AsImpl() -> Impl& - { - return static_cast(*this); - } - - auto AsImpl() const -> const Impl& - { - return static_cast(*this); - } -}; - -struct YamlWriter : BasicYamlWriter +struct YamlWriter : SilKitYaml::BasicYamlWriter { using BasicYamlWriter::BasicYamlWriter; using BasicYamlWriter::OptionalWrite; diff --git a/SilKit/source/core/vasio/VAsioCapabilities.cpp b/SilKit/source/core/vasio/VAsioCapabilities.cpp index 39906da0e..2451817e2 100644 --- a/SilKit/source/core/vasio/VAsioCapabilities.cpp +++ b/SilKit/source/core/vasio/VAsioCapabilities.cpp @@ -6,7 +6,7 @@ #include "silkit/participant/exception.hpp" -#include "config/YamlParser.hpp" +#include "config/CapabilitiesParser.hpp" #include #include diff --git a/SilKitYaml/CMakeLists.txt b/SilKitYaml/CMakeLists.txt new file mode 100644 index 000000000..7dc1a221b --- /dev/null +++ b/SilKitYaml/CMakeLists.txt @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: 2026 Vector Informatik GmbH +# +# SPDX-License-Identifier: MIT + +# SilKitYaml -- self-contained, header-only YAML (de)serialization on top of +# rapidyaml. Used internally by SIL Kit and vendorable into other projects via +# subrepo/submodule (add_subdirectory). It is NOT installed or exported, and its +# symbols are not leaked into the SIL Kit shared library. +# +# rapidyaml dependency: if the host project already provides a `rapidyaml` target +# (as SIL Kit does in-tree), it is used as-is. Otherwise SilKitYaml provisions the +# vendored rapidyaml itself, locating it via SILKIT_TOP_DIR (the root of the SIL +# Kit tree). Both SILKIT_TOP_DIR and the concrete SILKITYAML_RAPIDYAML_DIR are +# overridable so hosts with a different layout, or their own rapidyaml, still work. + +# Root of the SIL Kit tree. Honor an outer definition (set by the top-level +# CMakeLists in-tree); otherwise self-locate: SilKitYaml lives at /SilKitYaml. +if(NOT DEFINED SILKIT_TOP_DIR) + get_filename_component(SILKIT_TOP_DIR "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) +endif() + +set(SILKITYAML_RAPIDYAML_DIR "${SILKIT_TOP_DIR}/ThirdParty/rapidyaml" + CACHE PATH "Location of the rapidyaml sources consumed by SilKitYaml") + +if(NOT TARGET rapidyaml) + if(EXISTS "${SILKITYAML_RAPIDYAML_DIR}/CMakeLists.txt") + add_subdirectory("${SILKITYAML_RAPIDYAML_DIR}" + "${CMAKE_CURRENT_BINARY_DIR}/_rapidyaml") + # The rapidyaml folder only defines `rapidyaml-static`; provide the alias. + if(NOT TARGET rapidyaml) + add_library(rapidyaml ALIAS rapidyaml-static) + endif() + else() + message(FATAL_ERROR + "SilKitYaml: no 'rapidyaml' target and none found at " + "'${SILKITYAML_RAPIDYAML_DIR}'. Provide a 'rapidyaml' target, or set " + "SILKIT_TOP_DIR / SILKITYAML_RAPIDYAML_DIR to the rapidyaml location.") + endif() +endif() + +add_library(SilKitYaml INTERFACE) +add_library(SilKitYaml::SilKitYaml ALIAS SilKitYaml) + +target_include_directories(SilKitYaml + INTERFACE + $ +) + +# rapidyaml carries its include dirs and RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS=1. +target_link_libraries(SilKitYaml INTERFACE rapidyaml) +target_compile_features(SilKitYaml INTERFACE cxx_std_17) + +# Convenience project for IDEs to surface the headers. +file(GLOB_RECURSE SILKIT_YAML_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/*.hpp) +add_custom_target(SilKitYamlApi SOURCES ${SILKIT_YAML_HEADERS}) diff --git a/SilKitYaml/README.md b/SilKitYaml/README.md new file mode 100644 index 000000000..a7366103e --- /dev/null +++ b/SilKitYaml/README.md @@ -0,0 +1,82 @@ + + +# SilKitYaml — self-contained YAML (de)serialization + +`SilKitYaml` is a small, **header-only** C++17 library wrapping +[rapidyaml](https://github.com/biojppm/rapidyaml) with CRTP-based reader/writer +base classes and `Deserialize`/`Serialize` helper functions. It lets you parse +and emit your own YAML/JSON formats against your own schemata. + +It is used **internally by SIL Kit** and is intentionally **self-contained**. + +## Properties + +- Namespace `SilKitYaml`. Errors are reported as `SilKitYaml::YamlError`. +- Depends only on `rapidyaml`. It does **not** depend on SIL Kit. +- **Not installed and not exported** by SIL Kit; it is an internal interface + library whose symbols do not leak into the SIL Kit shared library. + +## CMake usage + +```cmake +add_subdirectory(SilKitYaml) +target_link_libraries(my_target PRIVATE SilKitYaml::SilKitYaml) +``` + +The `rapidyaml` dependency is resolved as follows: + +- If the host already defines a `rapidyaml` target (as SIL Kit does in-tree), it + is used as-is. +- Otherwise SilKitYaml provisions the vendored rapidyaml itself, locating it at + `${SILKIT_TOP_DIR}/ThirdParty/rapidyaml`. `SILKIT_TOP_DIR` defaults to the SIL + Kit tree root (self-located as the parent of this folder). + +To override for a different layout, set `SILKIT_TOP_DIR` or the more specific +`SILKITYAML_RAPIDYAML_DIR`, or simply provide your own `rapidyaml` target before +`add_subdirectory(SilKitYaml)`. + +## Code + +```cpp +#include "SilKitYaml/BasicYamlReader.hpp" +#include "SilKitYaml/BasicYamlWriter.hpp" +#include "SilKitYaml/YamlSerdes.hpp" + +struct MyConfig +{ + std::string name; + int count{}; +}; + +struct MyReader : SilKitYaml::BasicYamlReader +{ + using BasicYamlReader::BasicYamlReader; + using BasicYamlReader::Read; + + void Read(MyConfig& c) + { + ReadKeyValue(c.name, "Name"); + OptionalRead(c.count, "Count"); + } +}; + +struct MyWriter : SilKitYaml::BasicYamlWriter +{ + using BasicYamlWriter::BasicYamlWriter; + using BasicYamlWriter::Write; + + void Write(const MyConfig& c) + { + MakeMap(); + WriteKeyValue("Name", c.name); + WriteKeyValue("Count", c.count); + } +}; + +auto cfg = SilKitYaml::Deserialize("Name: foo\nCount: 7\n"); +auto yaml = SilKitYaml::Serialize(cfg); +``` diff --git a/SilKitYaml/include/SilKitYaml/BasicYamlReader.hpp b/SilKitYaml/include/SilKitYaml/BasicYamlReader.hpp new file mode 100644 index 000000000..82b7fd557 --- /dev/null +++ b/SilKitYaml/include/SilKitYaml/BasicYamlReader.hpp @@ -0,0 +1,271 @@ +// SPDX-FileCopyrightText: 2025 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#pragma once + +// SilKitYaml: self-contained, header-only YAML deserialization. See README.md. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rapidyaml.hpp" + +#include "SilKitYaml/YamlError.hpp" +#include "SilKitYaml/YamlParserUtils.hpp" + +namespace SilKitYaml { + +template +class BasicYamlReader +{ +protected: + ryml::Parser& _parser; + ryml::ConstNodeRef _node; + +public: + BasicYamlReader(ryml::Parser& parser_, ryml::ConstNodeRef node_) + : _parser(parser_) + , _node(node_) + { + } + +public: + template , bool> = true> + void OptionalRead(T& val, const std::string& name) + { + auto&& child = GetChildSafe(name); + if (child.IsValid()) + { + child.Read(val); + } + } + + void OptionalRead(bool& val, const std::string& name) + { + auto&& child = GetChildSafe(name); + if (child.IsValid()) + { + child.Read(val); + } + } + + template + void OptionalRead(std::optional& val, const std::string& name) + { + auto&& child = GetChildSafe(name); + if (child.IsValid()) + { + T tmp{}; + child.Read(tmp); + val = std::move(tmp); // needs a proper setter to set "has_value" + } + } + + + template , bool> = true> + void OptionalRead(T& val, const std::string& name) + { + auto tmp = ryml::fmt::overflow_checked(val); + (void)_node.get_if(ryml::to_csubstr(name), &tmp); + } + + template + void OptionalRead_deprecated_alternative(ConfigT& value, const std::string& fieldName, + std::initializer_list deprecatedFieldNames) + { + if (!(IsMap() || IsSequence())) + { + return; + } + + auto hasChild = [this](auto&& name) { + auto&& c = GetChildSafe(name); + return c.IsValid(); + }; + + std::vector presentDeprecatedFieldNames; + std::copy_if(deprecatedFieldNames.begin(), deprecatedFieldNames.end(), + std::back_inserter(presentDeprecatedFieldNames), hasChild); + + if (HasKey(fieldName) && presentDeprecatedFieldNames.size() >= 1) + { + std::stringstream ss; + ss << "The key \"" << fieldName << "\" and the deprecated alternatives"; + for (const auto& deprecatedFieldName : presentDeprecatedFieldNames) + { + ss << " \"" << deprecatedFieldName << "\""; + } + ss << " are present."; + throw YamlError{ss.str()}; + } + + if (presentDeprecatedFieldNames.size() >= 2) + { + std::stringstream ss; + ss << "The deprecated keys"; + for (const auto& deprecatedFieldName : presentDeprecatedFieldNames) + { + ss << " \"" << deprecatedFieldName << "\""; + } + ss << " are present."; + throw YamlError{ss.str()}; + } + + OptionalRead(value, fieldName); + for (const auto& deprecatedFieldName : deprecatedFieldNames) + { + OptionalRead(value, deprecatedFieldName); + } + } + +public: + template + void ReadKeyValue(T& value, const std::string& name) + { + auto&& child = GetChildSafe(name); + + if (!child.IsValid()) + { + std::ostringstream s; + s << "missing key: " << name; + throw MakeError(s.str()); + } + + child.Read(value); + } + +public: + template + void Read(T& value) + { + _node >> value; + } + + template + void Read(std::chrono::duration& obj) + { + Rep value{}; + auto tmp = ryml::fmt::overflow_checked(value); + Read(tmp); + obj = std::chrono::milliseconds{value}; + } + + template + void Read(std::vector& val) + { + if (!IsSequence()) + { + throw MakeError("Expected a sequence."); + } + + for (auto&& i : _node.cchildren()) + { + T element{}; + MakeImpl(i).Read(element); + val.emplace_back(std::move(element)); + } + } + +protected: + bool IsValid() const + { + return !_node.invalid(); + } + + bool IsMap() const + { + return _node.is_map(); + } + + bool IsScalar() const + { + return _node.is_val() || _node.is_keyval(); + } + + bool IsSequence() const + { + return _node.is_seq(); + } + + bool IsExistingString(const char* str) const + { + if (!_node.is_val()) + { + return false; + } + return _node.val() == ryml::to_csubstr(str); + } + + bool IsEmpty() const + { + return _node.empty(); + } + + bool IsString(const char* string) const + { + return IsScalar() && (_node.val() == ryml::to_csubstr(string)); + } + + bool HasKey(const std::string& name) const + { + return HasKey(_node, name); + } + + static bool HasKey(ryml::ConstNodeRef node, const std::string& name) + { + return node.is_map() && !node.find_child(ryml::to_csubstr(name)).invalid(); + } + + auto MakeError(const std::string_view message) const -> YamlError + { + const auto location = _parser.location(_node); + return SilKitYaml::MakeError(location, message); + } + + auto GetChildSafe(const std::string& name) const -> Impl + { + if (HasKey(name)) + { + return MakeImpl(_node.find_child(ryml::to_csubstr(name))); + } + + if (IsSequence()) + { + for (const auto& child : _node.cchildren()) + { + if (child.is_container() && HasKey(child, name)) + { + return MakeImpl(child.find_child(ryml::to_csubstr(name))); + } + } + } + + return MakeImpl({}); + } + + auto MakeImpl(ryml::ConstNodeRef node_) const -> Impl + { + return Impl{_parser, node_}; + } + +private: + auto AsImpl() -> Impl& + { + return static_cast(*this); + } + + auto AsImpl() const -> const Impl& + { + return static_cast(*this); + } +}; + +} // namespace SilKitYaml diff --git a/SilKitYaml/include/SilKitYaml/BasicYamlWriter.hpp b/SilKitYaml/include/SilKitYaml/BasicYamlWriter.hpp new file mode 100644 index 000000000..1bddaa43e --- /dev/null +++ b/SilKitYaml/include/SilKitYaml/BasicYamlWriter.hpp @@ -0,0 +1,130 @@ +// SPDX-FileCopyrightText: 2025 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#pragma once + +// SilKitYaml: self-contained, header-only YAML serialization. See README.md. + +#include +#include +#include +#include +#include + +#include "rapidyaml.hpp" + +#include "SilKitYaml/YamlError.hpp" + +namespace SilKitYaml { + +template +struct BasicYamlWriter +{ + ryml::NodeRef node; + +public: + BasicYamlWriter(ryml::NodeRef node_) + : node(node_) + { + } + +public: + template + void OptionalWrite(const std::optional& val, const std::string& name) + { + if (val.has_value()) + { + WriteKeyValue(name, val.value()); + } + } + + template + void OptionalWrite(const std::vector& val, const std::string& name) + { + if (!val.empty()) + { + WriteKeyValue(name, val); + } + } + + void OptionalWrite(const std::string& val, const std::string& name) + { + if (!val.empty()) + { + WriteKeyValue(name, val); + } + } + + template + void NonDefaultWrite(const T& val, const std::string& name, const T& defaultValue) + { + if (!(val == defaultValue)) + { + WriteKeyValue(name, val); + } + } + + template + void WriteKeyValue(const std::string& name, const T& val) + { + if (!node.is_map()) + { + throw YamlError("Parse error: trying to access child of something not a map"); + } + + auto writer = MakeImpl(node.append_child() << ryml::key(name)); + writer.Write(val); + } + + template + void Write(const T& val) + { + node << val; + } + + template + void Write(const std::vector& val) + { + node |= ryml::SEQ; + for (auto&& el : val) + { + auto writer = MakeImpl(node.append_child()); + writer.Write(el); + } + } + +protected: + void MakeMap() + { + node |= ryml::MAP; + } + + auto MakeError(const char* message) const -> YamlError + { + std::ostringstream s; + + s << "error writing yaml: " << message; + + return YamlError{s.str()}; + } + +protected: + auto MakeImpl(ryml::NodeRef node_) const -> Impl + { + return Impl{node_}; + } + +private: + auto AsImpl() -> Impl& + { + return static_cast(*this); + } + + auto AsImpl() const -> const Impl& + { + return static_cast(*this); + } +}; + +} // namespace SilKitYaml diff --git a/SilKitYaml/include/SilKitYaml/YamlError.hpp b/SilKitYaml/include/SilKitYaml/YamlError.hpp new file mode 100644 index 000000000..6e8d99f6c --- /dev/null +++ b/SilKitYaml/include/SilKitYaml/YamlError.hpp @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2026 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include + +namespace SilKitYaml { + +//! \brief Exception thrown by SilKitYaml on YAML parse/serialize errors. +//! +//! SilKitYaml is self-contained and depends on nothing from SIL Kit; it reports +//! all errors via this type. Hosting projects that wrap SilKitYaml (such as SIL +//! Kit itself) may translate this into their own exception type at their boundary. +class YamlError : public std::exception +{ + std::string _what; + +public: + explicit YamlError(std::string message) + : _what{std::move(message)} + { + } + + explicit YamlError(const char* message) + : _what{message} + { + } + + const char* what() const noexcept override + { + return _what.c_str(); + } +}; + +} // namespace SilKitYaml diff --git a/SilKitYaml/include/SilKitYaml/YamlParserUtils.hpp b/SilKitYaml/include/SilKitYaml/YamlParserUtils.hpp new file mode 100644 index 000000000..4100a3589 --- /dev/null +++ b/SilKitYaml/include/SilKitYaml/YamlParserUtils.hpp @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2026 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#pragma once + +// SilKitYaml: a self-contained, header-only YAML (de)serialization layer built on +// top of rapidyaml. It is consumed internally by SIL Kit and may be vendored into +// other projects via subrepo/submodule (add_subdirectory). It depends only on a +// `rapidyaml` target. See SilKitYaml/README.md. + +#include +#include +#include +#include + +#include "rapidyaml.hpp" + +#include "SilKitYaml/YamlError.hpp" + +namespace SilKitYaml { + +// Format a rapidyaml parse location and message into a SilKitYaml::YamlError. +inline auto MakeError(ryml::Location location, const std::string_view message) -> YamlError +{ + std::ostringstream s; + + s << "error parsing yaml"; + if (location.name.empty()) + { + s << " string: "; + } + else + { + s << " file " << location.name << ": "; + } + + s << "line " << (location.line + 1) << " column " << location.col << ": " << message; + + return YamlError{s.str()}; +} + +namespace detail { + +inline auto RapidyamlAllocate(const size_t length, void* /*hint*/, void* /*userData*/) -> void* +{ + return std::malloc(length); +} + +inline void RapidyamlFree(void* ptr, size_t /*length*/, void* /*userData*/) +{ + std::free(ptr); +} + +inline void RapidyamlError(const char* message, const size_t length, ryml::Location location, void* /*userData*/) +{ + const std::string_view rapidyamlMessage{message, length}; + throw SilKitYaml::MakeError(location, rapidyamlMessage); +} + +} // namespace detail + +// Error-handling callbacks for a ryml::Tree/Parser. The error callback nicely +// formats the location and throws SilKitYaml::YamlError. Requires the compile +// definition RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS=1 (provided by the rapidyaml target). +inline auto GetRapidyamlCallbacks() -> ryml::Callbacks +{ + return ryml::Callbacks{nullptr, detail::RapidyamlAllocate, detail::RapidyamlFree, detail::RapidyamlError}; +} + +} // namespace SilKitYaml diff --git a/SilKitYaml/include/SilKitYaml/YamlSerdes.hpp b/SilKitYaml/include/SilKitYaml/YamlSerdes.hpp new file mode 100644 index 000000000..2edf837e9 --- /dev/null +++ b/SilKitYaml/include/SilKitYaml/YamlSerdes.hpp @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2025 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#pragma once + +// SilKitYaml: self-contained, header-only YAML/JSON (de)serialization. See README.md. + +#include + +#include "rapidyaml.hpp" + +#include "SilKitYaml/YamlError.hpp" +#include "SilKitYaml/YamlParserUtils.hpp" + +namespace SilKitYaml { + +////////////////////////////////////////////////////////////////////// +// Generic YAML/JSON (de)serialization +// +// R must derive from SilKitYaml::BasicYamlReader and provide a +// void Read(T&) overload for the target type T. +// W must derive from SilKitYaml::BasicYamlWriter and provide a +// void Write(const T&) overload for the source type T. +// +// All errors are reported as SilKitYaml::YamlError. +////////////////////////////////////////////////////////////////////// + +template +auto Deserialize(const std::string& input) -> T +{ + if (input.empty()) + { + return {}; + } + + const auto rapidyamlCallbacks = SilKitYaml::GetRapidyamlCallbacks(); + + ryml::ParserOptions options{}; + options.locations(true); + + ryml::EventHandlerTree eventHandler{}; + auto parser = ryml::Parser(&eventHandler, options); + parser.reserve_locations(100u); + auto&& cinput = ryml::to_csubstr(input); + try + { + auto tree = ryml::parse_in_arena(&parser, cinput); + + // Install the error-handling callbacks. This will nicely format errors and throw an exception. + tree.callbacks(rapidyamlCallbacks); + + // Extract a reference to the root node of the document tree. + auto root = tree.crootref(); + + R reader{parser, root}; + T result{}; + reader.Read(result); + return result; + } + catch (const std::exception& ex) + { + throw YamlError{ex.what()}; + } + catch (...) + { + throw; + } +} + +template +auto Serialize(const T& input) -> std::string +{ + ryml::Tree t; + W writer{t.rootref()}; + writer.Write(input); + return ryml::emitrs_yaml(t); +} + +template +auto SerializeAsJson(const T& input) -> std::string +{ + ryml::Tree t; + W writer{t.rootref()}; + writer.Write(input); + return ryml::emitrs_json(t); +} + +} // namespace SilKitYaml