DNDSR 0.2.1
Distributed Numeric Data Structure for CFV
Loading...
Searching...
No Matches
ConfigParam.hpp
Go to the documentation of this file.
1#pragma once
2/// @file ConfigParam.hpp
3/// @brief pybind11-style configuration registration with macro-based field
4/// declaration and namespace-scoped tag kwargs.
5///
6/// @details
7/// ## Overview
8///
9/// Config sections are plain structs. Metadata is registered in a static
10/// method opened by the `DNDS_DECLARE_CONFIG(Type)` macro. Inside that
11/// body the user calls `DNDS_FIELD(member, "description", tags...)` which
12/// is a macro that auto-stringifies the member name — no name duplication.
13///
14/// ## Example
15///
16/// @code
17/// struct ImplicitCFLControl
18/// {
19/// real CFL = 10;
20/// int nForceLocalStartStep = INT_MAX;
21/// bool useLocalDt = true;
22/// real RANSRelax = 1;
23///
24/// DNDS_DECLARE_CONFIG(ImplicitCFLControl)
25/// {
26/// DNDS_FIELD(CFL, "CFL for implicit local dt");
27/// DNDS_FIELD(nForceLocalStartStep, "Step to force local dt",
28/// DNDS::Config::range(0));
29/// DNDS_FIELD(useLocalDt, "Use local (vs uniform) dTau");
30/// DNDS_FIELD(RANSRelax, "RANS under-relaxation factor",
31/// DNDS::Config::range(0.0, 1.0));
32///
33/// config.check([](const T &s) -> DNDS::CheckResult {
34/// if (s.RANSRelax <= 0)
35/// return {false, "RANSRelax must be positive"};
36/// return {true, ""};
37/// });
38/// }
39/// };
40/// @endcode
41///
42/// ## Adding a New Parameter
43///
44/// 1. Declare the member with default (plain C++).
45/// 2. Add `DNDS_FIELD(member, "description")` in the DNDS_DECLARE_CONFIG body.
46///
47/// ## Tag kwargs (namespace-scoped)
48///
49/// Tags are passed as extra arguments to `DNDS_FIELD`:
50///
51/// | Tag | Purpose | Example |
52/// |-----|---------|---------|
53/// | `DNDS::Config::range(min)` | Min constraint (schema + runtime check) | `DNDS::Config::range(0)` |
54/// | `DNDS::Config::range(min,max)` | Min+max constraint | `DNDS::Config::range(0.0, 1.0)` |
55/// | `DNDS::Config::info(k,v)` | Aux info (`"x-<key>"` in schema) | `DNDS::Config::info("unit","Pa")` |
56/// | `DNDS::Config::enum_values(v)` | Allowed string values for enum fields | `DNDS::Config::enum_values({"Roe","HLLC"})` |
57///
58/// ## Runtime Range Validation
59///
60/// When a field has a `range()` tag, `readFromJson()` checks the parsed value
61/// against the min/max bounds and throws `std::runtime_error` with a clear
62/// message on violation. This catches bad config values at load time.
63///
64/// ## Section-Level Checks
65///
66/// @code
67/// config.check([](const T &s) -> DNDS::CheckResult { ... });
68/// config.check_ctx([](const T &s, const DNDS::ConfigContext &ctx) -> DNDS::CheckResult { ... });
69/// @endcode
70///
71/// ## Nested Sections and Special Fields
72///
73/// Use explicit `config.*` calls (not the DNDS_FIELD macro) for these:
74///
75/// @code
76/// config.field_section(&T::frameRotation, "frameConstRotation", "Rotating frame");
77/// config.field_array_of<BoxInit>(&T::boxInits, "boxInitializers", "Box inits");
78/// config.field_map_of<CoarseCtrl>(&T::coarseList, "coarseGridList", "Per-level");
79/// config.field_json(&T::extra, "odeSettingsExtra", "Opaque ODE settings");
80/// config.field_alias(&T::rsType, "riemannSolverType", "Riemann solver type");
81/// @endcode
82///
83/// ## Trivial Copyability
84///
85/// The struct has no base class, no virtual methods, no instance-level data
86/// introduced by the macro. `DNDS_DECLARE_CONFIG` only generates static
87/// methods and friend functions.
88
89#include "ConfigRegistry.hpp"
90#include <type_traits>
91#include <utility>
92
93namespace DNDS
94{
95 // ================================================================
96 // Type-to-ConfigTypeTag mapping
97 // ================================================================
98
99 template <typename T, typename Enable = void>
101 {
103 };
104
105 template <>
106 struct ConfigTypeTagOf<bool>
107 {
109 };
110 template <>
111 struct ConfigTypeTagOf<std::string>
112 {
114 };
115
116 template <typename T>
117 struct ConfigTypeTagOf<T, std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>>>
118 {
120 };
121
122 template <typename T>
123 struct ConfigTypeTagOf<T, std::enable_if_t<std::is_floating_point_v<T>>>
124 {
126 };
127
128 template <typename T>
129 struct ConfigTypeTagOf<std::vector<T>>
130 {
132 };
133
134 template <typename T, std::size_t N>
135 struct ConfigTypeTagOf<std::array<T, N>>
136 {
138 };
139
140 template <>
141 struct ConfigTypeTagOf<nlohmann::ordered_json>
142 {
144 };
145
146 /// C++ enum types serialize as JSON strings via nlohmann, so map to Enum.
147 template <typename T>
148 struct ConfigTypeTagOf<T, std::enable_if_t<std::is_enum_v<T>>>
149 {
151 };
152
153 /// Eigen matrix/vector types serialize as JSON arrays, so map them to Array.
154 /// Detection uses the `Scalar` typedef and `RowsAtCompileTime` enum that
155 /// all Eigen matrix expressions expose — no Eigen headers needed here.
156 namespace detail
157 {
158 template <typename T, typename = void>
159 struct is_eigen_type : std::false_type
160 {
161 };
162
163 template <typename T>
164 struct is_eigen_type<T, std::void_t<
165 typename T::Scalar,
166 decltype(static_cast<int>(T::RowsAtCompileTime)),
167 decltype(static_cast<int>(T::ColsAtCompileTime))>> : std::true_type
168 {
169 };
170 } // namespace detail
171
172 template <typename T>
173 struct ConfigTypeTagOf<T, std::enable_if_t<detail::is_eigen_type<T>::value>>
174 {
176 };
177
178 inline std::string schemaTypeString(ConfigTypeTag tag)
179 {
180 switch (tag)
181 {
183 return "boolean";
185 return "integer";
187 return "number";
188 // NOLINTBEGIN(bugprone-branch-clone): JSON Schema has no distinct
189 // `enum` / `ArrayOfObjects` / `MapOfObjects` types; each maps to
190 // the closest built-in kind (string / array / object). Keeping
191 // the cases explicit documents the mapping at the call site.
193 return "string";
195 return "string";
197 return "array";
199 return "object";
201 return "array";
203 return "object";
204 // NOLINTEND(bugprone-branch-clone)
206 return {};
207 }
208 return {};
209 }
210
211 // ================================================================
212 // Namespace-scoped tag factories: DNDS::Config::range(), etc.
213 // ================================================================
214
215 /// @brief Namespace for config field tag kwargs.
216 ///
217 /// Tags are lightweight value objects passed as extra arguments to
218 /// `DNDS_FIELD(...)`. They attach optional metadata (constraints,
219 /// auxiliary info, enum values) to a field at registration time.
220 namespace Config
221 {
222 /// @brief Numeric range constraint.
223 /// Enforced at parse time in readFromJson and emitted in schema.
224 struct RangeTag
225 {
226 std::optional<double> min;
227 std::optional<double> max;
228 };
229
230 /// @brief Auxiliary info tag (emitted as `"x-<key>"` in schema).
231 struct InfoTag
232 {
233 std::string key;
234 std::string value;
235 };
236
237 /// @brief Explicit enum allowed-values tag.
239 {
240 std::vector<std::string> values;
241 };
242
243 /// @brief Create a minimum-only range constraint.
244 inline RangeTag range(double min) { return {min, std::nullopt}; }
245
246 /// @brief Create a min+max range constraint.
247 inline RangeTag range(double min, double max) { return {{min}, {max}}; }
248
249 /// @brief Create an auxiliary info tag.
250 inline InfoTag info(std::string key, std::string value)
251 {
252 return {std::move(key), std::move(value)};
253 }
254
255 /// @brief Create an enum allowed-values tag.
256 inline EnumValuesTag enum_values(std::vector<std::string> vals)
257 {
258 return {std::move(vals)};
259 }
260 } // namespace Config
261
262 // ================================================================
263 // Tag application helpers
264 // ================================================================
265
266 namespace detail
267 {
268 inline void applyTag(FieldMeta &meta, const Config::RangeTag &tag)
269 {
270 meta.minimum = tag.min;
271 meta.maximum = tag.max;
272 }
273
274 inline void applyTag(FieldMeta &meta, const Config::InfoTag &tag)
275 {
276 meta.auxInfo[tag.key] = tag.value;
277 }
278
279 inline void applyTag(FieldMeta &meta, const Config::EnumValuesTag &tag)
280 {
281 meta.enumValues = tag.values;
283 }
284
285 inline void applyTags(FieldMeta & /*meta*/) {}
286
287 template <typename Tag, typename... Rest>
288 void applyTags(FieldMeta &meta, Tag &&tag, Rest &&...rest)
289 {
290 applyTag(meta, std::forward<Tag>(tag));
291 applyTags(meta, std::forward<Rest>(rest)...);
292 }
293
294 /// @brief Build the schemaEntry closure from a fully-tagged FieldMeta.
295 ///
296 /// Captures the pointer-to-member, description, and all tag data
297 /// (range, enum, aux info) to produce the JSON Schema fragment on demand.
298 template <typename T, typename V>
299 std::function<nlohmann::ordered_json()>
300 makeSchemaEntry(V T::*member, const char *desc, const FieldMeta &meta)
301 {
302 // Capture a snapshot of the tag data.
303 auto typeTag = meta.typeTag;
304 auto minimum = meta.minimum;
305 auto maximum = meta.maximum;
306 auto enumVals = meta.enumValues;
307 auto auxInfo = meta.auxInfo;
308
309 return [member, desc, typeTag, minimum, maximum, enumVals, auxInfo]() -> nlohmann::ordered_json
310 {
311 nlohmann::ordered_json s;
312 auto ts = schemaTypeString(typeTag);
313 if (!ts.empty())
314 s["type"] = ts;
315 T defaults{};
316 s["default"] = nlohmann::ordered_json(defaults.*member);
317 s["description"] = desc;
318 if (minimum.has_value())
319 s["minimum"] = minimum.value();
320 if (maximum.has_value())
321 s["maximum"] = maximum.value();
322 if (!enumVals.empty())
323 s["enum"] = enumVals;
324 for (const auto &kv : auxInfo)
325 s["x-" + kv.first] = kv.second;
326 return s;
327 };
328 }
329
330 /// @brief Build a runtime range-check closure.
331 ///
332 /// Returns a function that, given a JSON value for this field, checks
333 /// it against min/max bounds and throws on violation. Returns nullptr
334 /// if no range constraint is set.
335 template <typename T, typename V>
336 std::function<void(const nlohmann::ordered_json &, const char *)>
338 {
339 if (!meta.minimum.has_value() && !meta.maximum.has_value())
340 return nullptr;
341
342 auto minimum = meta.minimum;
343 auto maximum = meta.maximum;
344
345 return [minimum, maximum](const nlohmann::ordered_json &val, const char *fieldName)
346 {
347 // Only check numeric types
348 if (!val.is_number())
349 return;
350 double v = val.get<double>();
351 if (minimum.has_value() && v < minimum.value())
352 {
353 throw std::runtime_error(
354 fmt::format("Config field '{}': value {} is below minimum {}",
355 fieldName, v, minimum.value()));
356 }
357 if (maximum.has_value() && v > maximum.value())
358 {
359 throw std::runtime_error(
360 fmt::format("Config field '{}': value {} is above maximum {}",
361 fieldName, v, maximum.value()));
362 }
363 };
364 }
365 } // namespace detail
366
367 // ================================================================
368 // ConfigSectionBuilder<T> — the pybind11-style registrar
369 // ================================================================
370
371 /// @brief Builder object passed to the user's registration function.
372 ///
373 /// Named `config` in the `DNDS_DECLARE_CONFIG` expansion for readability.
374 /// Provides `field()` (called via `DNDS_FIELD` macro), `check()`, `check_ctx()`,
375 /// and specialized field registrars for sections, arrays, maps, and JSON blobs.
376 template <typename T>
378 {
379 public:
380 // ---- field(): scalar, enum, or any nlohmann-serializable member ----
381
382 /// @brief Register a field with pointer-to-member.
383 ///
384 /// Typically called via the `DNDS_FIELD` macro (which auto-stringifies
385 /// the member name). Can also be called directly for aliased keys.
386 ///
387 /// @param member Pointer-to-member (e.g. `&T::CFL`).
388 /// @param jsonKey The JSON key string.
389 /// @param desc Human-readable description.
390 /// @param tags Zero or more DNDS::Config tag objects.
391 template <typename V, typename... Tags>
392 void field(V T::*member, const char *jsonKey, const char *desc, Tags &&...tags)
393 {
394 FieldMeta meta;
395 meta.name = jsonKey;
396 meta.description = desc;
398
399 // Apply tags first so range/enum data is available for closures.
400 detail::applyTags(meta, std::forward<Tags>(tags)...);
401
402 // Build range checker (may be nullptr if no range tag).
403 auto rangeChecker = detail::makeRangeChecker<T, V>(meta);
404
405 meta.readField = [member, jsonKey, rangeChecker](const nlohmann::ordered_json &j, void *obj)
406 {
407 const auto &val = j.at(jsonKey);
408 if (rangeChecker)
409 rangeChecker(val, jsonKey);
410 static_cast<T *>(obj)->*member = val.template get<V>();
411 };
412 meta.writeField = [member, jsonKey](nlohmann::ordered_json &j, const void *obj)
413 {
414 j[jsonKey] = static_cast<const T *>(obj)->*member;
415 };
416 meta.schemaEntry = detail::makeSchemaEntry<T>(member, desc, meta);
417
418 ConfigRegistry<T>::registerField(std::move(meta));
419 }
420
421 // ---- field_alias(): convenience alias for field() with different JSON key ----
422
423 /// @brief Register a field whose JSON key differs from the C++ member name.
424 template <typename V, typename... Tags>
425 void field_alias(V T::*member, const char *jsonKey, const char *desc, Tags &&...tags)
426 {
427 field(member, jsonKey, desc, std::forward<Tags>(tags)...);
428 }
429
430 // ---- field_section(): nested config sub-section ----
431
432 /// @brief Register a nested sub-section. The sub-section type must
433 /// itself use DNDS_DECLARE_CONFIG.
434 template <typename S, typename... Tags>
435 void field_section(S T::*member, const char *jsonKey, const char *desc, Tags &&...tags)
436 {
437 FieldMeta meta;
438 meta.name = jsonKey;
439 meta.description = desc;
441 meta.readField = [member, jsonKey](const nlohmann::ordered_json &j, void *obj)
442 {
443 // In-place deserialization preserves non-serialized members
444 // (e.g. EulerEvaluatorSettings::_nVars set by the constructor).
445 from_json(j.at(jsonKey), static_cast<T *>(obj)->*member);
446 };
447 meta.writeField = [member, jsonKey](nlohmann::ordered_json &j, const void *obj)
448 {
449 j[jsonKey] = static_cast<const T *>(obj)->*member;
450 };
451 meta.schemaEntry = [desc]()
452 {
453 S::_dnds_ensure_registered();
455 };
456 detail::applyTags(meta, std::forward<Tags>(tags)...);
457 ConfigRegistry<T>::registerField(std::move(meta));
458 }
459
460 // ---- field_array_of<S>(): std::vector<S> ----
461
462 /// @brief Register a `std::vector<S>` field (array of sub-objects).
463 template <typename S, typename... Tags>
464 void field_array_of(std::vector<S> T::*member, const char *jsonKey, const char *desc, Tags &&...tags)
465 {
466 FieldMeta meta;
467 meta.name = jsonKey;
468 meta.description = desc;
470 meta.readField = [member, jsonKey](const nlohmann::ordered_json &j, void *obj)
471 {
472 static_cast<T *>(obj)->*member = j.at(jsonKey).template get<std::vector<S>>();
473 };
474 meta.writeField = [member, jsonKey](nlohmann::ordered_json &j, const void *obj)
475 {
476 j[jsonKey] = static_cast<const T *>(obj)->*member;
477 };
478 meta.schemaEntry = [desc]() -> nlohmann::ordered_json
479 {
480 nlohmann::ordered_json s;
481 s["type"] = "array";
482 s["description"] = desc;
483 S::_dnds_ensure_registered();
484 s["items"] = ConfigRegistry<S>::emitSchema();
485 return s;
486 };
487 detail::applyTags(meta, std::forward<Tags>(tags)...);
488 ConfigRegistry<T>::registerField(std::move(meta));
489 }
490
491 // ---- field_map_of<S>(): std::map<std::string, S> ----
492
493 /// @brief Register a `std::map<std::string, S>` field.
494 template <typename S, typename... Tags>
495 void field_map_of(std::map<std::string, S> T::*member, const char *jsonKey, const char *desc, Tags &&...tags)
496 {
497 FieldMeta meta;
498 meta.name = jsonKey;
499 meta.description = desc;
501 meta.readField = [member, jsonKey](const nlohmann::ordered_json &j, void *obj)
502 {
503 static_cast<T *>(obj)->*member =
504 j.at(jsonKey).template get<std::map<std::string, S>>();
505 };
506 meta.writeField = [member, jsonKey](nlohmann::ordered_json &j, const void *obj)
507 {
508 j[jsonKey] = static_cast<const T *>(obj)->*member;
509 };
510 meta.schemaEntry = [desc]() -> nlohmann::ordered_json
511 {
512 nlohmann::ordered_json s;
513 s["type"] = "object";
514 s["description"] = desc;
515 S::_dnds_ensure_registered();
516 s["additionalProperties"] = ConfigRegistry<S>::emitSchema();
517 return s;
518 };
519 detail::applyTags(meta, std::forward<Tags>(tags)...);
520 ConfigRegistry<T>::registerField(std::move(meta));
521 }
522
523 // ---- field_json(): opaque JSON blob ----
524
525 /// @brief Register an opaque `nlohmann::ordered_json` field.
526 template <typename... Tags>
527 void field_json(nlohmann::ordered_json T::*member, const char *jsonKey, const char *desc, Tags &&...tags)
528 {
529 FieldMeta meta;
530 meta.name = jsonKey;
531 meta.description = desc;
533 meta.readField = [member, jsonKey](const nlohmann::ordered_json &j, void *obj)
534 {
535 static_cast<T *>(obj)->*member = j.at(jsonKey);
536 };
537 meta.writeField = [member, jsonKey](nlohmann::ordered_json &j, const void *obj)
538 {
539 j[jsonKey] = static_cast<const T *>(obj)->*member;
540 };
541 meta.schemaEntry = [desc]() -> nlohmann::ordered_json
542 {
543 nlohmann::ordered_json s;
544 s["description"] = desc;
545 return s;
546 };
547 detail::applyTags(meta, std::forward<Tags>(tags)...);
548 ConfigRegistry<T>::registerField(std::move(meta));
549 }
550
551 // ---- field_json_schema(): opaque JSON blob with explicit schema ----
552
553 /// @brief Register an opaque `nlohmann::ordered_json` field with a
554 /// user-supplied schema generator.
555 ///
556 /// Use this for heterogeneous structures (e.g. arrays of discriminated
557 /// union objects) where automatic schema inference is not possible.
558 ///
559 /// @param member Pointer-to-member.
560 /// @param jsonKey JSON key name.
561 /// @param desc Human-readable description.
562 /// @param schemaFn Callable `() -> ordered_json` returning the
563 /// full JSON Schema for this field.
564 /// @param tags Optional tag objects.
565 template <typename FSchema, typename... Tags>
566 void field_json_schema(nlohmann::ordered_json T::*member,
567 const char *jsonKey, const char *desc,
568 FSchema &&schemaFn, Tags &&...tags)
569 {
570 FieldMeta meta;
571 meta.name = jsonKey;
572 meta.description = desc;
574 meta.readField = [member, jsonKey](const nlohmann::ordered_json &j, void *obj)
575 {
576 static_cast<T *>(obj)->*member = j.at(jsonKey);
577 };
578 meta.writeField = [member, jsonKey](nlohmann::ordered_json &j, const void *obj)
579 {
580 j[jsonKey] = static_cast<const T *>(obj)->*member;
581 };
582 meta.schemaEntry = [fn = std::forward<FSchema>(schemaFn)]() -> nlohmann::ordered_json
583 {
584 return fn();
585 };
586 detail::applyTags(meta, std::forward<Tags>(tags)...);
587 ConfigRegistry<T>::registerField(std::move(meta));
588 }
589
590 // ---- check(): cross-field validation ----
591
592 /// @brief Register a context-free cross-field check.
593 /// @param f Lambda `(const T&) -> CheckResult`.
594 template <typename F>
595 auto check(F &&f) -> decltype(f(std::declval<const T &>()), void())
596 {
598 [fn = std::forward<F>(f)](const void *obj) -> CheckResult
599 {
600 return fn(*static_cast<const T *>(obj));
601 });
602 }
603
604 /// @brief Register a context-free cross-field check with a message string.
605 /// @param msg Error message shown when the check fails.
606 /// @param pred Lambda `(const T&) -> bool`, returns true if the check passes.
607 template <typename F>
608 void check(const char *msg, F &&pred)
609 {
611 [message = std::string(msg), fn = std::forward<F>(pred)](const void *obj) -> CheckResult
612 {
613 bool ok = fn(*static_cast<const T *>(obj));
614 return CheckResult{ok, ok ? "" : message};
615 });
616 }
617
618 // ---- check_ctx(): context-aware cross-field validation ----
619
620 /// @brief Register a context-aware cross-field check.
621 /// @param f Lambda `(const T&, const ConfigContext&) -> CheckResult`.
622 template <typename F>
623 auto check_ctx(F &&f) -> decltype(f(std::declval<const T &>(), std::declval<const ConfigContext &>()), void())
624 {
626 [fn = std::forward<F>(f)](const void *obj, const ConfigContext &ctx) -> CheckResult
627 {
628 return fn(*static_cast<const T *>(obj), ctx);
629 });
630 }
631
632 // ---- post_read(): hook called after all fields are deserialized ----
633
634 /// @brief Register a post-read hook for recomputing derived quantities.
635 /// @param f Lambda `(T&) -> void`, called after readFromJson completes.
636 template <typename F>
637 void post_read(F &&f)
638 {
639 ConfigRegistry<T>::registerPostReadHook(std::forward<F>(f));
640 }
641 };
642
643} // namespace DNDS
644
645// ============================================================================
646// DNDS_FIELD(member, description, tags...)
647// ============================================================================
648/// @brief Register a field inside a DNDS_DECLARE_CONFIG body.
649///
650/// Auto-stringifies the member name so you never write it twice.
651/// The JSON key equals the C++ member name.
652///
653/// @param name_ Member name (unquoted).
654/// @param desc_ Description string literal.
655/// @param ... Zero or more DNDS::Config tag objects.
656///
657/// @code
658/// DNDS_FIELD(CFL, "CFL for implicit dt", DNDS::Config::range(0));
659/// @endcode
660#define DNDS_FIELD(name_, desc_, ...) \
661 config.field(&T::name_, #name_, desc_, ##__VA_ARGS__)
662
663// ============================================================================
664// DNDS_DECLARE_CONFIG(Type)
665// ============================================================================
666/// @brief Open a config section registration body.
667///
668/// Expands to:
669/// - `using T = Type` (so DNDS_FIELD can reference `&T::member`).
670/// - Lazy `_dnds_ensure_registered()` with one-time init guard.
671/// - Friend `to_json` / `from_json` calling ensureRegistered first.
672/// - `schema()`, `validate()`, `validateWithContext()`, `validateKeys()`.
673/// - Opens `static void _dnds_do_register(ConfigSectionBuilder<T>& config)`
674/// — the user provides the `{ ... }` body after the macro.
675///
676/// @code
677/// struct MySection
678/// {
679/// real a = 1.0;
680/// int b = 42;
681///
682/// DNDS_DECLARE_CONFIG(MySection)
683/// {
684/// DNDS_FIELD(a, "The a parameter", DNDS::Config::range(0));
685/// DNDS_FIELD(b, "The b parameter");
686/// }
687/// };
688/// @endcode
689// NOLINTBEGIN(bugprone-macro-parentheses)
690// Rationale: `Type_` is a type name used in function parameter lists and
691// template arguments; neither context permits parenthesization.
692#define DNDS_DECLARE_CONFIG(Type_) \
693 using T = Type_; \
694 static void _dnds_ensure_registered() \
695 { \
696 static bool done = false; \
697 if (done) \
698 return; \
699 done = true; \
700 ::DNDS::ConfigSectionBuilder<Type_> config; \
701 _dnds_do_register(config); \
702 } \
703 friend void to_json(nlohmann::ordered_json &j, const Type_ &t) \
704 { \
705 Type_::_dnds_ensure_registered(); \
706 ::DNDS::ConfigRegistry<Type_>::writeToJson(j, t); \
707 } \
708 friend void from_json(const nlohmann::ordered_json &j, Type_ &t) \
709 { \
710 Type_::_dnds_ensure_registered(); \
711 ::DNDS::ConfigRegistry<Type_>::readFromJson(j, t); \
712 } \
713 static nlohmann::ordered_json schema(const std::string &desc = "") \
714 { \
715 Type_::_dnds_ensure_registered(); \
716 return ::DNDS::ConfigRegistry<Type_>::emitSchema(desc); \
717 } \
718 std::vector<::DNDS::CheckResult> validate() const \
719 { \
720 Type_::_dnds_ensure_registered(); \
721 return ::DNDS::ConfigRegistry<Type_>::validate(*this); \
722 } \
723 std::vector<::DNDS::CheckResult> validateWithContext( \
724 const ::DNDS::ConfigContext &ctx) const \
725 { \
726 Type_::_dnds_ensure_registered(); \
727 return ::DNDS::ConfigRegistry<Type_>::validateWithContext(*this, ctx); \
728 } \
729 static void validateKeys(const nlohmann::ordered_json &j) \
730 { \
731 Type_::_dnds_ensure_registered(); \
732 ::DNDS::ConfigRegistry<Type_>::validateKeys(j); \
733 } \
734 static void _dnds_do_register(::DNDS::ConfigSectionBuilder<Type_> &config)
735// NOLINTEND(bugprone-macro-parentheses)
Per-type configuration field registry with JSON serialization, JSON Schema (draft-07) emission,...
static bool registerField(FieldMeta meta)
Register a single field's metadata.
static bool registerCheck(CrossFieldCheck check)
Register a cross-field validation check (no runtime context needed).
static bool registerContextualCheck(ContextualCheck check)
Register a cross-field validation check that needs runtime context.
static nlohmann::ordered_json emitSchema(const std::string &sectionDescription="")
Emit a JSON Schema (draft-07) object describing all registered fields.
static void registerPostReadHook(std::function< void(T &)> hook)
Register a post-read hook called after all fields are deserialized. Useful for recomputing derived qu...
Builder object passed to the user's registration function.
void field_map_of(std::map< std::string, S > T::*member, const char *jsonKey, const char *desc, Tags &&...tags)
Register a std::map<std::string, S> field.
void field_json(nlohmann::ordered_json T::*member, const char *jsonKey, const char *desc, Tags &&...tags)
Register an opaque nlohmann::ordered_json field.
void field(V T::*member, const char *jsonKey, const char *desc, Tags &&...tags)
Register a field with pointer-to-member.
auto check(F &&f) -> decltype(f(std::declval< const T & >()), void())
Register a context-free cross-field check.
void field_json_schema(nlohmann::ordered_json T::*member, const char *jsonKey, const char *desc, FSchema &&schemaFn, Tags &&...tags)
Register an opaque nlohmann::ordered_json field with a user-supplied schema generator.
void check(const char *msg, F &&pred)
Register a context-free cross-field check with a message string.
void field_section(S T::*member, const char *jsonKey, const char *desc, Tags &&...tags)
Register a nested sub-section. The sub-section type must itself use DNDS_DECLARE_CONFIG.
void field_array_of(std::vector< S > T::*member, const char *jsonKey, const char *desc, Tags &&...tags)
Register a std::vector<S> field (array of sub-objects).
void post_read(F &&f)
Register a post-read hook for recomputing derived quantities.
auto check_ctx(F &&f) -> decltype(f(std::declval< const T & >(), std::declval< const ConfigContext & >()), void())
Register a context-aware cross-field check.
void field_alias(V T::*member, const char *jsonKey, const char *desc, Tags &&...tags)
Register a field whose JSON key differs from the C++ member name.
EnumValuesTag enum_values(std::vector< std::string > vals)
Create an enum allowed-values tag.
InfoTag info(std::string key, std::string value)
Create an auxiliary info tag.
RangeTag range(double min)
Create a minimum-only range constraint.
std::function< void(const nlohmann::ordered_json &, const char *)> makeRangeChecker(const FieldMeta &meta)
Build a runtime range-check closure.
void applyTags(FieldMeta &)
void applyTag(FieldMeta &meta, const Config::RangeTag &tag)
std::function< nlohmann::ordered_json()> makeSchemaEntry(V T::*member, const char *desc, const FieldMeta &meta)
Build the schemaEntry closure from a fully-tagged FieldMeta.
the host side operators are provided as implemented
ConfigTypeTag
Enumerates the JSON Schema type associated with a config field.
void from_json(const nlohmann::ordered_json &j, host_device_vector< real > &v)
Definition JsonUtil.hpp:183
std::string schemaTypeString(ConfigTypeTag tag)
message(STATUS "here!!!! ${DNDS_Euler_Models_List}") foreach(item IN LISTS DNDS_Euler_Models_List) string(REPLACE "
Result of a single validation check.
Runtime context supplied to context-aware validation checks.
static constexpr ConfigTypeTag value
Explicit enum allowed-values tag.
std::vector< std::string > values
Auxiliary info tag (emitted as "x-<key>" in schema).
Numeric range constraint. Enforced at parse time in readFromJson and emitted in schema.
std::optional< double > max
std::optional< double > min
Descriptor for a single configuration field.
std::function< void(nlohmann::ordered_json &j, const void *obj)> writeField
Write this field from a struct instance into a JSON object.
ConfigTypeTag typeTag
JSON Schema type category (zero-init = Bool; always overwritten by the builder).
std::function< void(const nlohmann::ordered_json &j, void *obj)> readField
Read this field from a JSON object into a struct instance.
std::vector< std::string > enumValues
Allowed string values for enum fields. Empty for non-enum fields.
std::optional< double > minimum
Optional minimum constraint for numeric fields (used in schema + validation).
std::map< std::string, std::string > auxInfo
Auxiliary key-value info (emitted as "x-<key>": "<value>" in schema). Use for units,...
std::string description
Human-readable description, used in JSON Schema and generated docs.
std::function< nlohmann::ordered_json()> schemaEntry
Emit the JSON Schema fragment for this field.
std::optional< double > maximum
Optional maximum constraint for numeric fields (used in schema + validation).
std::string name
JSON key name (may differ from C++ member name for aliased fields).
Eigen::Matrix< real, 5, 1 > v