Skip to content

feat(scaffold): x-altair-* OpenAPI extensions for round-trippable spec fields (#163)#169

Merged
tonydspaniard merged 1 commit into
masterfrom
feat/163-x-altair-extensions
May 30, 2026
Merged

feat(scaffold): x-altair-* OpenAPI extensions for round-trippable spec fields (#163)#169
tonydspaniard merged 1 commit into
masterfrom
feat/163-x-altair-extensions

Conversation

@tonydspaniard
Copy link
Copy Markdown
Member

Closes #163. Part of #160.

Summary

Defines the x-altair-* OpenAPI 3.1 extension family so the framework-specific spec fields the base spec cannot express survive a round trip through OpenAPI. spec:emit-openapi writes them on the forward path; openapi:import reads them on the reverse path. Closes the loop opened by #161 (emitter) and #162 (CLI).

V1 key set

Key Round-trips Schema
x-altair-domain Yes \xe2\x80\x94 spec.domain.{class, invocation} docs/openapi/extensions/x-altair-domain.schema.json
x-altair-persistence Yes \xe2\x80\x94 spec.persistence docs/openapi/extensions/x-altair-persistence.schema.json
x-altair-queue Yes \xe2\x80\x94 spec.queue docs/openapi/extensions/x-altair-queue.schema.json
x-altair-idempotency Carried through; runtime ships later docs/openapi/extensions/x-altair-idempotency.schema.json
x-altair-webhook Carried through; runtime ships later docs/openapi/extensions/x-altair-webhook.schema.json
x-altair-input-location Carried through; needs parameters[] parser support docs/openapi/extensions/x-altair-input-location.schema.json

"Carried through" means the parser preserves the key on OperationModel::$extensions and the receipt warns when the runner sees it but doesn't yet act on it. The schema is published so authoring tooling can validate.

Forward (spec:emit-openapi)

OpenApiEmitter now writes:

  • x-altair-domain on every operation (the domain class is always set on a spec)
  • x-altair-persistence when spec.persistence is non-null
  • x-altair-queue (list form) when spec.queue is non-empty

The Petstore-class golden snapshot was regenerated to include the new x-altair-domain block.

Reverse (openapi:import)

  • OperationModel grew an extensions: array<string, mixed> field. Backwards-compatible default [].
  • OpenApiParser extracts every key starting with x-altair- from each operation onto that field. Unknown keys ride along verbatim (forward compat).
  • OperationMapper reads x-altair-domain / x-altair-persistence / x-altair-queue when building the spec structure, preferring them over the path-derived defaults from PathDeriver.
  • OpenApiImportRunner collects any x-altair-* key outside the v1 known set into ImportReceipt::$warnings so v1 imports never silently drop a key a future release will rely on.

Round-trip example (from docs/openapi/extensions.md)

# Altair spec
persistence:
  entity:
    class: App\\User\\User
    table: users
    fields:
      id: { type: uuid, primary: true }

emits:

# OpenAPI fragment
x-altair-persistence:
  entity:
    class: App\\User\\User
    table: users
    fields:
      id: { type: uuid, primary: true }

which openapi:import recovers back into spec.persistence byte-for-byte (not the inferred persistence block the --persistence=cycle flag synthesises).

Tests

  • OpenApiEmitterExtensionsTest (4) \xe2\x80\x94 forward
  • OpenApiParserExtensionsTest (2) \xe2\x80\x94 parser captures known + unknown x-altair-*
  • OperationMapperExtensionsTest (5) \xe2\x80\x94 reverse mapping per extension
  • OpenApiImportExtensionsTest (5) \xe2\x80\x94 end-to-end round trip + unknown-key warning + no-false-positive warnings on known keys
  • Total: +16 new tests, all green; the existing snapshot regeneration brought the count to +1 in the OpenApiEmitter golden file.

Test plan

  • composer cs \xe2\x80\x94 green
  • composer stan \xe2\x80\x94 green
  • composer rector (full tree, no cache) \xe2\x80\x94 green
  • composer test \xe2\x80\x94 6220 tests (+16 new), 0 new failures (5 pre-existing environmental errors unchanged)
  • bin/altair manifest:generate \xe2\x80\x94 clean
  • Real-CLI smoke: openapi:import on the Posts benchmark fixture \xe2\x80\x94 ok=true, warnings=[]

Out of scope (per #160)

…c fields (#163)

Defines the x-altair-* extension family so framework-specific spec
fields survive a round trip through OpenAPI 3.1:

- x-altair-domain         spec.domain (class, invocation)
- x-altair-persistence    spec.persistence (entity + repository + fields)
- x-altair-queue          spec.queue (dispatches as a list)
- x-altair-idempotency    reserved; key + schema only this release
- x-altair-webhook        reserved; key + schema only this release
- x-altair-input-location reserved; needs parameters[] parser support

Forward (spec:emit-openapi)
- OpenApiEmitter writes x-altair-domain on every operation and
  x-altair-persistence / x-altair-queue when the spec carries those
  blocks.

Reverse (openapi:import)
- OperationModel grew an extensions: array<string, mixed> field.
- OpenApiParser extracts every x-altair-* key on each operation onto
  that field so unknown keys ride along verbatim (forward compat).
- OperationMapper reads x-altair-domain / x-altair-persistence /
  x-altair-queue when building the spec structure, so an imported
  endpoint keeps the original domain FQCN, persistence block, and
  queue map instead of falling back to path-derived defaults.
- The runner surfaces a warning entry in ImportReceipt.warnings for
  any x-altair-* key outside the v1 known set.

Documentation
- docs/openapi/extensions.md walks through the v1 key set,
  forward/reverse semantics, round-trip example, and what does not
  round-trip yet.
- docs/openapi/extensions/*.schema.json — Draft 2020-12 JSON Schemas
  for each key.

Tests
- 16 new tests across emitter, parser, mapper, and runner extension
  coverage. Existing OpenApiEmitterTest golden snapshot regenerated
  to include the new x-altair-domain block.

Part of #160. Closes #163.
@tonydspaniard tonydspaniard merged commit 0a19d77 into master May 30, 2026
4 checks passed
@tonydspaniard tonydspaniard deleted the feat/163-x-altair-extensions branch May 30, 2026 20:50
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.

x-altair-* OpenAPI extensions \xe2\x80\x94 round-trippable persistence, queue, webhook, idempotency

1 participant