DNDSR 0.1.0.dev1+gcd065ad
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 <> struct ConfigTypeTagOf<bool> { static constexpr ConfigTypeTag value = ConfigTypeTag::Bool; };
106 template <> struct ConfigTypeTagOf<std::string> { static constexpr ConfigTypeTag value = ConfigTypeTag::String; };
107
108 template <typename T>
109 struct ConfigTypeTagOf<T, std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>>>
110 {
112 };
113
114 template <typename T>
115 struct ConfigTypeTagOf<T, std::enable_if_t<std::is_floating_point_v<T>>>
116 {
118 };
119
120 template <typename T>
121 struct ConfigTypeTagOf<std::vector<T>>
122 {
124 };
125
126 template <typename T, std::size_t N>
127 struct ConfigTypeTagOf<std::array<T, N>>
128 {
130 };
131
132 template <>
133 struct ConfigTypeTagOf<nlohmann::ordered_json>
134 {
136 };
137
138 /// C++ enum types serialize as JSON strings via nlohmann, so map to Enum.
139 template <typename T>
140 struct ConfigTypeTagOf<T, std::enable_if_t<std::is_enum_v<T>>>
141 {
143 };
144
145 /// Eigen matrix/vector types serialize as JSON arrays, so map them to Array.
146 /// Detection uses the `Scalar` typedef and `RowsAtCompileTime` enum that
147 /// all Eigen matrix expressions expose — no Eigen headers needed here.
148 namespace detail
149 {
150 template <typename T, typename = void>
151 struct is_eigen_type : std::false_type {};
152
153 template <typename T>
154 struct is_eigen_type<T, std::void_t<
155 typename T::Scalar,
156 decltype(static_cast<int>(T::RowsAtCompileTime)),
157 decltype(static_cast<int>(T::ColsAtCompileTime))>> : std::true_type {};
158 } // namespace detail
159
160 template <typename T>
161 struct ConfigTypeTagOf<T, std::enable_if_t<detail::is_eigen_type<T>::value>>
162 {
164 };
165
166 inline std::string schemaTypeString(ConfigTypeTag tag)
167 {
168 switch (tag)
169 {
170 case ConfigTypeTag::Bool: return "boolean";
171 case ConfigTypeTag::Int: return "integer";
172 case ConfigTypeTag::Real: return "number";
173 case ConfigTypeTag::String: return "string";
174 case ConfigTypeTag::Enum: return "string";
175 case ConfigTypeTag::Array: return "array";
176 case ConfigTypeTag::Object: return "object";
177 case ConfigTypeTag::ArrayOfObjects: return "array";
178 case ConfigTypeTag::MapOfObjects: return "object";
179 case ConfigTypeTag::Json: return {};
180 }
181 return {};
182 }
183
184 // ================================================================
185 // Namespace-scoped tag factories: DNDS::Config::range(), etc.
186 // ================================================================
187
188 /// @brief Namespace for config field tag kwargs.
189 ///
190 /// Tags are lightweight value objects passed as extra arguments to
191 /// `DNDS_FIELD(...)`. They attach optional metadata (constraints,
192 /// auxiliary info, enum values) to a field at registration time.
193 namespace Config
194 {
195 /// @brief Numeric range constraint.
196 /// Enforced at parse time in readFromJson and emitted in schema.
197 struct RangeTag
198 {
199 std::optional<double> min;
200 std::optional<double> max;
201 };
202
203 /// @brief Auxiliary info tag (emitted as `"x-<key>"` in schema).
204 struct InfoTag
205 {
206 std::string key;
207 std::string value;
208 };
209
210 /// @brief Explicit enum allowed-values tag.
212 {
213 std::vector<std::string> values;
214 };
215
216 /// @brief Create a minimum-only range constraint.
217 inline RangeTag range(double min) { return {min, std::nullopt}; }
218
219 /// @brief Create a min+max range constraint.
220 inline RangeTag range(double min, double max) { return {{min}, {max}}; }
221
222 /// @brief Create an auxiliary info tag.
223 inline InfoTag info(std::string key, std::string value)
224 {
225 return {std::move(key), std::move(value)};
226 }
227
228 /// @brief Create an enum allowed-values tag.
229 inline EnumValuesTag enum_values(std::vector<std::string> vals)
230 {
231 return {std::move(vals)};
232 }
233 } // namespace Config
234
235 // ================================================================
236 // Tag application helpers
237 // ================================================================
238
239 namespace detail
240 {
241 inline void applyTag(FieldMeta &meta, const Config::RangeTag &tag)
242 {
243 meta.minimum = tag.min;
244 meta.maximum = tag.max;
245 }
246
247 inline void applyTag(FieldMeta &meta, const Config::InfoTag &tag)
248 {
249 meta.auxInfo[tag.key] = tag.value;
250 }
251
252 inline void applyTag(FieldMeta &meta, const Config::EnumValuesTag &tag)
253 {
254 meta.enumValues = tag.values;
256 }
257
258 inline void applyTags(FieldMeta & /*meta*/) {}
259
260 template <typename Tag, typename... Rest>
261 void applyTags(FieldMeta &meta, Tag &&tag, Rest &&...rest)
262 {
263 applyTag(meta, std::forward<Tag>(tag));
264 applyTags(meta, std::forward<Rest>(rest)...);
265 }
266
267 /// @brief Build the schemaEntry closure from a fully-tagged FieldMeta.
268 ///
269 /// Captures the pointer-to-member, description, and all tag data
270 /// (range, enum, aux info) to produce the JSON Schema fragment on demand.
271 template <typename T, typename V>
272 std::function<nlohmann::ordered_json()>
273 makeSchemaEntry(V T::*member, const char *desc, const FieldMeta &meta)
274 {
275 // Capture a snapshot of the tag data.
276 auto typeTag = meta.typeTag;
277 auto minimum = meta.minimum;
278 auto maximum = meta.maximum;
279 auto enumVals = meta.enumValues;
280 auto auxInfo = meta.auxInfo;
281
282 return [member, desc, typeTag, minimum, maximum, enumVals, auxInfo]() -> nlohmann::ordered_json
283 {
284 nlohmann::ordered_json s;
285 auto ts = schemaTypeString(typeTag);
286 if (!ts.empty())
287 s["type"] = ts;
288 T defaults{};
289 s["default"] = nlohmann::ordered_json(defaults.*member);
290 s["description"] = desc;
291 if (minimum.has_value())
292 s["minimum"] = minimum.value();
293 if (maximum.has_value())
294 s["maximum"] = maximum.value();
295 if (!enumVals.empty())
296 s["enum"] = enumVals;
297 for (const auto &kv : auxInfo)
298 s["x-" + kv.first] = kv.second;
299 return s;
300 };
301 }
302
303 /// @brief Build a runtime range-check closure.
304 ///
305 /// Returns a function that, given a JSON value for this field, checks
306 /// it against min/max bounds and throws on violation. Returns nullptr
307 /// if no range constraint is set.
308 template <typename T, typename V>
309 std::function<void(const nlohmann::ordered_json &, const char *)>
311 {
312 if (!meta.minimum.has_value() && !meta.maximum.has_value())
313 return nullptr;
314
315 auto minimum = meta.minimum;
316 auto maximum = meta.maximum;
317
318 return [minimum, maximum](const nlohmann::ordered_json &val, const char *fieldName)
319 {
320 // Only check numeric types
321 if (!val.is_number())
322 return;
323 double v = val.get<double>();
324 if (minimum.has_value() && v < minimum.value())
325 {
326 throw std::runtime_error(
327 fmt::format("Config field '{}': value {} is below minimum {}",
328 fieldName, v, minimum.value()));
329 }
330 if (maximum.has_value() && v > maximum.value())
331 {
332 throw std::runtime_error(
333 fmt::format("Config field '{}': value {} is above maximum {}",
334 fieldName, v, maximum.value()));
335 }
336 };
337 }
338 } // namespace detail
339
340 // ================================================================
341 // ConfigSectionBuilder<T> — the pybind11-style registrar
342 // ================================================================
343
344 /// @brief Builder object passed to the user's registration function.
345 ///
346 /// Named `config` in the `DNDS_DECLARE_CONFIG` expansion for readability.
347 /// Provides `field()` (called via `DNDS_FIELD` macro), `check()`, `check_ctx()`,
348 /// and specialized field registrars for sections, arrays, maps, and JSON blobs.
349 template <typename T>
351 {
352 public:
353 // ---- field(): scalar, enum, or any nlohmann-serializable member ----
354
355 /// @brief Register a field with pointer-to-member.
356 ///
357 /// Typically called via the `DNDS_FIELD` macro (which auto-stringifies
358 /// the member name). Can also be called directly for aliased keys.
359 ///
360 /// @param member Pointer-to-member (e.g. `&T::CFL`).
361 /// @param jsonKey The JSON key string.
362 /// @param desc Human-readable description.
363 /// @param tags Zero or more DNDS::Config tag objects.
364 template <typename V, typename... Tags>
365 void field(V T::*member, const char *jsonKey, const char *desc, Tags &&...tags)
366 {
367 FieldMeta meta;
368 meta.name = jsonKey;
369 meta.description = desc;
371
372 // Apply tags first so range/enum data is available for closures.
373 detail::applyTags(meta, std::forward<Tags>(tags)...);
374
375 // Build range checker (may be nullptr if no range tag).
376 auto rangeChecker = detail::makeRangeChecker<T, V>(meta);
377
378 meta.readField = [member, jsonKey, rangeChecker](const nlohmann::ordered_json &j, void *obj)
379 {
380 const auto &val = j.at(jsonKey);
381 if (rangeChecker)
382 rangeChecker(val, jsonKey);
383 static_cast<T *>(obj)->*member = val.template get<V>();
384 };
385 meta.writeField = [member, jsonKey](nlohmann::ordered_json &j, const void *obj)
386 {
387 j[jsonKey] = static_cast<const T *>(obj)->*member;
388 };
389 meta.schemaEntry = detail::makeSchemaEntry<T>(member, desc, meta);
390
391 ConfigRegistry<T>::registerField(std::move(meta));
392 }
393
394 // ---- field_alias(): convenience alias for field() with different JSON key ----
395
396 /// @brief Register a field whose JSON key differs from the C++ member name.
397 template <typename V, typename... Tags>
398 void field_alias(V T::*member, const char *jsonKey, const char *desc, Tags &&...tags)
399 {
400 field(member, jsonKey, desc, std::forward<Tags>(tags)...);
401 }
402
403 // ---- field_section(): nested config sub-section ----
404
405 /// @brief Register a nested sub-section. The sub-section type must
406 /// itself use DNDS_DECLARE_CONFIG.
407 template <typename S, typename... Tags>
408 void field_section(S T::*member, const char *jsonKey, const char *desc, Tags &&...tags)
409 {
410 FieldMeta meta;
411 meta.name = jsonKey;
412 meta.description = desc;
414 meta.readField = [member, jsonKey](const nlohmann::ordered_json &j, void *obj)
415 {
416 // In-place deserialization preserves non-serialized members
417 // (e.g. EulerEvaluatorSettings::_nVars set by the constructor).
418 from_json(j.at(jsonKey), static_cast<T *>(obj)->*member);
419 };
420 meta.writeField = [member, jsonKey](nlohmann::ordered_json &j, const void *obj)
421 {
422 j[jsonKey] = static_cast<const T *>(obj)->*member;
423 };
424 meta.schemaEntry = [desc]()
425 {
426 S::_dnds_ensure_registered();
428 };
429 detail::applyTags(meta, std::forward<Tags>(tags)...);
430 ConfigRegistry<T>::registerField(std::move(meta));
431 }
432
433 // ---- field_array_of<S>(): std::vector<S> ----
434
435 /// @brief Register a `std::vector<S>` field (array of sub-objects).
436 template <typename S, typename... Tags>
437 void field_array_of(std::vector<S> T::*member, const char *jsonKey, const char *desc, Tags &&...tags)
438 {
439 FieldMeta meta;
440 meta.name = jsonKey;
441 meta.description = desc;
443 meta.readField = [member, jsonKey](const nlohmann::ordered_json &j, void *obj)
444 {
445 static_cast<T *>(obj)->*member = j.at(jsonKey).template get<std::vector<S>>();
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]() -> nlohmann::ordered_json
452 {
453 nlohmann::ordered_json s;
454 s["type"] = "array";
455 s["description"] = desc;
456 S::_dnds_ensure_registered();
457 s["items"] = ConfigRegistry<S>::emitSchema();
458 return s;
459 };
460 detail::applyTags(meta, std::forward<Tags>(tags)...);
461 ConfigRegistry<T>::registerField(std::move(meta));
462 }
463
464 // ---- field_map_of<S>(): std::map<std::string, S> ----
465
466 /// @brief Register a `std::map<std::string, S>` field.
467 template <typename S, typename... Tags>
468 void field_map_of(std::map<std::string, S> T::*member, const char *jsonKey, const char *desc, Tags &&...tags)
469 {
470 FieldMeta meta;
471 meta.name = jsonKey;
472 meta.description = desc;
474 meta.readField = [member, jsonKey](const nlohmann::ordered_json &j, void *obj)
475 {
476 static_cast<T *>(obj)->*member =
477 j.at(jsonKey).template get<std::map<std::string, S>>();
478 };
479 meta.writeField = [member, jsonKey](nlohmann::ordered_json &j, const void *obj)
480 {
481 j[jsonKey] = static_cast<const T *>(obj)->*member;
482 };
483 meta.schemaEntry = [desc]() -> nlohmann::ordered_json
484 {
485 nlohmann::ordered_json s;
486 s["type"] = "object";
487 s["description"] = desc;
488 S::_dnds_ensure_registered();
489 s["additionalProperties"] = ConfigRegistry<S>::emitSchema();
490 return s;
491 };
492 detail::applyTags(meta, std::forward<Tags>(tags)...);
493 ConfigRegistry<T>::registerField(std::move(meta));
494 }
495
496 // ---- field_json(): opaque JSON blob ----
497
498 /// @brief Register an opaque `nlohmann::ordered_json` field.
499 template <typename... Tags>
500 void field_json(nlohmann::ordered_json T::*member, const char *jsonKey, const char *desc, Tags &&...tags)
501 {
502 FieldMeta meta;
503 meta.name = jsonKey;
504 meta.description = desc;
506 meta.readField = [member, jsonKey](const nlohmann::ordered_json &j, void *obj)
507 {
508 static_cast<T *>(obj)->*member = j.at(jsonKey);
509 };
510 meta.writeField = [member, jsonKey](nlohmann::ordered_json &j, const void *obj)
511 {
512 j[jsonKey] = static_cast<const T *>(obj)->*member;
513 };
514 meta.schemaEntry = [desc]() -> nlohmann::ordered_json
515 {
516 nlohmann::ordered_json s;
517 s["description"] = desc;
518 return s;
519 };
520 detail::applyTags(meta, std::forward<Tags>(tags)...);
521 ConfigRegistry<T>::registerField(std::move(meta));
522 }
523
524 // ---- field_json_schema(): opaque JSON blob with explicit schema ----
525
526 /// @brief Register an opaque `nlohmann::ordered_json` field with a
527 /// user-supplied schema generator.
528 ///
529 /// Use this for heterogeneous structures (e.g. arrays of discriminated
530 /// union objects) where automatic schema inference is not possible.
531 ///
532 /// @param member Pointer-to-member.
533 /// @param jsonKey JSON key name.
534 /// @param desc Human-readable description.
535 /// @param schemaFn Callable `() -> ordered_json` returning the
536 /// full JSON Schema for this field.
537 /// @param tags Optional tag objects.
538 template <typename FSchema, typename... Tags>
539 void field_json_schema(nlohmann::ordered_json T::*member,
540 const char *jsonKey, const char *desc,
541 FSchema &&schemaFn, Tags &&...tags)
542 {
543 FieldMeta meta;
544 meta.name = jsonKey;
545 meta.description = desc;
547 meta.readField = [member, jsonKey](const nlohmann::ordered_json &j, void *obj)
548 {
549 static_cast<T *>(obj)->*member = j.at(jsonKey);
550 };
551 meta.writeField = [member, jsonKey](nlohmann::ordered_json &j, const void *obj)
552 {
553 j[jsonKey] = static_cast<const T *>(obj)->*member;
554 };
555 meta.schemaEntry = [fn = std::forward<FSchema>(schemaFn)]() -> nlohmann::ordered_json
556 {
557 return fn();
558 };
559 detail::applyTags(meta, std::forward<Tags>(tags)...);
560 ConfigRegistry<T>::registerField(std::move(meta));
561 }
562
563 // ---- check(): cross-field validation ----
564
565 /// @brief Register a context-free cross-field check.
566 /// @param f Lambda `(const T&) -> CheckResult`.
567 template <typename F>
568 auto check(F &&f) -> decltype(f(std::declval<const T &>()), void())
569 {
571 [fn = std::forward<F>(f)](const void *obj) -> CheckResult
572 {
573 return fn(*static_cast<const T *>(obj));
574 });
575 }
576
577 /// @brief Register a context-free cross-field check with a message string.
578 /// @param msg Error message shown when the check fails.
579 /// @param pred Lambda `(const T&) -> bool`, returns true if the check passes.
580 template <typename F>
581 void check(const char *msg, F &&pred)
582 {
584 [message = std::string(msg), fn = std::forward<F>(pred)](const void *obj) -> CheckResult
585 {
586 bool ok = fn(*static_cast<const T *>(obj));
587 return CheckResult{ok, ok ? "" : message};
588 });
589 }
590
591 // ---- check_ctx(): context-aware cross-field validation ----
592
593 /// @brief Register a context-aware cross-field check.
594 /// @param f Lambda `(const T&, const ConfigContext&) -> CheckResult`.
595 template <typename F>
596 auto check_ctx(F &&f) -> decltype(f(std::declval<const T &>(), std::declval<const ConfigContext &>()), void())
597 {
599 [fn = std::forward<F>(f)](const void *obj, const ConfigContext &ctx) -> CheckResult
600 {
601 return fn(*static_cast<const T *>(obj), ctx);
602 });
603 }
604
605 // ---- post_read(): hook called after all fields are deserialized ----
606
607 /// @brief Register a post-read hook for recomputing derived quantities.
608 /// @param f Lambda `(T&) -> void`, called after readFromJson completes.
609 template <typename F>
610 void post_read(F &&f)
611 {
612 ConfigRegistry<T>::registerPostReadHook(std::forward<F>(f));
613 }
614 };
615
616} // namespace DNDS
617
618// ============================================================================
619// DNDS_FIELD(member, description, tags...)
620// ============================================================================
621/// @brief Register a field inside a DNDS_DECLARE_CONFIG body.
622///
623/// Auto-stringifies the member name so you never write it twice.
624/// The JSON key equals the C++ member name.
625///
626/// @param name_ Member name (unquoted).
627/// @param desc_ Description string literal.
628/// @param ... Zero or more DNDS::Config tag objects.
629///
630/// @code
631/// DNDS_FIELD(CFL, "CFL for implicit dt", DNDS::Config::range(0));
632/// @endcode
633#define DNDS_FIELD(name_, desc_, ...) \
634 config.field(&T::name_, #name_, desc_, ##__VA_ARGS__)
635
636// ============================================================================
637// DNDS_DECLARE_CONFIG(Type)
638// ============================================================================
639/// @brief Open a config section registration body.
640///
641/// Expands to:
642/// - `using T = Type` (so DNDS_FIELD can reference `&T::member`).
643/// - Lazy `_dnds_ensure_registered()` with one-time init guard.
644/// - Friend `to_json` / `from_json` calling ensureRegistered first.
645/// - `schema()`, `validate()`, `validateWithContext()`, `validateKeys()`.
646/// - Opens `static void _dnds_do_register(ConfigSectionBuilder<T>& config)`
647/// — the user provides the `{ ... }` body after the macro.
648///
649/// @code
650/// struct MySection
651/// {
652/// real a = 1.0;
653/// int b = 42;
654///
655/// DNDS_DECLARE_CONFIG(MySection)
656/// {
657/// DNDS_FIELD(a, "The a parameter", DNDS::Config::range(0));
658/// DNDS_FIELD(b, "The b parameter");
659/// }
660/// };
661/// @endcode
662#define DNDS_DECLARE_CONFIG(Type_) \
663 using T = Type_; \
664 static void _dnds_ensure_registered() \
665 { \
666 static bool done = false; \
667 if (done) return; \
668 done = true; \
669 ::DNDS::ConfigSectionBuilder<Type_> config; \
670 _dnds_do_register(config); \
671 } \
672 friend void to_json(nlohmann::ordered_json &j, const Type_ &t) \
673 { \
674 Type_::_dnds_ensure_registered(); \
675 ::DNDS::ConfigRegistry<Type_>::writeToJson(j, t); \
676 } \
677 friend void from_json(const nlohmann::ordered_json &j, Type_ &t) \
678 { \
679 Type_::_dnds_ensure_registered(); \
680 ::DNDS::ConfigRegistry<Type_>::readFromJson(j, t); \
681 } \
682 static nlohmann::ordered_json schema(const std::string &desc = "") \
683 { \
684 Type_::_dnds_ensure_registered(); \
685 return ::DNDS::ConfigRegistry<Type_>::emitSchema(desc); \
686 } \
687 std::vector<::DNDS::CheckResult> validate() const \
688 { \
689 Type_::_dnds_ensure_registered(); \
690 return ::DNDS::ConfigRegistry<Type_>::validate(*this); \
691 } \
692 std::vector<::DNDS::CheckResult> validateWithContext( \
693 const ::DNDS::ConfigContext &ctx) const \
694 { \
695 Type_::_dnds_ensure_registered(); \
696 return ::DNDS::ConfigRegistry<Type_>::validateWithContext(*this, ctx); \
697 } \
698 static void validateKeys(const nlohmann::ordered_json &j) \
699 { \
700 Type_::_dnds_ensure_registered(); \
701 ::DNDS::ConfigRegistry<Type_>::validateKeys(j); \
702 } \
703 static void _dnds_do_register(::DNDS::ConfigSectionBuilder<Type_> &config)
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.
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