|
1 | 1 | #pragma once |
2 | 2 |
|
| 3 | +#include <string> |
| 4 | +#include <type_traits> |
| 5 | +#include <unordered_map> |
| 6 | +#include <utility> |
| 7 | +#include <vector> |
| 8 | + |
3 | 9 | #include "error.h" |
4 | 10 | #include "optional.h" |
5 | 11 |
|
@@ -56,14 +62,94 @@ struct ConfigMetadata { |
56 | 62 | : name(n), value(std::move(v)), origin(orig), error(std::move(err)) {} |
57 | 63 | }; |
58 | 64 |
|
| 65 | +// Returns the final configuration value using the following |
| 66 | +// precedence order: environment > user code > default, and populates two maps: |
| 67 | +// 1. `telemetry_configs`: Records ALL configuration sources that were provided, |
| 68 | +// ordered from lowest to highest precedence. |
| 69 | +// 2. `metadata`: Records ONLY the winning configuration value (highest |
| 70 | +// precedence). Template Parameters: |
| 71 | +// Value: The type of the configuration value |
| 72 | +// Stringifier: Optional function type to convert Value to string |
| 73 | +// (defaults to std::nullptr_t, which uses string construction) |
| 74 | +// DefaultValue: Type of the fallback value (defaults to std::nullptr_t) |
| 75 | +// |
| 76 | +// Parameters: |
| 77 | +// from_env: Optional value from environment variables (highest precedence) |
| 78 | +// from_user: Optional value from user code (middle precedence) |
| 79 | +// telemetry_configs: Output map that will be populated with all config |
| 80 | +// sources found for this config_name, in precedence order |
| 81 | +// metadata: Output map that will be populated with the winning config value |
| 82 | +// for this config_name |
| 83 | +// config_name: The configuration parameter name identifier |
| 84 | +// fallback: Optional default value (lowest precedence). Pass nullptr to |
| 85 | +// indicate no default. |
| 86 | +// to_string_fn: Optional custom function to convert Value to string. |
| 87 | +// Required for non-string types. For string-like types, uses |
| 88 | +// default string construction if not provided. |
| 89 | +// |
| 90 | +// Returns: |
| 91 | +// The chosen configuration value based on precedence, or Value{} if no value |
| 92 | +// was provided. |
| 93 | +template <typename Value, typename Stringifier = std::nullptr_t, |
| 94 | + typename DefaultValue = std::nullptr_t> |
| 95 | +Value resolve_and_record_config( |
| 96 | + const Optional<Value>& from_env, const Optional<Value>& from_user, |
| 97 | + std::unordered_map<ConfigName, std::vector<ConfigMetadata>>* |
| 98 | + telemetry_configs, |
| 99 | + std::unordered_map<ConfigName, ConfigMetadata>* metadata, |
| 100 | + ConfigName config_name, DefaultValue fallback = nullptr, |
| 101 | + Stringifier to_string_fn = nullptr) { |
| 102 | + auto stringify = [&](const Value& v) -> std::string { |
| 103 | + if constexpr (!std::is_same_v<Stringifier, std::nullptr_t>) { |
| 104 | + return to_string_fn(v); // use provided function |
| 105 | + } else if constexpr (std::is_constructible_v<std::string, Value>) { |
| 106 | + return std::string(v); // default behaviour (works for string-like types) |
| 107 | + } else { |
| 108 | + static_assert(!std::is_same_v<Value, Value>, |
| 109 | + "Non-string types require a stringifier function"); |
| 110 | + return ""; // unreachable |
| 111 | + } |
| 112 | + }; |
| 113 | + |
| 114 | + std::vector<ConfigMetadata> telemetry_entries; |
| 115 | + Optional<Value> chosen_value; |
| 116 | + |
| 117 | + auto add_entry = [&](ConfigMetadata::Origin origin, const Value& val) { |
| 118 | + std::string val_str = stringify(val); |
| 119 | + telemetry_entries.emplace_back( |
| 120 | + ConfigMetadata{config_name, val_str, origin}); |
| 121 | + chosen_value = val; |
| 122 | + }; |
| 123 | + |
| 124 | + // Add DEFAULT entry if fallback was provided (detected by type) |
| 125 | + if constexpr (!std::is_same_v<DefaultValue, std::nullptr_t>) { |
| 126 | + add_entry(ConfigMetadata::Origin::DEFAULT, fallback); |
| 127 | + } |
| 128 | + |
| 129 | + if (from_user) { |
| 130 | + add_entry(ConfigMetadata::Origin::CODE, *from_user); |
| 131 | + } |
| 132 | + |
| 133 | + if (from_env) { |
| 134 | + add_entry(ConfigMetadata::Origin::ENVIRONMENT_VARIABLE, *from_env); |
| 135 | + } |
| 136 | + |
| 137 | + (*telemetry_configs)[config_name] = std::move(telemetry_entries); |
| 138 | + if (!(*telemetry_configs)[config_name].empty()) { |
| 139 | + (*metadata)[config_name] = (*telemetry_configs)[config_name].back(); |
| 140 | + } |
| 141 | + |
| 142 | + return chosen_value.value_or(Value{}); |
| 143 | +} |
| 144 | + |
59 | 145 | // Return a pair containing the configuration origin and value of a |
60 | 146 | // configuration value chosen from one of the specified `from_env`, |
61 | 147 | // `from_config`, and `fallback`. This function defines the relative precedence |
62 | 148 | // among configuration values originating from the environment, programmatic |
63 | 149 | // configuration, and default configuration. |
64 | 150 | template <typename Value, typename DefaultValue> |
65 | | -std::pair<ConfigMetadata::Origin, Value> pick(const Optional<Value> &from_env, |
66 | | - const Optional<Value> &from_user, |
| 151 | +std::pair<ConfigMetadata::Origin, Value> pick(const Optional<Value>& from_env, |
| 152 | + const Optional<Value>& from_user, |
67 | 153 | DefaultValue fallback) { |
68 | 154 | if (from_env) { |
69 | 155 | return {ConfigMetadata::Origin::ENVIRONMENT_VARIABLE, *from_env}; |
|
0 commit comments