Skip to content

JSON schema generation#723

Open
ad-cqc wants to merge 6 commits intomainfrom
ad-cqc/ref-schema
Open

JSON schema generation#723
ad-cqc wants to merge 6 commits intomainfrom
ad-cqc/ref-schema

Conversation

@ad-cqc
Copy link
Copy Markdown

@ad-cqc ad-cqc commented May 5, 2026

Collapses Palace's three disconnected schema sources: the C++ structs, the hand-written JSON schemas under scripts/schema/, and PR #716's annotated schema into one C++ source of truth. The new palace_emit_schema binary generates config-schema.json at build time from reflect-cpp-annotated mirrors of the config structs; the embed pipeline picks up the generated file and bakes it into libpalace via the existing embed_schema.cmake helper. The hand-written schema files are deleted.

Descriptions in the annotated types are transcribed verbatim from awslabs/palace#716, so the doc-generator output matches PR #716 by construction. CMAKE_CXX_STANDARD bumps to C++20 (reflect-cpp floor).

Test plan

  • C++20libpalace + palace build clean.
  • palace_emit_schema produces a 2655-line schema; embedded header regenerates via the CMake custom command.
  • Spot checks against PR [WIP] Auto-gen config docs #716 descriptions pass verbatim.
  • 53 advanced / 4 deprecated flags land on the correct properties.
  • test/unit/test-schema.cpp (sample configs validate against the embedded schema).
  • scripts/schema/ValidateConfig.jl against the build-generated path.
  • PR 716's docs/generate_config_docs.jl against the generated schema. The expected output should match PR 716's committed reference.md modulo trivial ordering.

ad-cqc added 6 commits May 4, 2026 21:41
Replaces the hand-written JSON schema files under scripts/schema/ with a
build-time pipeline that emits a single config-schema.json from
reflect-cpp-annotated C++ types under palace/schema/types/. Descriptions
are transcribed verbatim from PR #716 so generated docs stay in sync
with the configuration structs by construction.

- palace/schema/utils/: schema-generator utilities (reflect-cpp
  post-emit passes for defaults, required pruning, additionalProperties,
  TaggedUnion tag descriptions, anyOf to oneOf rewrite).
- palace/schema/types/: annotated mirrors of Palace's config structs.
- palace/schema/emit_schema.cpp: standalone helper invoked by CMake to
  produce build/generated/schema/config-schema.json, which is then
  embedded via the existing embed_schema.cmake helper.
- cmake/EmbedSchema.cmake: rewritten to drive palace_emit_schema.
- palace/utils/jsonschema.cpp: drop the external-$ref branch; the
  single-file schema resolves refs in-document via $defs.
- scripts/schema/: hand-written per-section JSONs removed.

Bumps CMAKE_CXX_STANDARD to 20 (reflect-cpp floor). Runtime config
parsing is untouched in this phase.
Closes the three gaps between Palace's generated schema and PR #716's
hand-written version:

- Per-enum-value descriptions: `PALACE_SCHEMA_ENUM(E, (V, "desc"), ...)`
  declares an `enum class` and the matching `enum_descriptions<E>`
  specialization in one place; the post-emit `inject_enum_descriptions`
  pass rewrites `{"type":"string","enum":[...]}` to
  `{"oneOf":[{"const":V,"description":"..."},...]}`. Empty descriptions
  opt an enumerator out of the prose (bare `{"const": V}`).
- x-palace-advanced / x-palace-deprecated: new
  `PALACE_SCHEMA_DESC_ADVANCED` / `_DEPRECATED` macros attach the flag at
  the field declaration via a `FieldFlagTag<"FieldName">`-keyed hidden
  friend; the walker finds it via ADL on the enclosing struct and the
  `inject_custom_keywords` pass stamps `x-palace-<flag>: true` onto the
  matching property. 53 advanced + 4 deprecated fields now land
  correctly.
- Root-level conditionals: emit_schema.cpp parses a verbatim copy of PR
  716's `allOf`/`if`/`then` block (driven/eigenmode/transient implying
  the matching `Solver.<Mode>` requirement) and splices it into the
  root. The block isn't expressible from the C++ types alone.
Introduce `version.hpp` with a `schema_version` constant following
the SchemaVer format (MODEL-REVISION-ADDITION), decoupling schema
compatibility tracking from `PROJECT_VERSION`. Remove the
`PALACE_VERSION` compile definition from the emit_schema target and
use the new constant directly instead.
…om $defs

- cmake/EmbedSchema.cmake: after palace_emit_schema runs, copy the generated
  config-schema.json into scripts/schema/ via copy_if_different. The copy is
  part of the default build (palace_schema_file ALL target), so the committed
  schema stays in sync with the annotated types. Downstream tooling (docs,
  Julia validator) consumes the stable scripts/schema/ path; embedding still
  reads the build-tree copy. The existing check-config GitHub Actions job
  validates example configs against this file unchanged.

- SchemaOptions gains `defs_prefix`. When non-empty, a final
  `strip_defs_prefix` pass renames each `$defs` entry and rewrites every
  matching `$ref` string to drop the prefix. emit_schema.cpp sets it to
  `"palace__schema__"`, so the published schema carries short names
  (`ProblemData`, `LinearSolverData`, ...) instead of reflect-cpp's
  fully-qualified `palace__schema__ProblemData`.

- scripts/schema/config-schema.json is the baseline generated output —
  committed alongside the types so `./scripts/validate-config` and
  ValidateConfig.jl work out of the box.
Replace the previous "prune unneeded from required" heuristic with an
explicit allow-list: fields marked PALACE_SCHEMA_DESC_REQUIRED (and
PALACE_SCHEMA_TAG for tagged-union discriminators) are the only entries
in each `$defs` required array. The marker is expressed as a subtype of
rfl::Description — DescRequired / DescAdvanced / DescDeprecated — so
detection is a pure compile-time type trait, with no hidden-friend or
ADL tricks and no PALACE_SCHEMA_SELF boilerplate on the struct.

Also:
 - Drop `default` emission on $ref-bearing properties; the referenced
   $defs entry already carries defaults on its own fields.
 - Flatten rfl::Validator-induced `allOf: [{minimum,type},{maximum,type}]`
   into a single `{minimum, maximum, type}` body.
 - Introduce schema_oneof_required<T> trait and inject_oneof_required
   pass; specialize for LumpedPort / SurfaceCurrent to match PR 716's
   "exactly one of Attributes or Elements" item constraint.
 - Remove std::optional usage from annotated types; every field has a
   concrete default, optional-ness is expressed only through the
   absence of PALACE_SCHEMA_DESC_REQUIRED.
 - Strip the `Data` suffix from all struct names (Boundary, Model,
   LumpedPort, ...); three collisions with existing enums
   (SurfaceFlux, InterfaceDielectric, LinearSolver) are renamed to
   SurfaceFluxProbe, DielectricInterface, LinearSolverConfig.

The generated config-schema.json reflects all of the above: required
arrays now match PR 716 per-type, every validator property is flat,
LumpedPort / SurfaceCurrent carry the oneOf mutex, and no $ref
property carries a default.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant