Skip to content

Commit 1341e19

Browse files
fixup
1 parent 462013f commit 1341e19

File tree

3 files changed

+243
-11
lines changed

3 files changed

+243
-11
lines changed

tesseract_common/include/tesseract_common/property_tree.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ constexpr std::string_view LONG_UNSIGNED_INT{ "long unsigned int" };
3939
constexpr std::string_view FLOAT{ "float" };
4040
constexpr std::string_view DOUBLE{ "double" };
4141

42+
// Container of properties
43+
constexpr std::string_view CONTAINER{ "YAML::NodeType::Map" };
44+
4245
// Eigen Types
4346
constexpr std::string_view EIGEN_ISOMETRY_3D{ "Eigen::Isometry3d" };
4447
// constexpr std::string_view EIGEN_MATRIX_XD{ "Eigen::MatrixXd" };
@@ -235,10 +238,17 @@ class PropertyTree
235238
/**
236239
* @brief Check if type is a sequence
237240
* @param type The type to check
238-
* @return If it is a sequence the underlying type is returned
241+
* @return If it is a sequence, the underlying type is returned
239242
*/
240243
std::optional<std::string> isSequenceType(std::string_view type);
241244

245+
/**
246+
* @brief Check if type is a map
247+
* @param type The type to check
248+
* @return If it is a map, the underlying type is returned
249+
*/
250+
std::optional<std::string> isMapType(std::string_view type);
251+
242252
/**
243253
* @brief Validator: ensure 'required' attribute is present and non-null.
244254
* @param node Node to validate.
@@ -267,6 +277,14 @@ void validateMap(const PropertyTree& node);
267277
*/
268278
void validateSequence(const PropertyTree& node);
269279

280+
/**
281+
* @brief Validator: ensure property is a container of child properties
282+
* The property should have children and the value should be null
283+
* @param node Node to validate.
284+
* @throws runtime_error if not correct.
285+
*/
286+
void validateContainer(const PropertyTree& node);
287+
270288
void validateCustomType(const PropertyTree& node);
271289

