plugify 1.2.8
Loading...
Searching...
No Matches
config.hpp
1#pragma once
2
3#include <any>
4#include <filesystem>
5#include <optional>
6#include <string>
7#include <unordered_set>
8
9#include "plugify/logger.hpp"
10#include "plugify/platform_ops.hpp"
11#include "plugify/types.hpp"
12
13namespace plugify {
14 // Update mode for explicit control
15 enum class UpdateMode {
16 Manual, // User calls Update() manually
17 BackgroundThread, // Automatic updates in background thread
18 Callback // User provides update callback
19 };
20
21 // Configuration source priority
22 enum class ConfigSource {
23 Default, // Default values
24 File, // Loaded from file
25 Builder, // Set via builder methods
26 Override // Explicit overrides (highest priority)
27 };
28
29 struct Config {
30 // Track source priority for each field group
32 ConfigSource paths = ConfigSource::Default;
33 ConfigSource loading = ConfigSource::Default;
34 ConfigSource runtime = ConfigSource::Default;
35 ConfigSource security = ConfigSource::Default;
36 ConfigSource logging = ConfigSource::Default;
37 } _sources;
38
39 // Paths configuration
40 struct Paths {
41 std::filesystem::path baseDir;
42 std::filesystem::path extensionsDir = "extensions";
43 std::filesystem::path configsDir = "configs";
44 std::filesystem::path dataDir = "data";
45 std::filesystem::path logsDir = "logs";
46 std::filesystem::path cacheDir = "cache";
47
48 // Check if paths have non-default values
49 bool HasCustomExtensionsDir() const {
50 return extensionsDir != "extensions";
51 }
52
53 bool HasCustomConfigsDir() const {
54 return configsDir != "configs";
55 }
56
57 bool HasCustomDataDir() const {
58 return dataDir != "data";
59 }
60
61 bool HasCustomLogsDir() const {
62 return logsDir != "logs";
63 }
64
65 bool HasCustomCacheDir() const {
66 return cacheDir != "cache";
67 }
68
69 void ResolveRelativePaths() {
70 auto makeAbsolute = [this](std::filesystem::path& path) {
71 if (!path.empty() && path.is_relative() && !baseDir.empty()) {
72 path = (baseDir / path).lexically_normal();
73 }
74 };
75
76 makeAbsolute(extensionsDir);
77 makeAbsolute(configsDir);
78 makeAbsolute(dataDir);
79 makeAbsolute(logsDir);
80 makeAbsolute(cacheDir);
81 }
82 } paths;
83
84 // Loading configuration
85 struct Loading {
86 bool preferOwnSymbols = false;
87 size_t maxConcurrentLoads = 4;
88 std::chrono::milliseconds loadTimeout{ 500 };
89 std::chrono::milliseconds exportTimeout{ 100 };
90 std::chrono::milliseconds startTimeout{ 250 };
91
92 // Check if values are non-default
93 bool HasCustomPreferOwnSymbols() const {
94 return preferOwnSymbols != false;
95 }
96
97 bool HasCustomMaxConcurrentLoads() const {
98 return maxConcurrentLoads != 4;
99 }
100
101 bool HasCustomLoadTimeout() const {
102 return loadTimeout != std::chrono::milliseconds{ 500 };
103 }
104
105 bool HasCustomExportTimeout() const {
106 return exportTimeout != std::chrono::milliseconds{ 100 };
107 }
108
109 bool HasCustomStartTimeout() const {
110 return startTimeout != std::chrono::milliseconds{ 250 };
111 }
112 } loading;
113
114 // Runtime configuration
115 struct Runtime {
116 bool pinToMainThread = true;
117 UpdateMode updateMode = UpdateMode::Manual;
118 std::chrono::milliseconds updateInterval{ 16 };
119 std::function<void(std::chrono::milliseconds)> updateCallback;
120 std::optional<size_t> threadPriority;
121
122 // Check if values are non-default
123 bool HasCustomPinToMainThread() const {
124 return pinToMainThread != true;
125 }
126
127 bool HasCustomUpdateMode() const {
128 return updateMode != UpdateMode::Manual;
129 }
130
131 bool HasCustomUpdateInterval() const {
132 return updateInterval != std::chrono::milliseconds{ 16 };
133 }
134
135 bool HasThreadPriority() const {
136 return threadPriority.has_value();
137 }
138 } runtime;
139
140 // Security configuration
141 struct Security {
142 std::unordered_set<std::string> whitelistedExtensions;
143 std::unordered_set<std::string> blacklistedExtensions;
144
145 bool HasWhitelist() const {
146 return !whitelistedExtensions.empty();
147 }
148
149 bool HasBlacklist() const {
150 return !blacklistedExtensions.empty();
151 }
152 } security;
153
154 // Logging configuration
155 struct Logging {
156 Severity severity{ Severity::Error };
157 bool printReport = false;
158 bool printLoadOrder = false;
159 bool printDependencyGraph = false;
160 bool printDigraphDot = false;
161 std::filesystem::path exportDigraphDot;
162
163 bool HasCustomSeverity() const {
164 return severity != Severity::Error;
165 }
166
167 bool HasCustomPrintReport() const {
168 return printReport != false;
169 }
170
171 bool HasCustomPrintLoadOrder() const {
172 return printLoadOrder != false;
173 }
174
175 bool HasCustomPrintDependencyGraph() const {
176 return printDependencyGraph != false;
177 }
178
179 bool HasCustomPrintDigraphDot() const {
180 return printDigraphDot != false;
181 }
182
183 bool HasExportPath() const {
184 return !exportDigraphDot.empty();
185 }
186 } logging;
187
188 // Comprehensive merge implementation
189 void MergeFrom(const Config& other, ConfigSource source = ConfigSource::Override) {
190 // Only merge if the new source has equal or higher priority
191
192 // Merge Paths
193 if (source >= _sources.paths) {
194 bool pathsChanged = false;
195
196 // Always take baseDir if provided
197 if (!other.paths.baseDir.empty()) {
198 paths.baseDir = other.paths.baseDir;
199 pathsChanged = true;
200 }
201
202 // Take other paths if they're customized
203 if (other.paths.HasCustomExtensionsDir()) {
204 paths.extensionsDir = other.paths.extensionsDir;
205 pathsChanged = true;
206 }
207 if (other.paths.HasCustomConfigsDir()) {
208 paths.configsDir = other.paths.configsDir;
209 pathsChanged = true;
210 }
211 if (other.paths.HasCustomDataDir()) {
212 paths.dataDir = other.paths.dataDir;
213 pathsChanged = true;
214 }
215 if (other.paths.HasCustomLogsDir()) {
216 paths.logsDir = other.paths.logsDir;
217 pathsChanged = true;
218 }
219 if (other.paths.HasCustomCacheDir()) {
220 paths.cacheDir = other.paths.cacheDir;
221 pathsChanged = true;
222 }
223
224 if (pathsChanged) {
225 _sources.paths = source;
226 }
227 }
228
229 // Merge Loading
230 if (source >= _sources.loading) {
231 bool loadingChanged = false;
232
233 if (other.loading.HasCustomPreferOwnSymbols()) {
234 loading.preferOwnSymbols = other.loading.preferOwnSymbols;
235 loadingChanged = true;
236 }
237 if (other.loading.HasCustomMaxConcurrentLoads()) {
238 loading.maxConcurrentLoads = other.loading.maxConcurrentLoads;
239 loadingChanged = true;
240 }
241 if (other.loading.HasCustomLoadTimeout()) {
242 loading.loadTimeout = other.loading.loadTimeout;
243 loadingChanged = true;
244 }
245 if (other.loading.HasCustomExportTimeout()) {
246 loading.exportTimeout = other.loading.exportTimeout;
247 loadingChanged = true;
248 }
249 if (other.loading.HasCustomStartTimeout()) {
250 loading.startTimeout = other.loading.startTimeout;
251 loadingChanged = true;
252 }
253
254 if (loadingChanged) {
255 _sources.loading = source;
256 }
257 }
258
259 // Merge Runtime
260 if (source >= _sources.runtime) {
261 bool runtimeChanged = false;
262
263 if (other.runtime.HasCustomUpdateMode()) {
264 runtime.updateMode = other.runtime.updateMode;
265 runtimeChanged = true;
266 }
267 if (other.runtime.HasCustomUpdateInterval()) {
268 runtime.updateInterval = other.runtime.updateInterval;
269 runtimeChanged = true;
270 }
271 if (other.runtime.updateCallback) {
272 runtime.updateCallback = other.runtime.updateCallback;
273 runtimeChanged = true;
274 }
275 if (other.runtime.HasCustomPinToMainThread()) {
276 runtime.pinToMainThread = other.runtime.pinToMainThread;
277 runtimeChanged = true;
278 }
279 if (other.runtime.HasThreadPriority()) {
280 runtime.threadPriority = other.runtime.threadPriority;
281 runtimeChanged = true;
282 }
283
284 if (runtimeChanged) {
285 _sources.runtime = source;
286 }
287 }
288
289 // Merge Security
290 if (source >= _sources.security) {
291 bool securityChanged = false;
292
293 // For sets, we have different merge strategies
294 if (source == ConfigSource::Override) {
295 // Override replaces entirely if non-empty
296 if (other.security.HasWhitelist()) {
297 security.whitelistedExtensions = other.security.whitelistedExtensions;
298 securityChanged = true;
299 }
300 if (other.security.HasBlacklist()) {
301 security.blacklistedExtensions = other.security.blacklistedExtensions;
302 securityChanged = true;
303 }
304 } else {
305 // Other sources merge/append
306 if (other.security.HasWhitelist()) {
307 security.whitelistedExtensions.insert(
308 other.security.whitelistedExtensions.begin(),
309 other.security.whitelistedExtensions.end()
310 );
311 securityChanged = true;
312 }
313 if (other.security.HasBlacklist()) {
314 security.blacklistedExtensions.insert(
315 other.security.blacklistedExtensions.begin(),
316 other.security.blacklistedExtensions.end()
317 );
318 securityChanged = true;
319 }
320 }
321
322 if (securityChanged) {
323 _sources.security = source;
324 }
325 }
326
327 // Merge Logging
328 if (source >= _sources.logging) {
329 bool loggingChanged = false;
330
331 if (other.logging.HasCustomSeverity()) {
332 logging.severity = other.logging.severity;
333 loggingChanged = true;
334 }
335 if (other.logging.HasCustomPrintReport()) {
336 logging.printReport = other.logging.printReport;
337 loggingChanged = true;
338 }
339 if (other.logging.HasCustomPrintLoadOrder()) {
340 logging.printLoadOrder = other.logging.printLoadOrder;
341 loggingChanged = true;
342 }
343 if (other.logging.HasCustomPrintDependencyGraph()) {
344 logging.printDependencyGraph = other.logging.printDependencyGraph;
345 loggingChanged = true;
346 }
347 if (other.logging.HasCustomPrintDigraphDot()) {
348 logging.printDigraphDot = other.logging.printDigraphDot;
349 loggingChanged = true;
350 }
351 if (other.logging.HasExportPath()) {
352 logging.exportDigraphDot = other.logging.exportDigraphDot;
353 loggingChanged = true;
354 }
355
356 if (loggingChanged) {
357 _sources.logging = source;
358 }
359 }
360
361 // Always resolve paths after merge
362 paths.ResolveRelativePaths();
363 }
364
365 // Alternative merge for selective field updates
366 /*void MergeField(std::string_view fieldPath, const Config& other, ConfigSource source =
367 ConfigSource::Override) {
368 // This allows merging specific fields only
369 if (fieldPath == "paths") {
370 if (source >= _sources.paths) {
371 paths = other.paths;
372 _sources.paths = source;
373 paths.ResolveRelativePaths();
374 }
375 } else if (fieldPath == "loading") {
376 if (source >= _sources.loading) {
377 loading = other.loading;
378 _sources.loading = source;
379 }
380 } else if (fieldPath == "runtime") {
381 if (source >= _sources.runtime) {
382 runtime = other.runtime;
383 _sources.runtime = source;
384 }
385 } else if (fieldPath == "security") {
386 if (source >= _sources.security) {
387 security = other.security;
388 _sources.security = source;
389 }
390 } else if (fieldPath == "logging") {
391 if (source >= _sources.logging) {
392 logging = other.logging;
393 _sources.logging = source;
394 }
395 }
396 }
397
398 // Get effective source for debugging
399 std::string GetSourceInfo() const {
400 return std::format(
401 "Config sources - Paths: {}, Loading: {}, Runtime: {}, Security: {}, Logging: {}",
402 plg::enum_to_string(_sources.paths),
403 plg::enum_to_string(_sources.loading),
404 plg::enum_to_string(_sources.runtime),
405 plg::enum_to_string(_sources.security),
406 plg::enum_to_string(_sources.logging)
407 );
408 }*/
409
410 // Reset to defaults
411 void Reset() {
412 *this = Config{};
413 }
414
415 // Validation remains the same
416 Result<void> Validate() const {
417 if (paths.baseDir.empty()) {
418 return MakeError("Base directory not set");
419 }
420
421 // Check for path collisions
422 using pair = std::pair<std::string_view, const std::filesystem::path*>;
423 std::array pathList = { pair{ "extensions", &paths.extensionsDir },
424 pair{ "configs", &paths.configsDir },
425 pair{ "data", &paths.dataDir },
426 pair{ "logs", &paths.logsDir },
427 pair{ "cache", &paths.cacheDir } };
428
429 for (size_t i = 0; i < pathList.size(); ++i) {
430 for (size_t j = i + 1; j < pathList.size(); ++j) {
431 if (*pathList[i].second == *pathList[j].second) {
432 return MakeError(
433 "{} and {} directories are the same: {}",
434 pathList[i].first,
435 pathList[j].first,
436 plg::as_string(*pathList[i].second)
437 );
438 }
439 }
440 }
441
442 // Validate runtime config
443 if (runtime.updateMode == UpdateMode::BackgroundThread
444 && runtime.updateInterval <= std::chrono::milliseconds{ 0 }) {
445 return MakeError("Invalid update interval for background thread mode");
446 }
447
448 if (runtime.updateMode == UpdateMode::Callback && !runtime.updateCallback) {
449 return MakeError("Update callback not set for callback mode");
450 }
451
452 return {};
453 }
454 };
455} // namespace plugify