From 0dbe1d17f595fe453d8e0e9eaca016e819bd2aee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20B=C3=B6rschig?= Date: Thu, 23 Jul 2026 10:05:25 +0200 Subject: [PATCH 1/4] yaml: be more robust when parsing objects vs sequences MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marius Börschig --- SilKit/source/config/Test_YamlParser.cpp | 123 +++++++++++++++++++++++ SilKit/source/config/YamlReader.hpp | 21 +++- 2 files changed, 142 insertions(+), 2 deletions(-) diff --git a/SilKit/source/config/Test_YamlParser.cpp b/SilKit/source/config/Test_YamlParser.cpp index 0b3e0da7c..681d68557 100644 --- a/SilKit/source/config/Test_YamlParser.cpp +++ b/SilKit/source/config/Test_YamlParser.cpp @@ -641,4 +641,127 @@ TEST_F(Test_YamlParser, yaml_throw_on_missing_keyword) { SilKit::ConfigurationError); } +TEST_F(Test_YamlParser, yaml_throw_on_healthcheck_as_sequence) +{ + // HealthCheck is an object with SoftResponseTimeout/HardResponseTimeout members. + // Here it is mistyped as a sequence (a list with a single element). The parser + // must reject the type mismatch instead of silently descending into the list and + // picking up the value from the contained map. + auto healthCheckAsSequence = R"( +HealthCheck: + - SoftResponseTimeout: 1 +)"; + + EXPECT_THROW( + { + auto cfg = Deserialize(healthCheckAsSequence); + }, + SilKit::ConfigurationError); +} + +// A YAML sequence where a map/object is expected is a type mismatch. The generalized +// check lives in the parser (GetChildSafe), so it applies to every object, not just +// HealthCheck. The following cases exercise the edges of that generalization. + +TEST_F(Test_YamlParser, yaml_throw_on_object_as_sequence) +{ + // Each of these mistypes a mapping as a single-element sequence at a different + // nesting level (top-level object, deeply nested object, object inside a list + // element). All must be rejected. + const std::initializer_list mistypedConfigurations = { + // top-level object as sequence + R"( +HealthCheck: + - SoftResponseTimeout: 1 +)", + R"( +Logging: + - FlushLevel: Critical +)", + R"( +Middleware: + - RegistryUri: silkit://localhost:8500 +)", + R"( +Experimental: + - Metrics: + CollectFromRemote: true +)", + // nested object (a controller's Replay) as sequence + R"( +CanControllers: +- Name: CAN1 + Replay: + - UseTraceSource: Source1 +)", + // object nested inside Experimental as sequence + R"( +Experimental: + Metrics: + - CollectFromRemote: true +)", + }; + + for (const auto configuration : mistypedConfigurations) + { + EXPECT_THROW( + { + auto&& cfg = Deserialize(configuration); + (void)cfg; + }, + SilKit::ConfigurationError); + } +} + +TEST_F(Test_YamlParser, yaml_healthcheck_as_map_is_accepted) +{ + // Regression guard: the correctly-typed object must still parse. + auto config = Deserialize(R"( +HealthCheck: + SoftResponseTimeout: 1 + HardResponseTimeout: 2 +)"); + + ASSERT_TRUE(config.healthCheck.softResponseTimeout.has_value()); + ASSERT_TRUE(config.healthCheck.hardResponseTimeout.has_value()); + EXPECT_EQ(config.healthCheck.softResponseTimeout.value(), 1ms); + EXPECT_EQ(config.healthCheck.hardResponseTimeout.value(), 2ms); +} + +TEST_F(Test_YamlParser, yaml_empty_object_keeps_defaults) +{ + // An object left empty (null node) is not a type mismatch: it keeps its defaults + // and must not throw. This guards the generalized check against over-rejecting. + ParticipantConfiguration defaults{}; + + auto config = Deserialize(R"( +HealthCheck: +)"); + + EXPECT_EQ(config.healthCheck.softResponseTimeout, defaults.healthCheck.softResponseTimeout); + EXPECT_EQ(config.healthCheck.hardResponseTimeout, defaults.healthCheck.hardResponseTimeout); +} + +TEST_F(Test_YamlParser, yaml_sequence_typed_fields_still_parse) +{ + // Fields that are genuinely sequences (lists of objects) must keep working after + // the object-vs-sequence check was tightened. + auto config = Deserialize(R"( +CanControllers: +- Name: CAN1 + Network: CAN2 +- Name: CAN2 +LinControllers: +- Name: LIN1 +)"); + + ASSERT_EQ(config.canControllers.size(), 2u); + EXPECT_EQ(config.canControllers.at(0).name, "CAN1"); + ASSERT_TRUE(config.canControllers.at(0).network.has_value()); + EXPECT_EQ(config.canControllers.at(0).network.value(), "CAN2"); + EXPECT_EQ(config.canControllers.at(1).name, "CAN2"); + ASSERT_EQ(config.linControllers.size(), 1u); + EXPECT_EQ(config.linControllers.at(0).name, "LIN1"); +} + } // anonymous namespace diff --git a/SilKit/source/config/YamlReader.hpp b/SilKit/source/config/YamlReader.hpp index 415fb23b7..49faf1117 100644 --- a/SilKit/source/config/YamlReader.hpp +++ b/SilKit/source/config/YamlReader.hpp @@ -227,13 +227,15 @@ class BasicYamlReader auto GetChildSafe(const std::string& name) const -> Impl { - if (HasKey(name)) + if (IsMap()) { return MakeImpl(_node.find_child(ryml::to_csubstr(name))); } - if (IsSequence()) + if (_node.is_stream()) { + // A leading "---" makes the document root a stream (a sequence of + // documents). Transparently descend into the document(s) to find the key. for (const auto& child : _node.cchildren()) { if (child.is_container() && HasKey(child, name)) @@ -241,8 +243,23 @@ class BasicYamlReader return MakeImpl(child.find_child(ryml::to_csubstr(name))); } } + return MakeImpl({}); + } + + if (IsSequence()) + { + // A YAML sequence (list) was provided where an object with named keys is + // expected. Reject the type mismatch instead of silently searching the + // list's elements for the key. This catches mistyped configuration such as + // "HealthCheck:\n - SoftResponseTimeout: 1", where the leading "- " turns + // an object into a single-element list. + std::ostringstream s; + s << "expected a mapping with key \"" << name << "\", but got a sequence"; + throw MakeConfigurationError(s.str()); } + // Not a container (absent, null or empty node): treat the key as not present so + // that objects left empty keep their default values. return MakeImpl({}); } From d6d86a0662de889b52e50cc93025fbd056cc89a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20B=C3=B6rschig?= Date: Thu, 23 Jul 2026 10:08:41 +0200 Subject: [PATCH 2/4] fixup! yaml: be more robust when parsing objects vs sequences MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marius Börschig --- SilKit/source/config/Test_YamlParser.cpp | 17 +++++++++++++++ SilKit/source/config/YamlReader.hpp | 27 ++++++++++++++++++------ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/SilKit/source/config/Test_YamlParser.cpp b/SilKit/source/config/Test_YamlParser.cpp index 681d68557..69a029765 100644 --- a/SilKit/source/config/Test_YamlParser.cpp +++ b/SilKit/source/config/Test_YamlParser.cpp @@ -713,6 +713,23 @@ TEST_F(Test_YamlParser, yaml_throw_on_object_as_sequence) } } +TEST_F(Test_YamlParser, yaml_throw_on_stream_document_as_sequence) +{ + // A leading "---" wraps the document in a stream node. The document itself must be + // a mapping; here the whole document is a bare sequence, which is the same + // object-vs-list mistype and must be rejected. + auto streamDocAsSequence = R"(--- +- SoftResponseTimeout: 1 +)"; + + EXPECT_THROW( + { + auto&& cfg = Deserialize(streamDocAsSequence); + (void)cfg; + }, + SilKit::ConfigurationError); +} + TEST_F(Test_YamlParser, yaml_healthcheck_as_map_is_accepted) { // Regression guard: the correctly-typed object must still parse. diff --git a/SilKit/source/config/YamlReader.hpp b/SilKit/source/config/YamlReader.hpp index 49faf1117..5e5f716da 100644 --- a/SilKit/source/config/YamlReader.hpp +++ b/SilKit/source/config/YamlReader.hpp @@ -236,11 +236,21 @@ class BasicYamlReader { // A leading "---" makes the document root a stream (a sequence of // documents). Transparently descend into the document(s) to find the key. - for (const auto& child : _node.cchildren()) + // A document must itself be a mapping; a document that is a bare sequence + // is the same object-vs-list mistype we reject below. + for (const auto& doc : _node.cchildren()) { - if (child.is_container() && HasKey(child, name)) + if (doc.is_seq()) { - return MakeImpl(child.find_child(ryml::to_csubstr(name))); + ThrowExpectedMapping(name); + } + if (doc.is_map()) + { + auto&& found = doc.find_child(ryml::to_csubstr(name)); + if (!found.invalid()) + { + return MakeImpl(found); + } } } return MakeImpl({}); @@ -253,9 +263,7 @@ class BasicYamlReader // list's elements for the key. This catches mistyped configuration such as // "HealthCheck:\n - SoftResponseTimeout: 1", where the leading "- " turns // an object into a single-element list. - std::ostringstream s; - s << "expected a mapping with key \"" << name << "\", but got a sequence"; - throw MakeConfigurationError(s.str()); + ThrowExpectedMapping(name); } // Not a container (absent, null or empty node): treat the key as not present so @@ -263,6 +271,13 @@ class BasicYamlReader return MakeImpl({}); } + [[noreturn]] void ThrowExpectedMapping(const std::string& name) const + { + std::ostringstream s; + s << "expected a mapping with key \"" << name << "\", but got a sequence"; + throw MakeConfigurationError(s.str()); + } + auto MakeImpl(ryml::ConstNodeRef node_) const -> Impl { return Impl{_parser, node_}; From d1804e7a5179b4d30cef2b2266939d98d15bc2b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20B=C3=B6rschig?= Date: Fri, 24 Jul 2026 12:32:50 +0200 Subject: [PATCH 3/4] fixup! fixup! yaml: be more robust when parsing objects vs sequences MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reject multiple document streams Signed-off-by: Marius Börschig --- SilKit/source/config/Test_YamlParser.cpp | 19 +++++++++++++++++++ SilKit/source/config/YamlReader.hpp | 15 ++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/SilKit/source/config/Test_YamlParser.cpp b/SilKit/source/config/Test_YamlParser.cpp index 69a029765..06b5dbeee 100644 --- a/SilKit/source/config/Test_YamlParser.cpp +++ b/SilKit/source/config/Test_YamlParser.cpp @@ -730,6 +730,25 @@ TEST_F(Test_YamlParser, yaml_throw_on_stream_document_as_sequence) SilKit::ConfigurationError); } +TEST_F(Test_YamlParser, yaml_throw_on_multi_document_stream) +{ + // A configuration must be a single YAML document. Two "---"-separated documents + // form a multi-document stream and must be rejected rather than silently reading + // keys from whichever document happens to contain them. + auto multiDocument = R"(--- +ParticipantName: Node0 +--- +ParticipantName: Node1 +)"; + + EXPECT_THROW( + { + auto&& cfg = Deserialize(multiDocument); + (void)cfg; + }, + SilKit::ConfigurationError); +} + TEST_F(Test_YamlParser, yaml_healthcheck_as_map_is_accepted) { // Regression guard: the correctly-typed object must still parse. diff --git a/SilKit/source/config/YamlReader.hpp b/SilKit/source/config/YamlReader.hpp index 5e5f716da..78c0782a2 100644 --- a/SilKit/source/config/YamlReader.hpp +++ b/SilKit/source/config/YamlReader.hpp @@ -235,9 +235,18 @@ class BasicYamlReader if (_node.is_stream()) { // A leading "---" makes the document root a stream (a sequence of - // documents). Transparently descend into the document(s) to find the key. - // A document must itself be a mapping; a document that is a bare sequence - // is the same object-vs-list mistype we reject below. + // documents). Only a single document is a valid configuration; reject a + // multi-document stream ("---"-separated documents) outright. + if (_node.num_children() > 1) + { + throw MakeConfigurationError( + "expected a single YAML document, but the configuration contains multiple " + "\"---\"-separated documents"); + } + + // Transparently descend into the document to find the key. The document + // must itself be a mapping; a document that is a bare sequence is the same + // object-vs-list mistype we reject below. for (const auto& doc : _node.cchildren()) { if (doc.is_seq()) From 591f4147f1469ad29052d1369fd85fbc243a0f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20B=C3=B6rschig?= Date: Fri, 24 Jul 2026 14:55:22 +0200 Subject: [PATCH 4/4] fixup! fixup! fixup! yaml: be more robust when parsing objects vs sequences MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit allow only single document streams, document it and update changelog Signed-off-by: Marius Börschig --- SilKit/source/config/Test_YamlParser.cpp | 30 ++++--------------- SilKit/source/config/Test_YamlValidator.cpp | 32 +++++++++++++++++++++ SilKit/source/config/YamlParser.hpp | 2 ++ SilKit/source/config/YamlParserUtils.cpp | 10 +++++++ SilKit/source/config/YamlParserUtils.hpp | 3 ++ SilKit/source/config/YamlReader.hpp | 23 ++------------- SilKit/source/config/YamlValidator.cpp | 3 ++ docs/changelog/versions/latest.md | 7 ++++- docs/configuration/configuration.rst | 12 ++++++-- 9 files changed, 75 insertions(+), 47 deletions(-) diff --git a/SilKit/source/config/Test_YamlParser.cpp b/SilKit/source/config/Test_YamlParser.cpp index 06b5dbeee..ab5ac1aac 100644 --- a/SilKit/source/config/Test_YamlParser.cpp +++ b/SilKit/source/config/Test_YamlParser.cpp @@ -643,10 +643,7 @@ TEST_F(Test_YamlParser, yaml_throw_on_missing_keyword) { TEST_F(Test_YamlParser, yaml_throw_on_healthcheck_as_sequence) { - // HealthCheck is an object with SoftResponseTimeout/HardResponseTimeout members. - // Here it is mistyped as a sequence (a list with a single element). The parser - // must reject the type mismatch instead of silently descending into the list and - // picking up the value from the contained map. + // HealthCheck is a mapping; the leading "- " mistypes it as a single-element list. auto healthCheckAsSequence = R"( HealthCheck: - SoftResponseTimeout: 1 @@ -659,17 +656,10 @@ TEST_F(Test_YamlParser, yaml_throw_on_healthcheck_as_sequence) SilKit::ConfigurationError); } -// A YAML sequence where a map/object is expected is a type mismatch. The generalized -// check lives in the parser (GetChildSafe), so it applies to every object, not just -// HealthCheck. The following cases exercise the edges of that generalization. - TEST_F(Test_YamlParser, yaml_throw_on_object_as_sequence) { - // Each of these mistypes a mapping as a single-element sequence at a different - // nesting level (top-level object, deeply nested object, object inside a list - // element). All must be rejected. + // A mapping mistyped as a sequence must be rejected at every nesting level. const std::initializer_list mistypedConfigurations = { - // top-level object as sequence R"( HealthCheck: - SoftResponseTimeout: 1 @@ -687,14 +677,12 @@ TEST_F(Test_YamlParser, yaml_throw_on_object_as_sequence) - Metrics: CollectFromRemote: true )", - // nested object (a controller's Replay) as sequence R"( CanControllers: - Name: CAN1 Replay: - UseTraceSource: Source1 )", - // object nested inside Experimental as sequence R"( Experimental: Metrics: @@ -715,9 +703,7 @@ TEST_F(Test_YamlParser, yaml_throw_on_object_as_sequence) TEST_F(Test_YamlParser, yaml_throw_on_stream_document_as_sequence) { - // A leading "---" wraps the document in a stream node. The document itself must be - // a mapping; here the whole document is a bare sequence, which is the same - // object-vs-list mistype and must be rejected. + // A single "---" document that is itself a bare sequence is the same mistype. auto streamDocAsSequence = R"(--- - SoftResponseTimeout: 1 )"; @@ -732,9 +718,7 @@ TEST_F(Test_YamlParser, yaml_throw_on_stream_document_as_sequence) TEST_F(Test_YamlParser, yaml_throw_on_multi_document_stream) { - // A configuration must be a single YAML document. Two "---"-separated documents - // form a multi-document stream and must be rejected rather than silently reading - // keys from whichever document happens to contain them. + // A configuration must be a single YAML document; two "---" documents are rejected. auto multiDocument = R"(--- ParticipantName: Node0 --- @@ -766,8 +750,7 @@ TEST_F(Test_YamlParser, yaml_healthcheck_as_map_is_accepted) TEST_F(Test_YamlParser, yaml_empty_object_keeps_defaults) { - // An object left empty (null node) is not a type mismatch: it keeps its defaults - // and must not throw. This guards the generalized check against over-rejecting. + // An empty object (null node) keeps its defaults and must not throw. ParticipantConfiguration defaults{}; auto config = Deserialize(R"( @@ -780,8 +763,7 @@ TEST_F(Test_YamlParser, yaml_empty_object_keeps_defaults) TEST_F(Test_YamlParser, yaml_sequence_typed_fields_still_parse) { - // Fields that are genuinely sequences (lists of objects) must keep working after - // the object-vs-sequence check was tightened. + // Genuinely sequence-typed fields (lists of objects) must keep working. auto config = Deserialize(R"( CanControllers: - Name: CAN1 diff --git a/SilKit/source/config/Test_YamlValidator.cpp b/SilKit/source/config/Test_YamlValidator.cpp index bac23c1e7..b8380f807 100644 --- a/SilKit/source/config/Test_YamlValidator.cpp +++ b/SilKit/source/config/Test_YamlValidator.cpp @@ -40,6 +40,38 @@ TEST_F(Test_YamlValidator, validate_without_warnings) EXPECT_TRUE(warnings.empty()) << "Warnings: " << warnings; } +TEST_F(Test_YamlValidator, validate_rejects_multi_document_stream) +{ + // A stray "---" mid-config splits it into two documents; the validator must reject it. + auto yamlString = R"yaml(Description: Log to Stdout with Level Info +Logging: +--- + Sinks: + - Level: Info + Type: Stdout +)yaml"; + + std::stringstream warnings; + bool yamlValid = ValidateWithSchema(yamlString, warnings); + EXPECT_FALSE(yamlValid) << "A multi-document stream must not validate"; + EXPECT_THAT(warnings.str(), testing::HasSubstr("one YAML document")); +} + +TEST_F(Test_YamlValidator, validate_accepts_single_leading_document_marker) +{ + // A single leading "---" is one document and stays valid. + auto yamlString = R"yaml(--- +schemaVersion: 1 +ParticipantName: CanDemoParticipant +)yaml"; + + std::stringstream warnings; + bool yamlValid = ValidateWithSchema(yamlString, warnings); + EXPECT_TRUE(yamlValid) << "A single leading '---' document must validate. Warnings: " + << warnings.str(); + EXPECT_TRUE(warnings.str().empty()) << "Warnings: " << warnings.str(); +} + TEST_F(Test_YamlValidator, validate_unknown_toplevel) { auto yamlString = R"yaml( diff --git a/SilKit/source/config/YamlParser.hpp b/SilKit/source/config/YamlParser.hpp index 7ccfee96c..b5688a44f 100644 --- a/SilKit/source/config/YamlParser.hpp +++ b/SilKit/source/config/YamlParser.hpp @@ -46,6 +46,8 @@ auto Deserialize(const std::string& input) -> T // Extract a reference to the root node of the document tree. auto root = tree.crootref(); + VSilKit::EnsureSingleDocument(root); + R reader{parser, root}; T result{}; reader.Read(result); diff --git a/SilKit/source/config/YamlParserUtils.cpp b/SilKit/source/config/YamlParserUtils.cpp index 60a462622..e2b8712c5 100644 --- a/SilKit/source/config/YamlParserUtils.cpp +++ b/SilKit/source/config/YamlParserUtils.cpp @@ -69,6 +69,16 @@ auto GetRapidyamlCallbacks() -> ryml::Callbacks return ryml::Callbacks{nullptr, RapidyamlAllocate, RapidyamlFree, RapidyamlError}; } +void EnsureSingleDocument(ryml::ConstNodeRef root) +{ + if (root.is_stream() && root.num_children() > 1) + { + throw SilKit::ConfigurationError{ + "configuration must contain exactly one YAML document, but multiple " + "\"---\"-separated documents were found"}; + } +} + } // namespace VSilKit diff --git a/SilKit/source/config/YamlParserUtils.hpp b/SilKit/source/config/YamlParserUtils.hpp index 9adba1d4f..13f11f850 100644 --- a/SilKit/source/config/YamlParserUtils.hpp +++ b/SilKit/source/config/YamlParserUtils.hpp @@ -21,4 +21,7 @@ auto MakeConfigurationError(ryml::Location location, const std::string_view mess auto GetRapidyamlCallbacks() -> ryml::Callbacks; +// Throws unless the configuration is a single YAML document. +void EnsureSingleDocument(ryml::ConstNodeRef root); + } // namespace VSilKit diff --git a/SilKit/source/config/YamlReader.hpp b/SilKit/source/config/YamlReader.hpp index 78c0782a2..454ef47c5 100644 --- a/SilKit/source/config/YamlReader.hpp +++ b/SilKit/source/config/YamlReader.hpp @@ -234,19 +234,8 @@ class BasicYamlReader if (_node.is_stream()) { - // A leading "---" makes the document root a stream (a sequence of - // documents). Only a single document is a valid configuration; reject a - // multi-document stream ("---"-separated documents) outright. - if (_node.num_children() > 1) - { - throw MakeConfigurationError( - "expected a single YAML document, but the configuration contains multiple " - "\"---\"-separated documents"); - } - - // Transparently descend into the document to find the key. The document - // must itself be a mapping; a document that is a bare sequence is the same - // object-vs-list mistype we reject below. + // Single-document stream (leading "---"): descend into the document. A + // document that is a bare sequence is the same mistype rejected below. for (const auto& doc : _node.cchildren()) { if (doc.is_seq()) @@ -267,16 +256,10 @@ class BasicYamlReader if (IsSequence()) { - // A YAML sequence (list) was provided where an object with named keys is - // expected. Reject the type mismatch instead of silently searching the - // list's elements for the key. This catches mistyped configuration such as - // "HealthCheck:\n - SoftResponseTimeout: 1", where the leading "- " turns - // an object into a single-element list. ThrowExpectedMapping(name); } - // Not a container (absent, null or empty node): treat the key as not present so - // that objects left empty keep their default values. + // Absent, null or empty node: treat the key as not present. return MakeImpl({}); } diff --git a/SilKit/source/config/YamlValidator.cpp b/SilKit/source/config/YamlValidator.cpp index 5453a40bb..5eb657561 100644 --- a/SilKit/source/config/YamlValidator.cpp +++ b/SilKit/source/config/YamlValidator.cpp @@ -630,6 +630,9 @@ bool ValidateWithSchema(const std::string& yamlString, std::ostream& warnings) auto tree = ryml::parse_in_arena(&parser, cinput); auto root = tree.crootref(); + + VSilKit::EnsureSingleDocument(root); + if (root.is_doc() && (root.is_map() || root.is_seq())) { std::string version; diff --git a/docs/changelog/versions/latest.md b/docs/changelog/versions/latest.md index c6d28a47b..c3efe83df 100644 --- a/docs/changelog/versions/latest.md +++ b/docs/changelog/versions/latest.md @@ -3,4 +3,9 @@ ## Changed - `config`: default format of file logging is set to JSON. Set "Format: Simple" in the sink to get previous behaviour. -- `docs`: added description of the logging "Format". \ No newline at end of file +- `docs`: added description of the logging "Format". + +## Fixed + +- `config`: a mapping that is mistyped as a YAML sequence (e.g. a stray leading `- ` turning `HealthCheck` into a list) is now rejected with a clear error instead of being silently accepted. +- `config`: a configuration containing multiple `---`-separated YAML documents is now rejected with a clear error. A single leading `---` remains valid. \ No newline at end of file diff --git a/docs/configuration/configuration.rst b/docs/configuration/configuration.rst index f5f178b92..41abf9c2e 100644 --- a/docs/configuration/configuration.rst +++ b/docs/configuration/configuration.rst @@ -67,9 +67,17 @@ This gives users the ability to run a simulation with multiple instances of a pa Many IDEs automatically support participant configuration schema support when the participant configuration file ends with the suffix ``.silkit.json/yaml``. -A participant configuration file is written in YAML syntax according to a specified schema. -It starts with the ``SchemaVersion``, the ``Description`` for the configuration and the ``ParticipantName``. +A participant configuration file is written in YAML syntax according to a specified schema. +It starts with the ``SchemaVersion``, the ``Description`` for the configuration and the ``ParticipantName``. This is followed by further sections for ``Includes``, ``Middleware``, ``Logging``, ``HealthCheck``, ``Tracing``, ``Extentions`` and sections for the different services of the |ProductName|. + +.. admonition:: Note + + A configuration must be a single YAML document. + A single leading document marker (``---``) is allowed, but a configuration that + contains multiple ``---``-separated documents is rejected with an error. + This most commonly happens when a stray ``---`` is placed in the middle of a file. + The outline of a participant configuration file is as follows: .. code-block:: yaml