272290
/**

tesseract_common/src/property_tree.cpp

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,13 @@ void PropertyTree::setAttribute(std::string_view name, const YAML::Node& attr)
159159
if (is_sequence.has_value())
160160
validators_.emplace_back(validateSequence);
161161

162-
if (str_type == property_type::STRING)
162+
std::optional<std::string> is_map = isMapType(str_type);
163+
if (is_map.has_value())
164+
validators_.emplace_back(validateMap);
165+
166+
if (str_type == property_type::CONTAINER)
167+
validators_.emplace_back(validateContainer);
168+
else if (str_type == property_type::STRING)
163169
validators_.emplace_back(validateTypeCast<std::string>);
164170
else if (str_type == property_type::BOOL)
165171
validators_.emplace_back(validateTypeCast<bool>);
@@ -343,6 +349,17 @@ std::optional<std::string> isSequenceType(std::string_view type)
343349
return std::string(type.substr(0, type.size() - 2));
344350
}
345351

352+
std::optional<std::string> isMapType(std::string_view type)
353+
{
354+
if (type.size() < 2)
355+
return std::nullopt;
356+
357+
if (type.substr(type.size() - 2) != "{}")
358+
return std::nullopt;
359+
360+
return std::string(type.substr(0, type.size() - 2));
361+
}
362+
346363
void validateRequired(const PropertyTree& node)
347364
{
348365
auto req_attr = node.getAttribute(property_attribute::REQUIRED);
@@ -371,7 +388,7 @@ void validateEnum(const PropertyTree& node)
371388

372389
void validateMap(const PropertyTree& node)
373390
{
374-
if (node.getValue().IsNull() && !node.getValue().IsMap())
391+
if (!node.getValue().IsMap())
375392
std::throw_with_nested(std::runtime_error("Property value is not of type YAML::NodeType::Map"));
376393
}
377394

@@ -381,6 +398,15 @@ void validateSequence(const PropertyTree& node)
381398
std::throw_with_nested(std::runtime_error("Property value is not of type YAML::NodeType::Sequence"));
382399
}
383400

401+
void validateContainer(const PropertyTree& node)
402+
{
403+
if (!node.isContainer())
404+
std::throw_with_nested(std::runtime_error("Property is not a container"));
405+
406+
if (!node.isNull())
407+
std::throw_with_nested(std::runtime_error("Property is a container but value is not null"));
408+
}
409+
384410
void validateCustomType(const PropertyTree& node)
385411
{
386412
const auto type_attr = node.getAttribute(property_attribute::TYPE);

tesseract_common/src/property_tree_demo.cpp

Lines changed: 196 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ PropertyTree buildConfigSchema()
1212
{
1313
PropertyTree schema;
1414
auto& cfg = schema.get("config");
15+
cfg.setAttribute(property_attribute::TYPE, property_type::CONTAINER);
1516
cfg.setAttribute(property_attribute::DOC, "Main config for plugin");
1617
cfg.setAttribute(property_attribute::REQUIRED, true);
1718
std::map<int, std::string> return_options;
@@ -30,6 +31,7 @@ PropertyTree buildConfigSchema()
3031
// inputs
3132
{
3233
auto& inputs = cfg.get("inputs");
34+
inputs.setAttribute(property_attribute::TYPE, property_type::CONTAINER);
3335
inputs.setAttribute(property_attribute::DOC, "Input sources");
3436
inputs.setAttribute(property_attribute::REQUIRED, true);
3537
// program
@@ -67,10 +69,15 @@ PropertyTree buildConfigSchema()
6769
}
6870
// outputs
6971
{
70-
auto& outs = cfg.get("outputs");
71-
auto& prop = outs.get("program");
72-
prop.setAttribute(property_attribute::TYPE, property_type::STRING);
73-
prop.setAttribute(property_attribute::REQUIRED, true);
72+
auto& outputs = cfg.get("outputs");
73+
outputs.setAttribute(property_attribute::TYPE, property_type::CONTAINER);
74+
outputs.setAttribute(property_attribute::DOC, "Output sources");
75+
outputs.setAttribute(property_attribute::REQUIRED, true);
76+
{
77+
auto& prop = outputs.get("program");
78+
prop.setAttribute(property_attribute::TYPE, property_type::STRING);
79+
prop.setAttribute(property_attribute::REQUIRED, true);
80+
}
7481
}
7582
// format_result_as_input
7683
{
@@ -85,6 +92,7 @@ tesseract_common::PropertyTree getTaskComposerGraphSchema()
8592
{
8693
using namespace tesseract_common;
8794
PropertyTree schema;
95+
schema.setAttribute(property_attribute::TYPE, property_type::CONTAINER);
8896
schema.setAttribute(property_attribute::DOC, "TaskComposerGraph");
8997
{
9098
auto& prop = schema.get("class");
@@ -94,6 +102,7 @@ tesseract_common::PropertyTree getTaskComposerGraphSchema()
94102
}
95103

96104
auto& config_schema = schema.get("config");
105+
config_schema.setAttribute(property_attribute::TYPE, property_type::CONTAINER);
97106
config_schema.setAttribute(property_attribute::REQUIRED, true);
98107
{
99108
auto& prop = config_schema.get("conditional");
@@ -105,21 +114,21 @@ tesseract_common::PropertyTree getTaskComposerGraphSchema()
105114

106115
{
107116
auto& inputs = config_schema.get("inputs");
117+
inputs.setAttribute(property_attribute::TYPE, property_type::CONTAINER);
108118
inputs.setAttribute(property_attribute::DOC, "Input sources");
109-
inputs.addValidator(validateMap);
110119
}
111120

112121
{
113122
auto& outputs = config_schema.get("outputs");
123+
outputs.setAttribute(property_attribute::TYPE, property_type::CONTAINER);
114124
outputs.setAttribute(property_attribute::DOC, "Output sources");
115-
outputs.addValidator(validateMap);
116125
}
117126

118127
{
119128
auto& prop = config_schema.get("nodes");
129+
prop.setAttribute(property_attribute::TYPE, "TaskComposerGraphNode{}");
120130
prop.setAttribute(property_attribute::DOC, "Map of all task nodes");
121131
prop.setAttribute(property_attribute::REQUIRED, true);
122-
prop.addValidator(validateMap);
123132
}
124133

125134
{
@@ -145,6 +154,7 @@ tesseract_common::PropertyTree getTaskComposerGraphEdgeSchema()
145154
{
146155
using namespace tesseract_common;
147156
PropertyTree schema;
157+
schema.setAttribute(property_attribute::TYPE, property_type::CONTAINER);
148158
schema.setAttribute(property_attribute::DOC, "TaskComposerGraphEdge");
149159
{
150160
auto& prop = schema.get("source");
@@ -158,6 +168,138 @@ tesseract_common::PropertyTree getTaskComposerGraphEdgeSchema()
158168
prop.setAttribute(property_attribute::TYPE, property_type::createList(property_type::STRING));
159169
prop.setAttribute(property_attribute::DOC, "The list of destination task name");
160170
prop.setAttribute(property_attribute::REQUIRED, true);
171+
prop.addValidator(validateTypeCast<std::vector<std::string>>);
172+
}
173+
174+
return schema;
175+
}
176+
177+
tesseract_common::PropertyTree getTaskComposerRasterOnlySchema()
178+
{
179+
using namespace tesseract_common;
180+
PropertyTree schema;
181+
schema.setAttribute(property_attribute::TYPE, property_type::CONTAINER);
182+
schema.setAttribute(property_attribute::DOC, "TaskComposerGraph");
183+
std::map<int, std::string> return_options;
184+
return_options[0] = "Error";
185+
return_options[1] = "Successful";
186+
schema.setAttribute("return_options", YAML::Node(return_options));
187+
{
188+
auto& prop = schema.get("class");
189+
prop.setAttribute(property_attribute::TYPE, property_type::STRING);
190+
prop.setAttribute(property_attribute::DOC, "The class factory name");
191+
prop.setAttribute(property_attribute::REQUIRED, true);
192+
}
193+
194+
auto& config_schema = schema.get("config");
195+
config_schema.setAttribute(property_attribute::TYPE, property_type::CONTAINER);
196+
config_schema.setAttribute(property_attribute::REQUIRED, true);
197+
{
198+
auto& prop = config_schema.get("conditional");
199+
prop.setAttribute(property_attribute::TYPE, property_type::BOOL);
200+
prop.setAttribute(property_attribute::DEFAULT, true);
201+
prop.setAttribute(property_attribute::DOC, "Enable conditional execution");
202+
prop.setAttribute(property_attribute::REQUIRED, true);
203+
}
204+
205+
{
206+
auto& inputs = config_schema.get("inputs");
207+
inputs.setAttribute(property_attribute::TYPE, property_type::CONTAINER);
208+
inputs.setAttribute(property_attribute::DOC, "Input sources");
209+
210+
// program
211+
{
212+
auto& prop = inputs.get("program");
213+
prop.setAttribute(property_attribute::TYPE, property_type::STRING);
214+
prop.setAttribute(property_attribute::DOC, "The composite instruction");
215+
prop.setAttribute(property_attribute::REQUIRED, true);
216+
}
217+
// environment
218+
{
219+
auto& prop = inputs.get("environment");
220+
prop.setAttribute(property_attribute::TYPE, property_type::STRING);
221+
prop.setAttribute(property_attribute::DOC, "The tesseract environment");
222+
prop.setAttribute(property_attribute::REQUIRED, true);
223+
}
224+
}
225+
226+
{
227+
auto& outputs = config_schema.get("outputs");
228+
outputs.setAttribute(property_attribute::TYPE, property_type::CONTAINER);
229+
outputs.setAttribute(property_attribute::DOC, "Output sources");
230+
// program
231+
{
232+
auto& prop = outputs.get("program");
233+
prop.setAttribute(property_attribute::TYPE, property_type::STRING);
234+
prop.setAttribute(property_attribute::DOC, "The composite instruction");
235+
prop.setAttribute(property_attribute::REQUIRED, true);
236+
}
237+
}
238+
239+
{
240+
auto& raster = config_schema.get("raster");
241+
raster.setAttribute(property_attribute::TYPE, property_type::CONTAINER);
242+
raster.setAttribute(property_attribute::DOC, "The raster task");
243+
raster.setAttribute(property_attribute::REQUIRED, true);
244+
{
245+
auto& prop = raster.get("task");
246+
prop.setAttribute(property_attribute::TYPE, property_type::STRING);
247+
prop.setAttribute(property_attribute::DOC, "The task name");
248+
prop.setAttribute(property_attribute::REQUIRED, true);
249+
}
250+
auto& raster_config = raster.get("config");
251+
raster_config.setAttribute(property_attribute::TYPE, property_type::CONTAINER);
252+
raster_config.setAttribute(property_attribute::REQUIRED, true);
253+
{
254+
auto& prop = raster_config.get("abort_terminal");
255+
prop.setAttribute(property_attribute::TYPE, property_type::INT);
256+
prop.setAttribute(property_attribute::MINIMUM, 0);
257+
prop.setAttribute(property_attribute::DOC, "The abort terminal");
258+
}
259+
{
260+
auto& prop = raster_config.get("remapping");
261+
prop.setAttribute(property_attribute::TYPE,
262+
property_type::createMap(property_type::STRING, property_type::STRING));
263+
prop.setAttribute(property_attribute::DOC, "The remapping of input and output keys");
264+
}
265+
{
266+
auto& prop = raster_config.get("indexing");
267+
prop.setAttribute(property_attribute::TYPE, property_type::createList(property_type::STRING));
268+
prop.setAttribute(property_attribute::DOC, "The input and output keys to index");
269+
}
270+
}
271+
272+
{
273+
auto& transition = config_schema.get("transition");
274+
transition.setAttribute(property_attribute::TYPE, property_type::CONTAINER);
275+
transition.setAttribute(property_attribute::DOC, "The transition task");
276+
transition.setAttribute(property_attribute::REQUIRED, true);
277+
{
278+
auto& prop = transition.get("task");
279+
prop.setAttribute(property_attribute::TYPE, property_type::STRING);
280+
prop.setAttribute(property_attribute::DOC, "The task name");
281+
prop.setAttribute(property_attribute::REQUIRED, true);
282+
}
283+
auto& transition_config = transition.get("config");
284+
transition_config.setAttribute(property_attribute::TYPE, property_type::CONTAINER);
285+
transition_config.setAttribute(property_attribute::REQUIRED, true);
286+
{
287+
auto& prop = transition_config.get("abort_terminal");
288+
prop.setAttribute(property_attribute::TYPE, property_type::INT);
289+
prop.setAttribute(property_attribute::MINIMUM, 0);
290+
prop.setAttribute(property_attribute::DOC, "The abort terminal");
291+
}
292+
{
293+
auto& prop = transition_config.get("remapping");
294+
prop.setAttribute(property_attribute::TYPE,
295+
property_type::createMap(property_type::STRING, property_type::STRING));
296+
prop.setAttribute(property_attribute::DOC, "The remapping of input and output keys");
297+
}
298+
{
299+
auto& prop = transition_config.get("indexing");
300+
prop.setAttribute(property_attribute::TYPE, property_type::createList(property_type::STRING));
301+
prop.setAttribute(property_attribute::DOC, "The input and output keys to index");
302+
}
161303
}
162304

163305
return schema;
@@ -221,6 +363,24 @@ class: PipelineTaskFactory
221363
destinations: [DoneTask]
222364
terminals: [DoneTask])";
223365

366+
std::string raster_only_str = R"(
367+
class: RasterOnlyTaskFactory
368+
config:
369+
conditional: true
370+
inputs:
371+
program: input_data
372+
environment: environment
373+
outputs:
374+
program: output_data
375+
raster:
376+
task: CartesianPipeline
377+
config:
378+
indexing: [input_data, output_data]
379+
transition:
380+
task: CartesianPipeline
381+
config:
382+
indexing: [input_data, output_data])";
383+
224384
int testBasic()
225385
{
226386
// Parse schema from external file (config_schema.yaml)
@@ -270,4 +430,32 @@ int testCustomType()
270430
return 0;
271431
}
272432

273-
int main() { return testCustomType(); }
433+
int testRasterOnlyType()
434+
{
435+
// Parse schema from external file (config_schema.yaml)
436+
PropertyTree schema = getTaskComposerRasterOnlySchema();
437+
std::cout << "Schema:\n" << schema.toYAML(false) << "\n\n";
438+
439+
// Merge schema metadata into config tree
440+
schema.mergeConfig(YAML::Load(raster_only_str));
441+
std::cout << "Exclude attrubutes:\n" << schema.toYAML() << "\n\n";
442+
std::cout << "Include attrubutes:\n" << schema.toYAML(false) << "\n\n";
443+
444+
try
445+
{
446+
schema.validate();
447+
}
448+
catch (const std::exception& e)
449+
{
450+
tesseract_common::printNestedException(e);
451+
return 1;
452+
}
453+
454+
return 0;
455+
}
456+
457+
int main()
458+
{
459+
// return testCustomType();
460+
return testRasterOnlyType();
461+
}

0 commit comments

Comments
 (0)