Skip to content

Commit 9d280fb

Browse files
authored
config: print proper error message for missing keys (#305)
Co-authored-by: Marius Börschig <marius.boerschig@vector.com> Co-authored-by: Daniel Edwards <Daniel.Edwards@vector.com> Signed-off-by: Marius Börschig <Marius.Boerschig@vector.com> Signed-off-by: Daniel Edwards <Daniel.Edwards@vector.com>
1 parent 58918b3 commit 9d280fb

8 files changed

Lines changed: 206 additions & 57 deletions

File tree

SilKit/source/config/CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ add_library(O_SilKit_Config OBJECT
3131
YamlReader.cpp
3232
YamlWriter.hpp
3333
YamlWriter.cpp
34-
34+
35+
YamlParserUtils.hpp
36+
YamlParserUtils.cpp
37+
3538
YamlValidator.hpp
3639
YamlValidator.cpp
3740
)

SilKit/source/config/Test_YamlParser.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,4 +517,66 @@ TEST_F(Test_YamlParser, yaml_deprecated_RpcServer_configuration)
517517
ASSERT_TRUE(participantConfiguration.rpcServers[2].functionName.has_value());
518518
EXPECT_EQ(participantConfiguration.rpcServers[2].functionName.value(), "TheFunction3");
519519
}
520+
521+
TEST_F(Test_YamlParser, yaml_throw_on_missing_keyword) {
522+
// the config has the pKeySlotId missing, but it is required
523+
auto configWithoutKeyslot = R"(
524+
FlexrayControllers:
525+
- ClusterParameters:
526+
gColdstartAttempts: 8
527+
gCycleCountMax: 63
528+
gListenNoise: 2
529+
gMacroPerCycle: 3636
530+
gMaxWithoutClockCorrectionFatal: 2
531+
gMaxWithoutClockCorrectionPassive: 2
532+
gNumberOfMiniSlots: 291
533+
gNumberOfStaticSlots: 70
534+
gPayloadLengthStatic: 16
535+
gSyncFrameIDCountMax: 15
536+
gdActionPointOffset: 2
537+
gdDynamicSlotIdlePhase: 1
538+
gdMiniSlot: 5
539+
gdMiniSlotActionPointOffset: 2
540+
gdStaticSlot: 31
541+
gdSymbolWindow: 1
542+
gdSymbolWindowActionPointOffset: 1
543+
gdTSSTransmitter: 9
544+
gdWakeupTxActive: 60
545+
gdWakeupTxIdle: 180
546+
Name: FlexRay1
547+
NodeParameters:
548+
pAllowHaltDueToClock: 1
549+
pAllowPassiveToActive: 0
550+
pChannels: AB
551+
pClusterDriftDamping: 2
552+
#pKeySlotId: 10
553+
pKeySlotOnlyEnabled: 0
554+
pKeySlotUsedForStartup: 1
555+
pKeySlotUsedForSync: 0
556+
pLatestTx: 249
557+
pMacroInitialOffsetA: 3
558+
pMacroInitialOffsetB: 3
559+
pMicroInitialOffsetA: 6
560+
pMicroInitialOffsetB: 6
561+
pMicroPerCycle: 200000
562+
pOffsetCorrectionOut: 127
563+
pOffsetCorrectionStart: 3632
564+
pRateCorrectionOut: 81
565+
pSamplesPerMicrotick: 2
566+
pWakeupChannel: A
567+
pWakeupPattern: 33
568+
pdAcceptedStartupRange: 212
569+
pdListenTimeout: 400162
570+
pdMicrotick: 25ns
571+
572+
)";
573+
574+
EXPECT_THROW(
575+
{
576+
auto cfg = Deserialize<ParticipantConfiguration>(
577+
configWithoutKeyslot);
578+
},
579+
SilKit::ConfigurationError);
580+
}
581+
520582
} // anonymous namespace
Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,5 @@
11
// SPDX-FileCopyrightText: 2025 Vector Informatik GmbH
22
//
33
// SPDX-License-Identifier: MIT
4-
#include "YamlParser.hpp"
5-
6-
namespace VSilKit {
7-
auto ParseCapabilities(const std::string& input) -> std::vector<std::map<std::string, std::string>>
8-
{
9-
std::vector<std::map<std::string, std::string>> result;
10-
auto&& cinput = ryml::to_csubstr(input);
11-
auto t = ryml::parse_in_arena(cinput);
124

13-
auto root = t.crootref();
14-
if (!root.is_seq())
15-
{
16-
throw SilKit::ConfigurationError{"First element in Capabilities string is not a sequence"};
17-
}
18-
if (root.has_children())
19-
{
20-
for (auto&& child : root.children())
21-
{
22-
if (!child.is_map())
23-
{
24-
throw SilKit::ConfigurationError{"Capabilities should be a sequence of map objects."};
25-
}
26-
}
27-
}
28-
root >> result;
29-
return result;
30-
}
31-
32-
} // namespace VSilKit
5+
#include "YamlParser.hpp"

