87template <
typename Schema, Serializer SerializerT = ConfSerializer,
typename ThreadPolicy = SingleThreadedPolicy>
97 : file_path_(std::move(file_path))
98 , file_values_(
Value::Object())
111 template <IsSetting S>
112 requires(Schema::template has_setting<S>)
113 [[nodiscard]]
auto GetImpl()
const ->
typename S::value_type
115 using value_type =
typename S::value_type;
118 constexpr auto env_override = GetEnvOverride<S>();
119 if constexpr (!env_override.empty()) {
120 if (
const char* env_value = std::getenv(std::string(env_override).c_str())) {
122 if (!parsed.has_value()) {
123 Logger::WarnF(
"Failed to parse environment variable %.*s='%s', using fallback",
124 static_cast<int>(env_override.size()), env_override.data(), env_value);
129 auto validation = GetSettingValidator<S>()(*parsed);
133 Logger::WarnF(
"Environment variable %.*s='%s' is invalid (%s), using fallback",
134 static_cast<int>(env_override.size()), env_override.data(), env_value,
135 validation.error_message.c_str());
142 typename ThreadPolicy::shared_lock lock(mutex_);
143 auto file_result = file_values_.
GetAtPath(S::path);
144 if (file_result.ok()) {
146 if (parsed.has_value()) {
149 Logger::WarnF(
"Failed to parse file value for '%.*s', using default",
150 static_cast<int>(S::path.size()), S::path.data());
155 return S::default_value();
163 template <IsSetting S>
164 requires(Schema::template has_setting<S>)
167 constexpr auto env_override = GetEnvOverride<S>();
168 if constexpr (env_override.empty()) {
172 return std::getenv(std::string(env_override).c_str()) !=
nullptr;
184 template <IsSetting S>
185 requires(Schema::template has_setting<S>)
188 using value_type =
typename S::value_type;
191 auto validator = GetSettingValidator<S>();
192 auto validation = validator(value);
198 constexpr auto env_override = GetEnvOverride<S>();
199 if constexpr (!env_override.empty()) {
200 if (IsOverriddenImpl<S>()) {
201 Logger::WarnF(
"Set('%.*s') stored, but %.*s is overriding it and Get will return the override",
202 static_cast<int>(S::path.size()), S::path.data(),
203 static_cast<int>(env_override.size()), env_override.data());
208 typename ThreadPolicy::unique_lock lock(mutex_);
210 file_values_.
SetAtPath(S::path, serialized);
221 typename ThreadPolicy::unique_lock lock(mutex_);
222 return LoadUnlocked();
233 typename ThreadPolicy::unique_lock lock(mutex_);
234 return SaveUnlocked();
242 typename ThreadPolicy::shared_lock lock(mutex_);
251 typename ThreadPolicy::shared_lock lock(mutex_);
252 return ValidateAllUnlocked();
269 typename ThreadPolicy::shared_lock lock(mutex_);
281 [[nodiscard]]
auto LoadUnlocked() ->
Status
283 namespace fs = std::filesystem;
285 if (!fs::exists(file_path_)) {
287 Logger::InfoF(
"Configuration file '%s' not found, creating with defaults", file_path_.c_str());
288 file_values_ = defaults_;
289 auto save_status = SaveUnlocked();
290 if (!save_status.ok()) {
293 return ValidateFileValuesUnlocked();
297 auto result = ReadFile<SerializerT>(file_path_);
299 return result.status();
302 file_values_ = *result;
303 CoerceToSchemaTypes();
307 auto added = diff.Added();
309 if (!added.empty()) {
310 Logger::Warn(
"New settings detected in schema, adding to configuration file:");
311 for (
const auto& entry : added) {
312 Logger::WarnF(
" - %s = %s", entry.path.c_str(), entry.new_value.c_str());
314 auto default_val = defaults_.
GetAtPath(entry.path);
315 if (default_val.ok()) {
316 file_values_.
SetAtPath(entry.path, *default_val);
321 auto save_status = SaveUnlocked();
322 if (!save_status.ok()) {
324 std::string(save_status.message()).c_str());
332 return ValidateFileValuesUnlocked();
336 [[nodiscard]]
auto SaveUnlocked() const -> Status
338 namespace fs = std::filesystem;
341 fs::path path(file_path_);
342 if (path.has_parent_path()) {
343 std::error_code error_code;
344 fs::create_directories(path.parent_path(), error_code);
346 return InternalError(
"Failed to create directory: " + error_code.message());
350 return WriteFile<SerializerT>(file_path_, file_values_);
357 [[nodiscard]]
auto ValidateFileValuesUnlocked() const -> Status
361 Schema::ForEachSetting([
this, &status]<
typename S>() {
366 using value_type =
typename S::value_type;
367 auto file_result = file_values_.
GetAtPath(S::path);
368 if (!file_result.ok()) {
373 if (!parsed.has_value()) {
377 auto validation = GetSettingValidator<S>()(*parsed);
392 [[nodiscard]]
auto ValidateAllUnlocked() const -> Status
394 Status status = ValidateEnvOverridesUnlocked();
398 return ValidateFileValuesUnlocked();
402 [[nodiscard]]
auto ValidateEnvOverridesUnlocked() const -> Status
406 Schema::ForEachSetting([&status]<
typename S>() {
411 constexpr auto env_override = GetEnvOverride<S>();
412 if constexpr (!env_override.empty()) {
413 const char* env_value = std::getenv(std::string(env_override).c_str());
414 if (env_value ==
nullptr) {
418 using value_type =
typename S::value_type;
420 if (!parsed.has_value()) {
421 status =
InvalidArgumentError(std::string(env_override) +
": cannot parse '" + env_value +
"' as a value for '" + std::string(S::path) +
"'");
425 auto validation = GetSettingValidator<S>()(*parsed);
447 void CoerceToSchemaTypes()
449 Schema::ForEachSetting([
this]<
typename S>() {
450 using value_type =
typename S::value_type;
452 auto file_result = file_values_.
GetAtPath(S::path);
453 if (!file_result.ok()) {
460 auto text = file_result->TryToText();
461 if (!text.has_value()) {
466 if (parsed.has_value()) {
476 Schema::ForEachSetting([
this]<
typename S>() {
477 using value_type =
typename S::value_type;
479 defaults_.
SetAtPath(S::path, serialized);
483 std::string file_path_;
486 mutable typename ThreadPolicy::mutex_type mutex_;
Result of comparing two configurations.
Definition diff.h:40
Main configuration manager.
Definition configuration.h:88
auto IsOverriddenImpl() const -> bool
Reports whether an environment variable is overriding a setting.
Definition configuration.h:165
auto GetImpl() const -> typename S::value_type
Gets the value for a setting type.
Definition configuration.h:113
auto SetImpl(typename S::value_type value) -> Status
Sets the value for a setting type.
Definition configuration.h:186
Configuration(std::string file_path)
Creates a configuration manager with a file path.
Definition configuration.h:96
auto LoadImpl() -> Status
Loads configuration from the file.
Definition configuration.h:219
SerializerT serializer_type
Definition configuration.h:90
auto GetFilePathImpl() const -> std::string_view
Returns the file path.
Definition configuration.h:258
auto DiffImpl() const -> ConfigDiff
Returns the diff between file values and defaults.
Definition configuration.h:240
auto GetFileValues() const -> Value
Returns a snapshot of the current file values.
Definition configuration.h:267
auto GetDefaults() const -> Value
Returns a snapshot of the default values.
Definition configuration.h:277
auto SaveImpl() const -> Status
Saves the current configuration to the file.
Definition configuration.h:231
auto ValidateAllImpl() const -> Status
Validates all current values against their validators.
Definition configuration.h:249
CRTP base class for configuration providers.
Definition interface.h:20
static void WarnF(const char *format, Args... args)
Logs a formatted warning message to stderr.
Definition logging.h:62
static void ErrorF(const char *format, Args... args)
Logs a formatted error message to stderr.
Definition logging.h:74
static void InfoF(const char *format, Args... args)
Logs a formatted info message to stdout.
Definition logging.h:50
static void Warn(std::string_view message)
Logs a warning message to stderr.
Definition logging.h:21
A lightweight status object carrying an error code and message.
Definition status.h:25
A self-contained, recursive value type for configuration data.
Definition value.h:32
auto GetAtPath(std::string_view path) const -> StatusOr< Value >
Gets a value at a dot-separated path.
Definition value.h:442
void SetAtPath(std::string_view path, const Value &value)
Sets a value at a dot-separated path, creating intermediate objects.
Definition value.h:466
static auto Object() -> Value
Creates an empty object value.
Definition value.h:139
C++20 compile-time type-safe configuration library.
Definition conf.h:17
auto DiffDefaultsFromFile(const Value &defaults, const Value &file_values) -> ConfigDiff
Compares defaults against file configuration.
Definition diff.h:169
auto InternalError(std::string message) -> Status
Returns an Internal error status.
Definition status.h:69
auto DiffFileFromDefaults(const Value &defaults, const Value &file_values) -> ConfigDiff
Compares file configuration against defaults.
Definition diff.h:158
auto OkStatus() -> Status
Returns an OK status.
Definition status.h:54
auto InvalidArgumentError(std::string message) -> Status
Returns an InvalidArgument error status.
Definition status.h:63
static auto Deserialize(const Value &value) -> std::optional< T >=delete
Deserializes a value from a Value node.
static auto FromString(std::string_view str) -> std::optional< T >=delete
Parses a value from a string (e.g., from environment variables).
static auto Serialize(const T &value) -> Value=delete
Serializes a value to a Value node.