Skip to content

[WIP] JSON schema generation#723

Open
ad-cqc wants to merge 18 commits into
mainfrom
ad-cqc/ref-schema
Open

[WIP] JSON schema generation#723
ad-cqc wants to merge 18 commits into
mainfrom
ad-cqc/ref-schema

Conversation

@ad-cqc

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

Copy link
Copy Markdown
Contributor

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 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 13 commits June 23, 2026 11:59
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.
Simplify and standardize struct names across the schema type
definitions. Remove redundant suffixes (e.g. "Boundary", "Probe",
"Interface") where context is already clear, and expand abbreviated
names for explicitness (e.g. PalaceConfig → PalaceConfiguration).
Rename enum SurfaceFlux → SurfaceFluxType to avoid collision with
the similarly-named struct.
Introduce a shared `Direction` type alias
(`rfl::Variant<DirectionLabel, std::array<double, 3>>`) in
`schema/types/common.hpp`, replacing the four bare `std::string` Direction
fields across `Element`, `LumpedPort`, `SurfaceCurrent`, and `CurrentDipole`
so the emitted schema matches PR 716's `anyOf: [pattern-string, 3-array]`
shape. `DirectionLabel` is a `rfl::Pattern<"^[+\\-]?[XYZRxyzr]$", ...>`
giving the pattern-validated string arm; fields default to
`DirectionLabel("+X")`.

Teach the type-graph walker about `rfl::Variant` so it recurses into
alternatives without treating the variant itself as a reflectable
aggregate (which would default-construct the validated-string arm against
an empty value and throw).

Add a marker-driven alias-hoisting pass: specialize
`schema_alias_name<T>` with a non-empty `value` and the post-emit
`inject_field_aliases` pass promotes every matching field's inline body
into a shared `$defs[alias]` entry, leaving
`{"$ref": "#/$defs/<alias>", ...}` at the field site with
`description`/`default` preserved as siblings. Applied to `Direction` so
all four use sites share a single `$defs/Direction` body instead of
duplicating the `anyOf` inline. Adding another alias is a two-line change:
the `using` plus a trait specialization.

Sync `scripts/schema/config-schema.json` with the new emission.
Switch DirectionLabel from rfl::Pattern (regex) to rfl::Literal
enumerating all 24 valid axis keywords. This produces a clearer
JSON schema with an explicit "enum" array instead of a regex pattern.

Default-initialize Direction fields with {} rather than an explicit
DirectionLabel("+X"), as the Literal's first alternative ("R") now
serves as the natural default.
- Add Makefile `build` target with Homebrew LLVM/OpenBLAS config and
  parallel build support via NPROCS
- Fix LIBXSMM macOS build by passing -isysroot via CFLAGS when
  CMAKE_OSX_SYSROOT is set (fixes missing headers with non-Xcode
  compilers)
- Disable MFEM test step to speed up builds
- Rename Direction schema type to PortDirection to disambiguate from
  general direction usage
- Add variant arm alias support in schema visitor for improved
  JSON schema codegen output
…enums

Replace the custom `x-schema-version` root key with the spec-blessed
`$id: urn:palace:schema:<version>`, placed right after `$schema`.
`PALACE_SCHEMA_ENUM` now auto-emits a `schema_alias_name<EnumName>`
specialization so every described enum hoists to its own `$defs` entry
without per-enum boilerplate.

Other changes folded into the regen:

* Rename `Domain` -> `Domains`, `LinearSolverConfig` -> `Linear` for
  consistency with their schema slot names.
* `PalaceConfiguration::SchemaVersion` is now required.
* Tagged-union discriminator arms emit as `{const, description}` instead
  of the verbose `{type, enum, default}` reflect-cpp default.
* New `inject_field_composition` pass handles `anyOf` -> `oneOf`
  rewrites on nested variant fields (the root-level pass only touches
  the top object).
* `container_inner` helper lets the variant-composition walk descend
  into `vector` / `array` element types without force-instantiating
  `value_type` on non-containers.
…-array items

Add `MaterialAxes` to `Material` as a fixed 3x3 array of doubles, default
identity matrix, matching PR 716's `$defs/Vector3`-backed inner-row
shape.

Make this layout possible by:

  - Promoting `std::array<double, 3>` to a named
    `palace::schema::Vector3` alias and switching every Cartesian field
    site (Center, Translation, FloquetWaveVector, BoundingBox*,
    Direction's numeric arm, ...) to use it.

  - Adding `inject_nested_items_aliases`, a small post-emit pass that
    rewrites `properties[field]/items` to `{"$ref": "#/$defs/<alias>"}`
    when the field's container element type specializes
    `schema_alias_name`. The existing `inject_field_aliases` pass only
    fires at the top-level field body, so nested 3-arrays (the inner
    row of `MaterialAxes`) need this targeted rewrite to share the
    canonical `$defs/Vector3` entry. Reusable for any future
    `vector<Vector3>` / `array<Vector3, N>` field.
@ad-cqc ad-cqc force-pushed the ad-cqc/ref-schema branch from 3ee4ed8 to dff9037 Compare June 23, 2026 21:57
ad-cqc added 5 commits June 23, 2026 15:14
The enum types `AdaptiveCircuitSynthesisDomainOrthogonalization` and
`Excitation` are now fully qualified with `::palace::schema::` to avoid
potential name resolution conflicts with struct member names in the
PALACE_SCHEMA_DESC macros.
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