Skip to content

Move Configuration Value Look Ups to Dedicated Namespace #17

Description

@MattToast

At time of writing, the default IClinet::wait_for_bytes method has the following block of code for selecting the polling interval

radex/src/cpp/client.cpp

Lines 63 to 66 in a67478b

const auto poll_interval = std::chrono::milliseconds(
(poll_interval_s = std::getenv(RADEX_POLL_INTERVAL_VAR.c_str()))
? std::stoul(poll_interval_s)
: RADEX_DEFAULT_POLL_INTERVAL_MILLISECONDS);

This is functional, but problematic for a couple of reasons:

  1. It embeds configuration into buisness logic of the library, crossing concerns
  2. It becomes difficult for us to add a new lookup location (e.g. config file) for polling interval
  3. Everywhere that needs to uses a config must contain similar logic which could become problematic if we find a bug in how we retrieve a value, causing us to need to visit many touch points

I purpose adding a radex::configuration namespace specifically for dealing with the look up of these values. A VERY ROUGH implementation of what I'm thinking might look something like this:

namespace radex::configuration {

template <typename>
inline constexpr bool unsupported_type_v = false;

template <typename T>
class ILookUp {
public:
    std::optional<T> get() const noexcept = 0;
};

template <typename T>
class FromEnvironment: public ILookUp {
private:
    std::string env_var;
public:
    FromEnvironment(std::string env_var): env_var(std::move(env_var)) {}

    std::optional<T> get() const noexcept override {
        const char *val = std::getenv(env_var.c_str());
        if (val == nullptr) return std::nullopt;

        if constexpr (std::is_same_v<T, std::chrono::milliseconds>) {
            const unsigned long num = std std::stoul(val);
            return std::chrono::milliseconds(num);
        }
        static_assert(unsupported_type_v<T>, "Don't know how to parse that type");
    }
};

template <typename T>
class ConfigValueLookUp {
private:
    std::vector<ILookUp<T>> look_up_locations;
    T fallback;
public:
    ConfigValueLookUp(std::vector<ILookUp<T>> look_up_locations, T fallback)
        : look_up_locations(std::move(look_up_locations))
        , fallback {fallback} {};
    T find() noexcept {
        for (auto const& loc: look_up_locations) {
            const T val = loc.get();
            if (loc) return *loc;
        }
        return fallback;
    }
};

static inline ConfigValueLookUp<std::chrono::milliseconds>
RADEX_DEFAULT_POLL_INTERVAL {
    {FromEnvironment("RADEX_POLL_INTERVAL")}, 100ms };
}

I cannot stress how much of that code block is speculative pseudo-code 😅

But hopefully it sends a good idea of what I'm thinking. Anything that moves look up of config variables out of business logic is sufficient!

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions