Skip to content

Support OpenAPI 3.1 top-level webhooks (#375) - #377

Merged
jemacineiras merged 7 commits into
sngular:mainfrom
joseegman-idoneea:feat/375-openapi-3.1-webhooks
Jul 23, 2026
Merged

Support OpenAPI 3.1 top-level webhooks (#375)#377
jemacineiras merged 7 commits into
sngular:mainfrom
joseegman-idoneea:feat/375-openapi-3.1-webhooks

Conversation

@joseegman-idoneea

Copy link
Copy Markdown
Contributor

What

Adds OpenAPI 3.1 top-level webhooks support — a checklist item of #375.

Stacked on #376 (array-valued type). This branch is based on that PR's branch, so until
#376 merges the diff here also shows its commit. Please merge #376 first, then this. Both
are part of the 3.1-support milestone, so the version stays 6.4.0 (no extra bump).

Background

OpenAPI 3.1 adds a top-level webhooks object: a map of named Path Item Objects describing
out-of-band requests. The generator only iterated paths, so webhooks were silently ignored —
no handler interface and no payload models were generated.

Change

  • Add OpenApiUtil.mergeWebhooksIntoPaths(openApi), invoked right after parsing in
    OpenApiGenerator.processFile. Each webhook (keyed by name) is merged into paths under a
    "/"-prefixed key, so the existing path pipeline generates a handler interface for its
    operations and the request/response payload models. paths is created if the contract has
    none; existing paths entries are never overwritten.
  • Add OpenApiUtil.WEBHOOKS constant.

This reuses the whole existing grouping / path-mapping / model-extraction pipeline rather than
adding a parallel one, keeping the change small.

Tests

New 3.1.0 webhooks-only fixture testWebhooks:

webhooks:
  newPet:
    post:
      operationId: newPetWebhook
      tags: [webhook]
      requestBody:
        content: { application/json: { schema: { $ref: '#/components/schemas/Pet' } } }
      responses: { '200': { description: Notification acknowledged } }

generates:

  • NewPetApi.java — handler interface with POST /newPet (newPetWebhook) consuming PetDTO
  • model/PetDTO.java — the payload model

OpenApiGeneratorTest: Tests run: 46, Failures: 0, Errors: 0; checkstyle:check — 0 violations.

Design note / scope

Webhooks are represented as endpoints in a generated handler interface (the common approach for a
webhook receiver), reusing the path pipeline; the primary value is that webhook payload
schemas become models
. $ref'd webhook Path Items (external files) depend on the Path-Item-$ref
resolution in #372/#374; inline webhook Path Items are covered here. Remaining #375 items
(examples array, $ref siblings) are separate follow-ups.

🤖 Generated with Claude Code

joseegarcia and others added 2 commits July 22, 2026 15:44
…able)

OpenAPI 3.1 / JSON Schema 2020-12 allows a schema's `type` to be an array,
e.g. type: ["string", "null"] (the nullable idiom that replaces 3.0's
`nullable: true`, and general unions). ApiTool.getType() read the type via
JsonNode.textValue(), which returns null for an array node and collapsed to
"", so every isObject/isArray/isString/isNumber/isDateTime predicate and
MapperUtil.getSimpleType failed -> the generator silently produced wrong or
empty types for those fields.

- ApiTool.getType: when `type` is an array, resolve to the first non-"null"
  entry ("null" only marks the type as nullable). All the isX predicates and
  getSimpleType then work unchanged for union/nullable types.
- MapperUtil.processNumber: read the type via ApiTool.getType instead of
  schema.get("type").asText(), so array-valued numeric types resolve.
- Add TypeConstants.NULL.
- Add a 3.1.0 regression fixture (testOpenApi31Types) covering ["string",
  "null"], ["integer","null"], ["number","null"]+double, int64 and a plain
  array, with golden assets.

Bumps version 6.3.2 -> 6.4.0 across the engine, maven and gradle modules.
Non-array `type` handling is unchanged, so existing 3.0 specs are unaffected.

Follow-up 3.1 items (examples array, $ref siblings, webhooks) tracked in sngular#375.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OpenAPI 3.1 adds a top-level `webhooks` object: a map of named Path Item
Objects describing out-of-band requests. The generator only iterated
`paths`, so webhooks were ignored - no handler interface, no payload models.

Add OpenApiUtil.mergeWebhooksIntoPaths, invoked right after parsing in
OpenApiGenerator.processFile. Each webhook (keyed by name) is merged into
`paths` under a "/"-prefixed key so the existing pipeline generates a
handler interface for its operations and the request/response payload
models. `paths` is created if the contract has none, and existing `paths`
entries are never overwritten.

Adds a 3.1.0 webhooks-only regression fixture (testWebhooks) with golden
assets (NewPetApi + PetDTO).

Stacked on the 6.4.0 array-type work (sngular#376); both are part of the 3.1
support milestone, so the version stays 6.4.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codacy-production

codacy-production Bot commented Jul 22, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 15 complexity · 0 duplication

Metric Results
Complexity 15
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

joseegarcia and others added 2 commits July 22, 2026 16:12
Webhook operations normally omit `tags`, but createOperation requires
one (Objects.requireNonNull), so a realistic tag-less webhook crashed
with a NullPointerException in the default by-url grouping mode - and
was silently skipped in tag-grouping mode. The prior test masked this
by giving the webhook explicit tags.

mergeWebhooksIntoPaths now defaults each webhook operation's tags to the
webhook name when absent/empty, and skips blank keys (guards the
pathUrl.split()[1] grouping). The testWebhooks fixture is updated to the
realistic tag-less shape to cover this.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
getType: extract array-type resolution into a helper. A genuine multi-type
union (e.g. ["string","integer"]) now logs a warning and uses the first
concrete type; a "null"-only/empty type array falls back to `object`
instead of an empty type string.

Adds edge-case regression fixtures:
- testOpenApi31Union: union -> String (+warning), ["null"] -> Object
- testWebhookPathCollision: webhook name colliding with an existing path
  is dropped (existing paths take precedence).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jemacineiras
jemacineiras self-requested a review July 22, 2026 16:34
@jemacineiras jemacineiras self-assigned this Jul 22, 2026
@jemacineiras jemacineiras added enhancement New feature or request release Adding this tag to a PR will cause a release on merge labels Jul 22, 2026
jemacineiras
jemacineiras previously approved these changes Jul 22, 2026
Gradle 9.x plugin validation fails the build when a task type is neither
@CacheableTask nor @DisableCachingByDefault. OpenApiTask and AsyncApiTask
are code generators whose spec-file inputs are not declared as cacheable
inputs, so caching is disabled explicitly with a documented reason.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jemacineiras
jemacineiras merged commit 12fd597 into sngular:main Jul 23, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request release Adding this tag to a PR will cause a release on merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants