cppfig 0.1.0
Modern C++20 compile-time type-safe configuration library
Loading...
Searching...
No Matches
configuration.h
Go to the documentation of this file.
1#pragma once
2
3#include <cstdlib>
4#include <filesystem>
5#include <string>
6#include <string_view>
7
8#include "cppfig/conf.h"
9#include "cppfig/diff.h"
10#include "cppfig/interface.h"
11#include "cppfig/logging.h"
12#include "cppfig/serializer.h"
13#include "cppfig/setting.h"
15#include "cppfig/traits.h"
16#include "cppfig/value.h"
17
18namespace cppfig {
19
81template <typename Schema, Serializer SerializerT = ConfSerializer, typename ThreadPolicy = SingleThreadedPolicy>
82class Configuration : public IConfigurationProvider<Configuration<Schema, SerializerT, ThreadPolicy>, Schema>,
84public:
85 using serializer_type = SerializerT;
87
91 explicit Configuration(std::string file_path)
92 : file_path_(std::move(file_path))
93 , file_values_(Value::Object())
94 {
95 BuildDefaults();
96 }
97
106 template <IsSetting S>
107 requires(Schema::template has_setting<S>)
108 [[nodiscard]] auto GetImpl() const -> typename S::value_type
109 {
110 using value_type = typename S::value_type;
111
112 // 1. Check environment variable (no lock needed — no mutable state accessed)
113 constexpr auto env_override = GetEnvOverride<S>();
114 if constexpr (!env_override.empty()) {
115 if (const char* env_value = std::getenv(std::string(env_override).c_str())) {
116 auto parsed = ConfigTraits<value_type>::FromString(env_value);
117 if (parsed.has_value()) {
118 return *parsed;
119 }
120 Logger::WarnF("Failed to parse environment variable %.*s='%s', using fallback",
121 static_cast<int>(env_override.size()), env_override.data(), env_value);
122 }
123 }
124
125 // 2. Check file value (shared lock — concurrent readers allowed)
126 {
127 typename ThreadPolicy::shared_lock lock(mutex_);
128 auto file_result = file_values_.GetAtPath(S::path);
129 if (file_result.ok()) {
130 auto parsed = ConfigTraits<value_type>::Deserialize(*file_result);
131 if (parsed.has_value()) {
132 return *parsed;
133 }
134 Logger::WarnF("Failed to parse file value for '%.*s', using default",
135 static_cast<int>(S::path.size()), S::path.data());
136 }
137 }
138
139 // 3. Return default value (immutable after construction — no lock needed)
140 return S::default_value();
141 }
142
147 template <IsSetting S>
148 requires(Schema::template has_setting<S>)
149 auto SetImpl(typename S::value_type value) -> Status
150 {
151 using value_type = typename S::value_type;
152
153 // Validate the value *before* acquiring the exclusive lock
154 auto validator = GetSettingValidator<S>();
155 auto validation = validator(value);
156 if (!validation) {
157 return InvalidArgumentError(validation.error_message);
158 }
159
160 // Set the value under exclusive lock
161 typename ThreadPolicy::unique_lock lock(mutex_);
162 auto serialized = ConfigTraits<value_type>::Serialize(value);
163 file_values_.SetAtPath(S::path, serialized);
164
165 return OkStatus();
166 }
167
172 [[nodiscard]] auto LoadImpl() -> Status
173 {
174 typename ThreadPolicy::unique_lock lock(mutex_);
175 return LoadUnlocked();
176 }
177
182 [[nodiscard]] auto SaveImpl() const -> Status
183 {
184 typename ThreadPolicy::shared_lock lock(mutex_);
185 return SaveUnlocked();
186 }
187
191 [[nodiscard]] auto DiffImpl() const -> ConfigDiff
192 {
193 typename ThreadPolicy::shared_lock lock(mutex_);
194 return DiffFileFromDefaults(defaults_, file_values_);
195 }
196
200 [[nodiscard]] auto ValidateAllImpl() const -> Status
201 {
202 typename ThreadPolicy::shared_lock lock(mutex_);
203 return ValidateAllUnlocked();
204 }
205
209 [[nodiscard]] auto GetFilePathImpl() const -> std::string_view { return file_path_; }
210
215 [[nodiscard]] auto GetFileValues() const -> const Value& { return file_values_; }
216
221 [[nodiscard]] auto GetDefaults() const -> const Value& { return defaults_; }
222
223 [[nodiscard]] auto Load() -> Status override { return LoadImpl(); }
224
225 [[nodiscard]] auto Save() const -> Status override { return SaveImpl(); }
226
227 [[nodiscard]] auto GetFilePath() const -> std::string_view override { return GetFilePathImpl(); }
228
229 [[nodiscard]] auto ValidateAll() const -> Status override { return ValidateAllImpl(); }
230
231 [[nodiscard]] auto GetDiffString() const -> std::string override { return DiffImpl().ToString(); }
232
233private:
235 [[nodiscard]] auto LoadUnlocked() -> Status
236 {
237 namespace fs = std::filesystem;
238
239 if (!fs::exists(file_path_)) {
240 // File doesn't exist - create with defaults
241 Logger::InfoF("Configuration file '%s' not found, creating with defaults", file_path_.c_str());
242 file_values_ = defaults_;
243 return SaveUnlocked();
244 }
245
246 // Load existing file
247 auto result = ReadFile<SerializerT>(file_path_);
248 if (!result.ok()) {
249 return result.status();
250 }
251
252 file_values_ = *result;
253
254 // Check for schema migration (new settings in defaults not in file)
255 auto diff = DiffDefaultsFromFile(defaults_, file_values_);
256 auto added = diff.Added();
257
258 if (!added.empty()) {
259 Logger::Warn("New settings detected in schema, adding to configuration file:");
260 for (const auto& entry : added) {
261 Logger::WarnF(" - %s = %s", entry.path.c_str(), entry.new_value.c_str());
262 // Copy the default value directly from the defaults tree
263 auto default_val = defaults_.GetAtPath(entry.path);
264 if (default_val.ok()) {
265 file_values_.SetAtPath(entry.path, *default_val);
266 }
267 }
268
269 // Save the updated configuration
270 auto save_status = SaveUnlocked();
271 if (!save_status.ok()) {
272 Logger::ErrorF("Failed to save migrated configuration: %s",
273 std::string(save_status.message()).c_str());
274 return save_status;
275 }
276 }
277
278 return OkStatus();
279 }
280
282 [[nodiscard]] auto SaveUnlocked() const -> Status
283 {
284 namespace fs = std::filesystem;
285
286 // Create parent directories if needed
287 fs::path path(file_path_);
288 if (path.has_parent_path()) {
289 std::error_code error_code;
290 fs::create_directories(path.parent_path(), error_code);
291 if (error_code) {
292 return InternalError("Failed to create directory: " + error_code.message());
293 }
294 }
295
296 return WriteFile<SerializerT>(file_path_, file_values_);
297 }
298
300 [[nodiscard]] auto ValidateAllUnlocked() const -> Status
301 {
302 Status status = OkStatus();
303
304 Schema::ForEachSetting([this, &status]<typename S>() {
305 if (!status.ok()) {
306 return; // Stop on first error
307 }
308
309 using value_type = typename S::value_type;
310 auto file_result = file_values_.GetAtPath(S::path);
311
312 if (file_result.ok()) {
313 auto parsed = ConfigTraits<value_type>::Deserialize(*file_result);
314 if (parsed.has_value()) {
315 auto validator = GetSettingValidator<S>();
316 auto validation = validator(*parsed);
317 if (!validation) {
318 status = InvalidArgumentError(std::string(S::path) + ": " + validation.error_message);
319 }
320 }
321 }
322 });
323
324 return status;
325 }
326
327 void BuildDefaults()
328 {
329 defaults_ = Value::Object();
330
331 Schema::ForEachSetting([this]<typename S>() {
332 using value_type = typename S::value_type;
333 auto serialized = ConfigTraits<value_type>::Serialize(S::default_value());
334 defaults_.SetAtPath(S::path, serialized);
335 });
336 }
337
338 std::string file_path_;
339 Value file_values_;
340 Value defaults_;
341 mutable typename ThreadPolicy::mutex_type mutex_;
342};
343
344} // namespace cppfig
Result of comparing two configurations.
Definition diff.h:39
Main configuration manager.
Definition configuration.h:83
auto GetFileValues() const -> const Value &
Returns the current file values.
Definition configuration.h:215
auto Load() -> Status override
Loads configuration from the file.
Definition configuration.h:223
auto GetImpl() const -> typename S::value_type
Gets the value for a setting type.
Definition configuration.h:108
auto SetImpl(typename S::value_type value) -> Status
Sets the value for a setting type.
Definition configuration.h:149
Configuration(std::string file_path)
Creates a configuration manager with a file path.
Definition configuration.h:91
auto LoadImpl() -> Status
Loads configuration from the file.
Definition configuration.h:172
SerializerT serializer_type
Definition configuration.h:85
auto GetDefaults() const -> const Value &
Returns the default values.
Definition configuration.h:221
auto GetFilePathImpl() const -> std::string_view
Returns the file path.
Definition configuration.h:209
auto GetDiffString() const -> std::string override
Gets a string representation of the diff.
Definition configuration.h:231
auto DiffImpl() const -> ConfigDiff
Returns the diff between file values and defaults.
Definition configuration.h:191
auto ValidateAll() const -> Status override
Validates all current values.
Definition configuration.h:229
auto GetFilePath() const -> std::string_view override
Returns the file path.
Definition configuration.h:227
auto Save() const -> Status override
Saves the current configuration to the file.
Definition configuration.h:225
auto SaveImpl() const -> Status
Saves the current configuration to the file.
Definition configuration.h:182
auto ValidateAllImpl() const -> Status
Validates all current values against their validators.
Definition configuration.h:200
Virtual interface for type-erased configuration access.
Definition interface.h:89
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:23
A self-contained, recursive value type for configuration data.
Definition value.h:26
auto GetAtPath(std::string_view path) const -> StatusOr< Value >
Gets a value at a dot-separated path.
Definition value.h:265
void SetAtPath(std::string_view path, const Value &value)
Sets a value at a dot-separated path, creating intermediate objects.
Definition value.h:285
static auto Object() -> Value
Creates an empty object value.
Definition value.h:133
C++20 compile-time type-safe configuration library.
Definition conf.h:13
auto DiffDefaultsFromFile(const Value &defaults, const Value &file_values) -> ConfigDiff
Compares defaults against file configuration.
Definition diff.h:168
auto InternalError(std::string message) -> Status
Returns an Internal error status.
Definition status.h:67
auto DiffFileFromDefaults(const Value &defaults, const Value &file_values) -> ConfigDiff
Compares file configuration against defaults.
Definition diff.h:157
auto OkStatus() -> Status
Returns an OK status.
Definition status.h:52
auto InvalidArgumentError(std::string message) -> Status
Returns an InvalidArgument error status.
Definition status.h:61
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.