SilKit/source/config/YamlParser.hpp

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,12 @@
44
// SPDX-License-Identifier: MIT
55

66
#include <string>
7-
#include <memory>
8-
#include <sstream>
9-
#include <map>
10-
#include <vector>
117

128
#include "YamlReader.hpp"
139
#include "YamlWriter.hpp"
14-
#include "rapidyaml.hpp"
15-
16-
namespace VSilKit {
10+
#include "YamlParserUtils.hpp"
1711

18-
// Utility for parsing key-value lists for protocol capabilities
19-
auto ParseCapabilities(const std::string& input) -> std::vector<std::map<std::string, std::string>>;
20-
21-
} // namespace VSilKit
12+
#include "rapidyaml.hpp"
2213

2314

2415
namespace SilKit {
@@ -36,6 +27,8 @@ auto Deserialize(const std::string& input) -> T
3627
return {};
3728
}
3829

30+
const auto rapidyamlCallbacks = VSilKit::GetRapidyamlCallbacks();
31+
3932
ryml::ParserOptions options{};
4033
options.locations(true);
4134

@@ -46,6 +39,11 @@ auto Deserialize(const std::string& input) -> T
4639
try
4740
{
4841
auto tree = ryml::parse_in_arena(&parser, cinput);
42+
43+
// Install the error-handling callbacks. This will nicely format errors and throw an exception.
44+
tree.callbacks(rapidyamlCallbacks);
45+
46+
// Extract a reference to the root node of the document tree.
4947
auto root = tree.crootref();
5048

5149
R reader{parser, root};
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// SPDX-FileCopyrightText: 2026 Vector Informatik GmbH
2+
//
3+
// SPDX-License-Identifier: MIT
4+
5+
#include "YamlParserUtils.hpp"
6+
7+
#include "silkit/participant/exception.hpp"
8+
9+
#include <sstream>
10+
11+
12+
namespace {
13+
14+
auto RapidyamlAllocate(size_t length, void* hint, void* userData) -> void*;
15+
void RapidyamlFree(void* ptr, size_t length, void* userData);
16+
void RapidyamlError(const char* message, size_t length, ryml::Location location, void* userData);
17+
18+
} // namespace
19+
20+
21+
namespace VSilKit {
22+
23+
auto ParseCapabilities(const std::string& input) -> std::vector<std::map<std::string, std::string>>
24+
{
25+
std::vector<std::map<std::string, std::string>> result;
26+
auto&& cinput = ryml::to_csubstr(input);
27+
auto t = ryml::parse_in_arena(cinput);
28+
29+
auto root = t.crootref();
30+
if (!root.is_seq())
31+
{
32+
throw SilKit::ConfigurationError{"First element in Capabilities string is not a sequence"};
33+
}
34+
if (root.has_children())
35+
{
36+
for (auto&& child : root.children())
37+
{
38+
if (!child.is_map())
39+
{
40+
throw SilKit::ConfigurationError{"Capabilities should be a sequence of map objects."};
41+
}
42+
}
43+
}
44+
root >> result;
45+
return result;
46+
}
47+
48+
auto MakeConfigurationError(ryml::Location location, const std::string_view message) -> SilKit::ConfigurationError
49+
{
50+
std::ostringstream s;
51+
52+
s << "error parsing configuration";
53+
if (location.name.empty())
54+
{
55+
s << " string: ";
56+
}
57+
else
58+
{
59+
s << " file " << location.name << ": ";
60+
}
61+
62+
s << "line " << (location.line + 1) << " column " << location.col << ": " << message;
63+
64+
return SilKit::ConfigurationError{s.str()};
65+
}
66+
67+
auto GetRapidyamlCallbacks() -> ryml::Callbacks
68+
{
69+
return ryml::Callbacks{nullptr, RapidyamlAllocate, RapidyamlFree, RapidyamlError};
70+
}
71+
72+
} // namespace VSilKit
73+
74+
75+
namespace {
76+
77+
auto RapidyamlAllocate(const size_t length, void* /*hint*/, void* /*userData*/) -> void*
78+
{
79+
return std::malloc(length);
80+
}
81+
82+
void RapidyamlFree(void* ptr, size_t /*length*/, void* /*userData*/)
83+
{
84+
std::free(ptr);
85+
}
86+
87+
void RapidyamlError(const char* message, const size_t length, ryml::Location location, void* /*userData*/)
88+
{
89+
const std::string_view rapidyamlMessage{message, length};
90+
throw VSilKit::MakeConfigurationError(location, message);
91+
}
92+
93+
} // namespace
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// SPDX-FileCopyrightText: 2026 Vector Informatik GmbH
2+
//
3+
// SPDX-License-Identifier: MIT
4+
5+
#pragma once
6+
7+
#include <map>
8+
#include <string>
9+
#include <vector>
10+
11+
#include "silkit/participant/exception.hpp"
12+
13+
#include "rapidyaml.hpp"
14+
15+
namespace VSilKit {
16+
17+
// Utility for parsing key-value lists for protocol capabilities
18+
auto ParseCapabilities(const std::string& input) -> std::vector<std::map<std::string, std::string>>;
19+
20+
auto MakeConfigurationError(ryml::Location location, const std::string_view message) -> SilKit::ConfigurationError;
21+
22+
auto GetRapidyamlCallbacks() -> ryml::Callbacks;
23+
24+
} // namespace VSilKit

SilKit/source/config/YamlReader.hpp

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "rapidyaml.hpp"
1515

1616
#include "ParticipantConfiguration.hpp"
17+
#include "YamlParserUtils.hpp"
1718

1819
namespace VSilKit {
1920

@@ -125,6 +126,14 @@ class BasicYamlReader
125126
void ReadKeyValue(T& value, const std::string& name)
126127
{
127128
auto&& child = GetChildSafe(name);
129+
130+
if (!child.IsValid())
131+
{
132+
std::ostringstream s;
133+
s << "missing key: " << name;
134+
throw MakeConfigurationError(s.str());
135+
}
136+
128137
child.Read(value);
129138
}
130139

@@ -205,25 +214,10 @@ class BasicYamlReader
205214
return node.is_map() && !node.find_child(ryml::to_csubstr(name)).invalid();
206215
}
207216

208-
auto MakeConfigurationError(const char* message) const -> SilKit::ConfigurationError
217+
auto MakeConfigurationError(const std::string_view message) const -> SilKit::ConfigurationError
209218
{
210219
const auto location = _parser.location(_node);
211-
212-
std::ostringstream s;
213-
214-
s << "error parsing configuration";
215-
if (location.name.empty())
216-
{
217-
s << " file " << location.name << ": ";
218-
}
219-
else
220-
{
221-
s << " string: ";
222-
}
223-
224-
s << "line " << location.line << " column " << location.col << ": " << message;
225-
226-
return SilKit::ConfigurationError{s.str()};
220+
return VSilKit::MakeConfigurationError(location, message);
227221
}
228222

229223
auto GetChildSafe(const std::string& name) const -> Impl

docs/changelog/versions/latest.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ This is a Quality Assured Release.
1515
- `lin`: fixed TX/RX behavior when controller is not operational
1616

1717
- `lin`: populate the `timestamp` field in `LinControllerStatusUpdate` message
18+
19+
- `config`: print proper error message for missing keys

0 commit comments

Comments
 (0)