Skip to content

Commit 9dd06d0

Browse files
committed
engine: Provide better error messages for missing plugins
This resolves #82.
1 parent 4aa7ad0 commit 9dd06d0

File tree

6 files changed

+193
-9
lines changed

6 files changed

+193
-9
lines changed

engine/src/stack.cpp

Lines changed: 87 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
namespace fs = boost::filesystem;
3535

3636
#include <cloe/utility/std_extensions.hpp> // for join_vector
37+
#include <fable/utility.hpp> // for indent_string
3738

3839
namespace cloe {
3940

@@ -459,7 +460,8 @@ void Stack::from_conf(const Conf& _conf, size_t depth) {
459460
//
460461
// But that would result in a very verbose error message, so we shall
461462
// throw a more user-friendly error message instead.
462-
throw Error{"require version compatible with {}, got {}", CLOE_STACK_VERSION, ver}.explanation(R"(
463+
throw Error{"require version compatible with {}, got {}", CLOE_STACK_VERSION, ver}
464+
.explanation(R"(
463465
It looks like you are attempting to load a stack file with an
464466
incompatible version.
465467
@@ -534,14 +536,14 @@ void Stack::from_conf(const Conf& _conf, size_t depth) {
534536
If you feel that you need more than the default allowed recursion
535537
depth (64), you are free to increase the limit within the stack file:
536538
537-
{{
538-
"engine": {{
539-
"security": {{
539+
{
540+
"version": "4",
541+
"engine": {
542+
"security": {
540543
"max_include_depth": 1024
541-
}}
542-
}},
543-
...
544-
}}
544+
}
545+
}
546+
}
545547
546548
This should be done sparingly. If you have such an inclusion depth,
547549
chances are the structure of the stack files is sub-optimal.
@@ -619,7 +621,7 @@ void Stack::validate_or_throw(const Conf& c) const {
619621
copy.validate_self();
620622
}
621623

622-
bool Stack::validate(const Conf &c, std::optional<SchemaError> &err) const {
624+
bool Stack::validate(const Conf& c, std::optional<SchemaError>& err) const {
623625
Stack copy(*this);
624626
try {
625627
copy.from_conf(c);
@@ -740,4 +742,80 @@ void Stack::check_completeness() const {
740742
}
741743
}
742744

745+
[[noreturn]] void throw_missing_plugin_error(Conf conf,
746+
std::string_view plugin_type,
747+
std::string_view plugin_name,
748+
const std::vector<std::string>& available_plugins) {
749+
throw Error{"unknown {} plugin: {}", plugin_type, plugin_name}.explanation(
750+
R"(
751+
Error located in JSON segment at {}:
752+
753+
{}
754+
755+
The plugin referred to by the "binding" field is not available.
756+
The following {} plugins are available:
757+
758+
{}
759+
760+
If the plugin you are looking for is not in that list, then you need
761+
to get cloe-engine to load the plugin first.
762+
There are several mechanisms for loading plugins:
763+
764+
1. Add directory to CLOE_PLUGIN_PATH environment variable:
765+
766+
export CLOE_PLUGIN_PATH="$CLOE_PLUGIN_PATH:/path/to/dir/containing/plugin"
767+
768+
2. Add directory containing plugin from the command-line:
769+
770+
cloe-engine --plugin-path=/path/to/dir/containing/plugin run config.json
771+
772+
3. Specify path to plugin in a stackfile directly:
773+
774+
{{
775+
"version": "4",
776+
"plugins": [
777+
{{
778+
"path": "/path/to/dir/containing/plugin"
779+
}},
780+
{{
781+
"path": "/path/to/plugin.so"
782+
}}
783+
]
784+
}}
785+
786+
)",
787+
conf.root(), fable::indent_string(conf->dump(2), " "), plugin_type,
788+
fable::join_vector(available_plugins, "\n "));
789+
}
790+
791+
bool SimulatorSchema::validate(const Conf& c, std::optional<SchemaError>& err) const {
792+
assert(schema_ != nullptr);
793+
auto factory = c.get<std::string>(factory_key_);
794+
if (available_.count(factory) == 0) {
795+
// Override error message.
796+
throw_missing_plugin_error(c, "simulator", factory, get_factory_keys());
797+
}
798+
return schema_->validate(c, err);
799+
}
800+
801+
bool ControllerSchema::validate(const Conf& c, std::optional<SchemaError>& err) const {
802+
assert(schema_ != nullptr);
803+
auto factory = c.get<std::string>(factory_key_);
804+
if (available_.count(factory) == 0) {
805+
// Override error message.
806+
throw_missing_plugin_error(c, "controller", factory, get_factory_keys());
807+
}
808+
return schema_->validate(c, err);
809+
}
810+
811+
bool ComponentSchema::validate(const Conf& c, std::optional<SchemaError>& err) const {
812+
assert(schema_ != nullptr);
813+
auto factory = c.get<std::string>(factory_key_);
814+
if (available_.count(factory) == 0) {
815+
// Override error message.
816+
throw_missing_plugin_error(c, "component", factory, get_factory_keys());
817+
}
818+
return schema_->validate(c, err);
819+
}
820+
743821
} // namespace cloe

engine/src/stack.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,8 @@ class SimulatorSchema : public FactoryPlugin<SimulatorConf, SimulatorFactory> {
536536
public:
537537
SimulatorSchema();
538538
virtual ~SimulatorSchema() = default;
539+
540+
bool validate(const Conf& c, std::optional<SchemaError>& err) const override;
539541
};
540542

541543
// --------------------------------------------------------------------------------------------- //
@@ -572,6 +574,8 @@ class ControllerSchema : public FactoryPlugin<ControllerConf, ControllerFactory>
572574
public:
573575
ControllerSchema();
574576
virtual ~ControllerSchema() = default;
577+
578+
bool validate(const Conf& c, std::optional<SchemaError>& err) const override;
575579
};
576580

577581
// --------------------------------------------------------------------------------------------- //
@@ -664,6 +668,8 @@ class ComponentSchema : public FactoryPlugin<ComponentConf, ComponentFactory> {
664668
public:
665669
ComponentSchema();
666670
virtual ~ComponentSchema() = default;
671+
672+
bool validate(const Conf& c, std::optional<SchemaError>& err) const override;
667673
};
668674

669675
// TODO(ben): Add AliasConf as alternative to ComponentConf.

engine/tests/test_engine.bats

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ check_engine_with_server() {
157157
cloe-engine run test_engine_ignore.json
158158
}
159159

160+
@test "$(testname 'Expect check failure' 'test_engine_unavailable_*.json' '6cf4ded3-8a57-4dee-afac-bb03a8068e41')" {
161+
run cloe-engine -l info check -d test_engine_unavailable_*.json
162+
test $status -eq $CLOE_EXIT_UNKNOWN
163+
}
164+
160165
@test "$(testname 'Expect check failure' 'test_engine_include_nonexistent.json' 'bad115cc-0397-48e6-9a51-bdcfeaf6b024')" {
161166
run cloe-engine check test_engine_include_nonexistent.json
162167
assert_check_failure $status $output
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// This test checks that cloe-engine will find the error that the component
2+
// binding does not exist.
3+
//
4+
// The stack file is complete except for the incorrect "component" binding.
5+
{
6+
"version": "4",
7+
"server": {
8+
"listen_port": 23456
9+
},
10+
"vehicles": [
11+
{
12+
"from": {
13+
"simulator": "nop",
14+
"index": 0
15+
},
16+
"name": "default",
17+
"components": {
18+
"test_failure": {
19+
"binding": "misspelled"
20+
}
21+
}
22+
}
23+
],
24+
"simulators": [
25+
{
26+
"binding": "nop"
27+
}
28+
],
29+
"controllers": [
30+
{
31+
"binding": "basic",
32+
"vehicle": "default"
33+
}
34+
]
35+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// This test checks that cloe-engine will find the error that the controller
2+
// binding does not exist.
3+
//
4+
// The stack file is complete except for the incorrect "controller" binding.
5+
{
6+
"version": "4",
7+
"server": {
8+
"listen_port": 23456
9+
},
10+
"vehicles": [
11+
{
12+
"from": {
13+
"simulator": "nop",
14+
"index": 0
15+
},
16+
"name": "default"
17+
}
18+
],
19+
"simulators": [
20+
{
21+
"binding": "nop"
22+
}
23+
],
24+
"controllers": [
25+
{
26+
"binding": "misspelled",
27+
"vehicle": "default"
28+
}
29+
]
30+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// This test checks that cloe-engine will find the error that the simulator
2+
// binding does not exist.
3+
//
4+
// The stack file is complete except for the incorrect "simulator" binding.
5+
{
6+
"version": "4",
7+
"server": {
8+
"listen_port": 23456
9+
},
10+
"vehicles": [
11+
{
12+
"from": {
13+
"simulator": "nop",
14+
"index": 0
15+
},
16+
"name": "default"
17+
}
18+
],
19+
"simulators": [
20+
{
21+
"binding": "unknown"
22+
}
23+
],
24+
"controllers": [
25+
{
26+
"binding": "basic",
27+
"vehicle": "default"
28+
}
29+
]
30+
}

0 commit comments

Comments
 (0)