From 1a9a41f9bad4014aecea4e7ab3771cc887f63e5c Mon Sep 17 00:00:00 2001 From: lschmid Date: Sun, 17 Nov 2024 18:36:53 -0500 Subject: [PATCH 1/3] add getters file --- config_utilities/include/config_utilities/getters.h | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 config_utilities/include/config_utilities/getters.h diff --git a/config_utilities/include/config_utilities/getters.h b/config_utilities/include/config_utilities/getters.h new file mode 100644 index 0000000..e69de29 From 4adaa12129131b6ed13594f90cbb8626531dcb61 Mon Sep 17 00:00:00 2001 From: Florian Tschopp Date: Sun, 17 Nov 2024 23:37:57 +0000 Subject: [PATCH 2/3] getters (#34) --- .../include/config_utilities/config.h | 56 ++++++++++++ config_utilities/test/CMakeLists.txt | 1 + config_utilities/test/tests/getters.cpp | 86 +++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 config_utilities/test/tests/getters.cpp diff --git a/config_utilities/include/config_utilities/config.h b/config_utilities/include/config_utilities/config.h index db8fe14..65f195e 100644 --- a/config_utilities/include/config_utilities/config.h +++ b/config_utilities/include/config_utilities/config.h @@ -42,6 +42,7 @@ #include #include "config_utilities/internal/checks.h" +#include "config_utilities/internal/logger.h" #include "config_utilities/internal/namespacing.h" #include "config_utilities/internal/visitor.h" #include "config_utilities/traits.h" @@ -94,6 +95,61 @@ void field(T& field, const std::string& field_name, const std::string& unit = "" internal::Visitor::visitField(field, field_name, unit); } + +/** + * @brief Lists all the fields of the given configuration. + * + * This function retrieves metadata from the provided configuration object + * and extracts the names of all fields, returning them in a vector. + * + * @tparam ConfigT The type of the configuration. + * @param config The configuration object whose fields are to be listed. + * @return A vector containing the names of all fields in the configuration. + */ +template +std::vector listFields(const ConfigT& config) { + internal::MetaData data = internal::Visitor::getValues(config); + std::vector fields; + for (const auto& field_info : data.field_infos) { + fields.emplace_back(field_info.name); + } + return fields; +} + +/** + * @brief Retrieves the value of a specified field from the given configuration. + * + * This function searches for a field with the specified name in the provided + * configuration object and attempts to convert its value to the requested type. + * If the field is found and the conversion is successful, the value is returned + * as an optional. If the field is not found or the conversion fails, a warning + * is logged and an empty optional is returned. + * + * @tparam ConfigT The type of the configuration. + * @tparam T The type to which the field value should be converted. + * @param config The configuration object from which the field value is to be retrieved. + * @param field_name The name of the field whose value is to be retrieved. + * @return An optional containing the value of the field if found and successfully converted, + * otherwise an empty optional. + */ +template +std::optional getField(const ConfigT& config, const std::string& field_name) { + internal::MetaData data = internal::Visitor::getValues(config); + for (const auto& field_info : data.field_infos) { + if (field_info.name == field_name) { + try { + return field_info.value.as(); + } catch (const YAML::BadConversion& e) { + internal::Logger::logWarning("Field " + field_name + " could not be converted to the requested type."); + + return std::nullopt; + } + } + } + internal::Logger::logWarning("Field " + field_name + " not found in config."); + return std::nullopt; +} + /** * @brief Declare that this config inherits from a base config. Note that this call typically requires explicit * template declaration or explicit casting for argument dependent look-up. E.g. 'base(config)' or ' diff --git a/config_utilities/test/CMakeLists.txt b/config_utilities/test/CMakeLists.txt index 2f0061e..e313267 100644 --- a/config_utilities/test/CMakeLists.txt +++ b/config_utilities/test/CMakeLists.txt @@ -19,6 +19,7 @@ add_executable( tests/enums.cpp tests/external_registry.cpp tests/factory.cpp + tests/getters.cpp tests/inheritance.cpp tests/missing_fields.cpp tests/namespacing.cpp diff --git a/config_utilities/test/tests/getters.cpp b/config_utilities/test/tests/getters.cpp new file mode 100644 index 0000000..6e6f01e --- /dev/null +++ b/config_utilities/test/tests/getters.cpp @@ -0,0 +1,86 @@ +/** ----------------------------------------------------------------------------- + * Copyright (c) 2023 Massachusetts Institute of Technology. + * All Rights Reserved. + * + * AUTHORS: Lukas Schmid , Nathan Hughes + * AFFILIATION: MIT-SPARK Lab, Massachusetts Institute of Technology + * YEAR: 2023 + * LICENSE: BSD 3-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * -------------------------------------------------------------------------- */ + +#include + +#include "config_utilities/config.h" +#include "config_utilities/parsing/yaml.h" +#include "config_utilities/test/default_config.h" + +namespace config::test { + +struct GetterStruct { + int some_number; + std::string some_string; +}; + +void declare_config(GetterStruct& config) { + name("GetterStruct"); + field(config.some_number, "some_number"); + field(config.some_string, "some_string"); +} + +TEST(ConfigGetters, Getters) { + const std::string yaml_string = R"yaml( +some_number: 5 +some_string: "Hello" +)yaml"; + const auto node = YAML::Load(yaml_string); + + const auto config = fromYaml(node); + EXPECT_EQ(config.some_number, 5); + EXPECT_EQ(config.some_string, "Hello"); + + const auto fields = listFields(config); + EXPECT_EQ(fields.size(), 2); + EXPECT_EQ(fields[0], "some_number"); + EXPECT_EQ(fields[1], "some_string"); + + const auto number = getField(config, "some_number"); + EXPECT_TRUE(number.has_value()); + EXPECT_EQ(number.value(), 5); + + const auto string = getField(config, "some_string"); + EXPECT_TRUE(string.has_value()); + EXPECT_EQ(string.value(), "Hello"); + + const auto wrong = getField(config, "some_string"); + EXPECT_FALSE(wrong.has_value()); + + const auto wrong2 = getField(config, "non_existent_field"); + EXPECT_FALSE(wrong2.has_value()); +} + +} // namespace config::test From 7d380f39a46fd0e608edaa86159f9fedf01ac036 Mon Sep 17 00:00:00 2001 From: lschmid Date: Sun, 17 Nov 2024 19:04:39 -0500 Subject: [PATCH 3/3] small updates to PR --- .../include/config_utilities/config.h | 56 ----------- .../include/config_utilities/getters.h | 99 +++++++++++++++++++ .../config_utilities/internal/yaml_parser.h | 23 +++++ config_utilities/test/tests/getters.cpp | 8 ++ 4 files changed, 130 insertions(+), 56 deletions(-) diff --git a/config_utilities/include/config_utilities/config.h b/config_utilities/include/config_utilities/config.h index 65f195e..db8fe14 100644 --- a/config_utilities/include/config_utilities/config.h +++ b/config_utilities/include/config_utilities/config.h @@ -42,7 +42,6 @@ #include #include "config_utilities/internal/checks.h" -#include "config_utilities/internal/logger.h" #include "config_utilities/internal/namespacing.h" #include "config_utilities/internal/visitor.h" #include "config_utilities/traits.h" @@ -95,61 +94,6 @@ void field(T& field, const std::string& field_name, const std::string& unit = "" internal::Visitor::visitField(field, field_name, unit); } - -/** - * @brief Lists all the fields of the given configuration. - * - * This function retrieves metadata from the provided configuration object - * and extracts the names of all fields, returning them in a vector. - * - * @tparam ConfigT The type of the configuration. - * @param config The configuration object whose fields are to be listed. - * @return A vector containing the names of all fields in the configuration. - */ -template -std::vector listFields(const ConfigT& config) { - internal::MetaData data = internal::Visitor::getValues(config); - std::vector fields; - for (const auto& field_info : data.field_infos) { - fields.emplace_back(field_info.name); - } - return fields; -} - -/** - * @brief Retrieves the value of a specified field from the given configuration. - * - * This function searches for a field with the specified name in the provided - * configuration object and attempts to convert its value to the requested type. - * If the field is found and the conversion is successful, the value is returned - * as an optional. If the field is not found or the conversion fails, a warning - * is logged and an empty optional is returned. - * - * @tparam ConfigT The type of the configuration. - * @tparam T The type to which the field value should be converted. - * @param config The configuration object from which the field value is to be retrieved. - * @param field_name The name of the field whose value is to be retrieved. - * @return An optional containing the value of the field if found and successfully converted, - * otherwise an empty optional. - */ -template -std::optional getField(const ConfigT& config, const std::string& field_name) { - internal::MetaData data = internal::Visitor::getValues(config); - for (const auto& field_info : data.field_infos) { - if (field_info.name == field_name) { - try { - return field_info.value.as(); - } catch (const YAML::BadConversion& e) { - internal::Logger::logWarning("Field " + field_name + " could not be converted to the requested type."); - - return std::nullopt; - } - } - } - internal::Logger::logWarning("Field " + field_name + " not found in config."); - return std::nullopt; -} - /** * @brief Declare that this config inherits from a base config. Note that this call typically requires explicit * template declaration or explicit casting for argument dependent look-up. E.g. 'base(config)' or ' diff --git a/config_utilities/include/config_utilities/getters.h b/config_utilities/include/config_utilities/getters.h index e69de29..2fbd3d3 100644 --- a/config_utilities/include/config_utilities/getters.h +++ b/config_utilities/include/config_utilities/getters.h @@ -0,0 +1,99 @@ +/** ----------------------------------------------------------------------------- + * Copyright (c) 2023 Massachusetts Institute of Technology. + * All Rights Reserved. + * + * AUTHORS: Lukas Schmid , Nathan Hughes + * AFFILIATION: MIT-SPARK Lab, Massachusetts Institute of Technology + * YEAR: 2023 + * LICENSE: BSD 3-Clause + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * -------------------------------------------------------------------------- */ + +#include + +#include +#include +#include + +namespace config { + +/** + * @brief Lists all the fields of the given configuration. + * + * This function retrieves metadata from the provided configuration object + * and extracts the names of all fields, returning them in a vector. + * + * @note This function does not list fields of nested and sub-configurations. + * @tparam ConfigT The type of the configuration. + * @param config The configuration object whose fields are to be listed. + * @return A vector containing the names of all fields in the configuration. + */ +template +std::vector listFields(const ConfigT& config) { + internal::MetaData data = internal::Visitor::getValues(config); + std::vector fields; + for (const auto& field_info : data.field_infos) { + fields.emplace_back(field_info.name); + } + return fields; +} + +/** + * @brief Retrieves the value of a specified field from the given configuration. + * + * This function searches for a field with the specified name in the provided + * configuration object and attempts to convert its value to the requested type. + * If the field is found and the conversion is successful, the value is returned + * as an optional. If the field is not found or the conversion fails, a warning + * is logged and an empty optional is returned. + * + * @tparam ConfigT The type of the configuration. + * @tparam T The type to which the field value should be converted. + * @param config The configuration object from which the field value is to be retrieved. + * @param field_name The name of the field whose value is to be retrieved. + * @return An optional containing the value of the field if found and successfully converted, + * otherwise an empty optional. + */ +template +std::optional getField(const ConfigT& config, const std::string& field_name) { + internal::MetaData data = internal::Visitor::getValues(config); + for (const auto& field_info : data.field_infos) { + if (field_info.name == field_name) { + std::string error; + std::optional value = internal::YamlParser::fromYaml(field_info.value, &error); + if (!error.empty()) { + internal::Logger::logWarning("Field '" + field_name + + "' could not be converted to the requested type: " + error); + } + return value; + } + } + internal::Logger::logWarning("Field '" + field_name + "' not found in config."); + return std::nullopt; +} + +} // namespace config \ No newline at end of file diff --git a/config_utilities/include/config_utilities/internal/yaml_parser.h b/config_utilities/include/config_utilities/internal/yaml_parser.h index 43cf2b8..3b4a881 100644 --- a/config_utilities/include/config_utilities/internal/yaml_parser.h +++ b/config_utilities/include/config_utilities/internal/yaml_parser.h @@ -91,6 +91,29 @@ class YamlParser { return error.empty(); } + /** + * @brief Parse a single yaml node into a value. + * @param node The yaml node to parse the value from. + * @param error Optional: Where to store the error message if conversion fails. + */ + template + static std::optional fromYaml(const YAML::Node& node, std::string* error = nullptr) { + T value; + std::string err; + try { + fromYamlImpl(value, node, err); + } catch (const std::exception& e) { + err = std::string(e.what()); + } + if (!err.empty()) { + if (error) { + *error = err; + } + return std::nullopt; + } + return value; + } + /** * @brief Parse a C++ value to the yaml node. If the conversion fails, a warning is issued and the node is not * modified. diff --git a/config_utilities/test/tests/getters.cpp b/config_utilities/test/tests/getters.cpp index 6e6f01e..34687f4 100644 --- a/config_utilities/test/tests/getters.cpp +++ b/config_utilities/test/tests/getters.cpp @@ -33,11 +33,14 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * -------------------------------------------------------------------------- */ +#include "config_utilities/getters.h" + #include #include "config_utilities/config.h" #include "config_utilities/parsing/yaml.h" #include "config_utilities/test/default_config.h" +#include "config_utilities/test/utils.h" namespace config::test { @@ -76,11 +79,16 @@ some_string: "Hello" EXPECT_TRUE(string.has_value()); EXPECT_EQ(string.value(), "Hello"); + auto logger = TestLogger::create(); const auto wrong = getField(config, "some_string"); EXPECT_FALSE(wrong.has_value()); + EXPECT_EQ(logger->numMessages(), 1); + EXPECT_EQ(logger->lastMessage(), "Field 'some_string' could not be converted to the requested type: bad conversion"); const auto wrong2 = getField(config, "non_existent_field"); EXPECT_FALSE(wrong2.has_value()); + EXPECT_EQ(logger->numMessages(), 2); + EXPECT_EQ(logger->lastMessage(), "Field 'non_existent_field' not found in config."); } } // namespace config::test