mirror of
https://github.com/Icinga/icinga2.git
synced 2025-09-26 02:58:43 +02:00
Make ValueGenerator
more flexible & easy to use
This commit refactors the ValueGenerator class to be a template that can work with any container type. Previously, one has to manually take care of the used container by lazily iterating over it within a lambda. Now, the `ValueGenerator` class itself takes care of all the iteration, making it easier to use and less error-prone. The new base `Generator` class is required to allow the `JsonEncoder` to handle generators in a type-erased manner.
This commit is contained in:
parent
2063d2bdbc
commit
ccf4eebc3d
@ -10,39 +10,90 @@ namespace icinga
|
||||
{
|
||||
|
||||
/**
|
||||
* ValueGenerator is a class that defines a generator function type for producing Values on demand.
|
||||
* Abstract base class for generators that produce a sequence of Values.
|
||||
*
|
||||
* This class is used to create generator functions that can yield any values that can be represented by the
|
||||
* Icinga Value type. The generator function is exhausted when it returns `std::nullopt`, indicating that there
|
||||
* are no more values to produce. Subsequent calls to `Next()` will always return `std::nullopt` after exhaustion.
|
||||
* @note Any instance of a Generator should be treated as an @c Array -like object that produces its elements
|
||||
* on-the-fly.
|
||||
*
|
||||
* @ingroup base
|
||||
*/
|
||||
class ValueGenerator final : public Object
|
||||
class Generator : public Object
|
||||
{
|
||||
public:
|
||||
DECLARE_PTR_TYPEDEFS(Generator);
|
||||
|
||||
/**
|
||||
* Produces the next Value in the sequence.
|
||||
*
|
||||
* This method returns the next Value produced by the generator. If the generator is exhausted,
|
||||
* it returns std::nullopt for all subsequent calls to this method.
|
||||
*
|
||||
* @return The next Value in the sequence, or std::nullopt if the generator is exhausted.
|
||||
*/
|
||||
virtual std::optional<Value> Next() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* A generator that transforms elements of a container into Values using a provided transformation function.
|
||||
*
|
||||
* This class takes a container and a transformation function as input. It uses the transformation function
|
||||
* to convert each element of the container into a Value. The generator produces Values on-the-fly as they
|
||||
* are requested via the `Next()` method. If the transformation function returns `std::nullopt` for an element,
|
||||
* that element is skipped, and the generator continues to the next element in the container without its caller
|
||||
* being aware of it. The generator is exhausted when all elements of the container have been processed.
|
||||
*
|
||||
* @tparam Container The type of the container holding the elements to be transformed.
|
||||
*
|
||||
* @ingroup base
|
||||
*/
|
||||
template<typename Container>
|
||||
class ValueGenerator final : public Generator
|
||||
{
|
||||
public:
|
||||
DECLARE_PTR_TYPEDEFS(ValueGenerator);
|
||||
|
||||
/**
|
||||
* Generates a Value using the provided generator function.
|
||||
*
|
||||
* The generator function should return an `std::optional<Value>` which contains the produced Value or
|
||||
* `std::nullopt` when there are no more values to produce. After the generator function returns `std::nullopt`,
|
||||
* the generator is considered exhausted, and further calls to `Next()` will always return `std::nullopt`.
|
||||
*/
|
||||
using GenFunc = std::function<std::optional<Value>()>;
|
||||
// The type of elements in the container and the type to be passed to the transformation function.
|
||||
using ValueType = typename Container::value_type;
|
||||
|
||||
explicit ValueGenerator(GenFunc generator): m_Generator(std::move(generator))
|
||||
// The type of the transformation function that takes a ValueType and returns an optional Value.
|
||||
using TransFormFunc = std::function<std::optional<Value> (const ValueType&)>;
|
||||
|
||||
/**
|
||||
* Constructs a ValueGenerator with the given container and transformation function.
|
||||
*
|
||||
* The generator will iterate over the elements of the container, applying the transformation function
|
||||
* to each element to produce values on-the-fly. You must ensure that the container remains valid for
|
||||
* the lifetime of the generator.
|
||||
*
|
||||
* @param container The container holding the elements to be transformed.
|
||||
* @param generator The transformation function to convert elements into Values.
|
||||
*/
|
||||
ValueGenerator(Container& container, TransFormFunc generator)
|
||||
: m_It{container.begin()}, m_End{container.end()}, m_Func{std::move(generator)}
|
||||
{
|
||||
}
|
||||
|
||||
std::optional<Value> Next() const
|
||||
std::optional<Value> Next() override
|
||||
{
|
||||
return m_Generator();
|
||||
if (m_It == m_End) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto next = m_Func(*m_It);
|
||||
++m_It;
|
||||
|
||||
if (next == std::nullopt && m_It != m_End) {
|
||||
return Next();
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
private:
|
||||
GenFunc m_Generator; // The generator function that produces Values.
|
||||
using Iterator = typename Container::iterator;
|
||||
Iterator m_It; // Current iterator position.
|
||||
Iterator m_End; // End iterator position.
|
||||
|
||||
TransFormFunc m_Func; // The transformation function.
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace icinga
|
||||
|
@ -73,7 +73,7 @@ void JsonEncoder::Encode(const Value& value, boost::asio::yield_context* yc)
|
||||
EncodeObject(static_pointer_cast<Dictionary>(obj), extractor, yc);
|
||||
} else if (type == Array::TypeInstance) {
|
||||
EncodeArray(static_pointer_cast<Array>(obj), yc);
|
||||
} else if (auto gen(dynamic_pointer_cast<ValueGenerator>(obj)); gen) {
|
||||
} else if (auto gen(dynamic_pointer_cast<Generator>(obj)); gen) {
|
||||
EncodeValueGenerator(gen, yc);
|
||||
} else {
|
||||
// Some other non-serializable object type!
|
||||
@ -126,7 +126,7 @@ void JsonEncoder::EncodeArray(const Array::Ptr& array, boost::asio::yield_contex
|
||||
* @param yc The optional yield context for asynchronous operations. If provided, it allows the encoder
|
||||
* to flush the output stream safely when it has not acquired any object lock on the parent containers.
|
||||
*/
|
||||
void JsonEncoder::EncodeValueGenerator(const ValueGenerator::Ptr& generator, boost::asio::yield_context* yc)
|
||||
void JsonEncoder::EncodeValueGenerator(const Generator::Ptr& generator, boost::asio::yield_context* yc)
|
||||
{
|
||||
BeginContainer('[');
|
||||
bool isEmpty = true;
|
||||
|
@ -73,7 +73,7 @@ public:
|
||||
|
||||
private:
|
||||
void EncodeArray(const Array::Ptr& array, boost::asio::yield_context* yc);
|
||||
void EncodeValueGenerator(const ValueGenerator::Ptr& generator, boost::asio::yield_context* yc);
|
||||
void EncodeValueGenerator(const Generator::Ptr& generator, boost::asio::yield_context* yc);
|
||||
|
||||
template<typename Iterable, typename ValExtractor>
|
||||
void EncodeObject(const Iterable& container, const ValExtractor& extractor, boost::asio::yield_context* yc);
|
||||
|
@ -209,15 +209,7 @@ bool ObjectQueryHandler::HandleRequest(
|
||||
std::unordered_map<Type*, std::pair<bool, std::unique_ptr<Expression>>> typePermissions;
|
||||
std::unordered_map<Object*, bool> objectAccessAllowed;
|
||||
|
||||
auto it = objs.begin();
|
||||
auto generatorFunc = [&]() -> std::optional<Value> {
|
||||
if (it == objs.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ConfigObject::Ptr obj = *it;
|
||||
++it;
|
||||
|
||||
auto generatorFunc = [&](const ConfigObject::Ptr& obj) -> std::optional<Value> {
|
||||
DictionaryData result1{
|
||||
{ "name", obj->GetName() },
|
||||
{ "type", obj->GetReflectionType()->GetName() }
|
||||
@ -330,7 +322,7 @@ bool ObjectQueryHandler::HandleRequest(
|
||||
response.set(http::field::content_type, "application/json");
|
||||
response.StartStreaming();
|
||||
|
||||
Dictionary::Ptr results = new Dictionary{{"results", new ValueGenerator{generatorFunc}}};
|
||||
Dictionary::Ptr results = new Dictionary{{"results", new ValueGenerator{objs, generatorFunc}}};
|
||||
results->Freeze();
|
||||
|
||||
bool pretty = HttpUtility::GetLastParameter(params, "pretty");
|
||||
|
@ -18,14 +18,10 @@ BOOST_AUTO_TEST_SUITE(base_json)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(encode)
|
||||
{
|
||||
auto generate = []() -> std::optional<Value> {
|
||||
static int count = 0;
|
||||
if (++count == 4) {
|
||||
count = 0;
|
||||
return std::nullopt;
|
||||
}
|
||||
return Value(count);
|
||||
};
|
||||
int emptyGenCounter = 0;
|
||||
std::vector<int> empty;
|
||||
std::vector<int> vec{1, 2, 3};
|
||||
auto generate = [](int count) -> std::optional<Value> { return Value(count); };
|
||||
|
||||
Dictionary::Ptr input (new Dictionary({
|
||||
{ "array", new Array({ new Namespace() }) },
|
||||
@ -42,8 +38,17 @@ BOOST_AUTO_TEST_CASE(encode)
|
||||
{ "string", "LF\nTAB\tAUml\xC3\xA4Ill\xC3" },
|
||||
{ "true", true },
|
||||
{ "uint", 23u },
|
||||
{ "generator", new ValueGenerator(generate) },
|
||||
{ "empty_generator", new ValueGenerator([]() -> std::optional<Value> { return std::nullopt; }) },
|
||||
{ "generator", new ValueGenerator{vec, generate} },
|
||||
{
|
||||
"empty_generator",
|
||||
new ValueGenerator{
|
||||
empty,
|
||||
[&emptyGenCounter](int) -> std::optional<Value> {
|
||||
emptyGenCounter++;
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
String output (R"EOF({
|
||||
@ -72,15 +77,20 @@ BOOST_AUTO_TEST_CASE(encode)
|
||||
|
||||
auto got(JsonEncode(input, true));
|
||||
BOOST_CHECK_EQUAL(output, got);
|
||||
BOOST_CHECK_EQUAL(emptyGenCounter, 0); // Ensure the transformation function was never invoked.
|
||||
input->Set("generator", new ValueGenerator{vec, generate});
|
||||
|
||||
std::ostringstream oss;
|
||||
JsonEncode(input, oss, true);
|
||||
BOOST_CHECK_EQUAL(emptyGenCounter, 0); // Ensure the transformation function was never invoked.
|
||||
BOOST_CHECK_EQUAL(output, oss.str());
|
||||
|
||||
boost::algorithm::replace_all(output, " ", "");
|
||||
boost::algorithm::replace_all(output, "Objectoftype'Function'", "Object of type 'Function'");
|
||||
boost::algorithm::replace_all(output, "\n", "");
|
||||
|
||||
input->Set("generator", new ValueGenerator{vec, generate});
|
||||
BOOST_CHECK_EQUAL(emptyGenCounter, 0); // Ensure the transformation function was never invoked.
|
||||
BOOST_CHECK(JsonEncode(input, false) == output);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user