Skip to content

Commit

Permalink
Feature/getters (#36)
Browse files Browse the repository at this point in the history
* add getters file

* getters (#34)

* small updates to PR

---------

Co-authored-by: Florian Tschopp <[email protected]>
  • Loading branch information
Schmluk and floriantschopp authored Feb 19, 2025
1 parent b826c7c commit db0f4b2
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 0 deletions.
99 changes: 99 additions & 0 deletions config_utilities/include/config_utilities/getters.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/** -----------------------------------------------------------------------------
* Copyright (c) 2023 Massachusetts Institute of Technology.
* All Rights Reserved.
*
* AUTHORS: Lukas Schmid <[email protected]>, Nathan Hughes <[email protected]>
* 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 <vector>

#include <config_utilities/internal/logger.h>
#include <config_utilities/internal/visitor.h>
#include <config_utilities/internal/yaml_parser.h>

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 <typename ConfigT>
std::vector<std::string> listFields(const ConfigT& config) {
internal::MetaData data = internal::Visitor::getValues(config);
std::vector<std::string> 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 <typename ConfigT, typename T>
std::optional<T> 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<T> value = internal::YamlParser::fromYaml<T>(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
23 changes: 23 additions & 0 deletions config_utilities/include/config_utilities/internal/yaml_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename T>
static std::optional<T> 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.
Expand Down
1 change: 1 addition & 0 deletions config_utilities/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,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
Expand Down
94 changes: 94 additions & 0 deletions config_utilities/test/tests/getters.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/** -----------------------------------------------------------------------------
* Copyright (c) 2023 Massachusetts Institute of Technology.
* All Rights Reserved.
*
* AUTHORS: Lukas Schmid <[email protected]>, Nathan Hughes <[email protected]>
* 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 "config_utilities/getters.h"

#include <gtest/gtest.h>

#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 {

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<GetterStruct>(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<GetterStruct, int>(config, "some_number");
EXPECT_TRUE(number.has_value());
EXPECT_EQ(number.value(), 5);

const auto string = getField<GetterStruct, std::string>(config, "some_string");
EXPECT_TRUE(string.has_value());
EXPECT_EQ(string.value(), "Hello");

auto logger = TestLogger::create();
const auto wrong = getField<GetterStruct, int>(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<GetterStruct, std::string>(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

0 comments on commit db0f4b2

Please sign in to comment.