Skip to content

Checker: Add OpenAPI 3.1 breaking change rules #793

@reuvenharrison

Description

@reuvenharrison

Overview

Add changelog and breaking change detection rules for OpenAPI 3.1 specific features to the checker package.

Depends on: PR #791 (diff support for OpenAPI 3.1 fields)

Architecture

The diff layer (PR #791) already produces diffs for all 3.1 fields. This issue is about the checker layer that interprets those diffs as breaking/non-breaking changes.

Each checker function has the signature:

func XxxCheck(diffReport *diff.Diff, operationsSources *diff.OperationsSourcesMap, config *Config) Changes

For each new rule: define a change ID constant, add a localization message in `messages.yaml`, implement the checker, register it in `rules.go`, and add tests.


Priority 1 — Critical

1.1 Handle nullable as type array

Problem: In 3.0, nullable is `nullable: true`. In 3.1, it's `type: ["string", "null"]`. The existing nullable checkers use `NullableDiff` which won't fire for 3.1 type arrays. Meanwhile, the existing type-change checkers (`TypeDiff`, `StringsDiff`) will see adding/removing `"null"` in the type array as a type change rather than a nullable change.

Approach: Modify the type-change checkers to filter out `"null"` from `TypeDiff.Added`/`TypeDiff.Deleted` and instead emit nullable change IDs. Key files:

  • `check_request_property_type_changed.go` — filter "null" from TypeDiff, emit `request-property-became-nullable` / `request-property-became-not-nullable`
  • `check_response_property_type_changed.go` — same for response
  • `check_request_property_became_not_nuallable.go` — extend to also check TypeDiff for "null" addition/removal
  • `check_response_property_became_nuallable.go` — same for response
  • `check_types.go` — `isTypeContained()` needs to ignore "null" in type arrays, `getSingleType()` needs to skip "null"

Existing IDs to reuse: `request-property-became-nullable`, `request-property-became-not-nullable`, `request-body-became-nullable`, `request-body-became-not-nullable`, `response-property-became-nullable`, `response-body-became-nullable`

Note: The `ListOfTypesDiff` infrastructure and `list_of_types_core.go` checkers already handle oneOf/anyOf type-list patterns. The type array ("null" handling) is different — it's about the `type` field itself, not composition.

1.2 Webhook breaking change rules

Problem: Webhooks are new in 3.1. `WebhooksDiff` already provides `Added`, `Deleted`, and `Modified` (each Modified webhook is a `*PathDiff`, same structure as path diffs). No checker rules reference `WebhooksDiff` today.

Approach: Two layers:

  1. Webhook-level rules — new checker file:

    • `webhook-added` (INFO)
    • `webhook-removed` (ERR — consumers may depend on it)
  2. Reuse existing operation rules for webhook operations — Each webhook has operations (POST, etc.) that produce the same `PathDiff` structure as paths. Existing checkers iterate `diffReport.PathsDiff.Modified`. To apply them to webhooks, either:

    • (a) Refactor each checker to accept a generic "modified endpoints" map, then call it for both paths and webhooks
    • (b) Merge webhook modified operations into PathsDiff before running checkers (with a `webhook:` prefix)
    • (c) Duplicate the iteration for webhooks in each checker

    Recommended: (b) — merge webhooks into a synthetic paths map with prefixed keys (e.g., `[webhook] newUser`) before calling `CheckBackwardCompatibility`. This gives all 118 existing rules webhook support for free with zero per-checker changes.

New change IDs:

ID Level Message
`webhook-added` INFO `webhook %s added`
`webhook-removed` ERR `webhook %s removed`

Priority 2 — Important

2.1 `const` field rules

When a request property's `const` value changes, consumers sending the old value will be rejected. When a response property's `const` changes, consumers expecting the old value will break.

New change IDs:

ID Level Message
`request-property-const-changed` ERR `the request property %s const value changed from %s to %s`
`request-property-const-added` ERR `the request property %s now requires const value %s`
`request-property-const-removed` INFO `the request property %s no longer requires a const value`
`response-property-const-changed` ERR `the response property %s const value changed from %s to %s`
`response-property-const-added` INFO `the response property %s now has const value %s`
`response-property-const-removed` ERR `the response property %s no longer has a const value`
`request-body-const-changed` ERR `the request body const value changed from %s to %s`
`response-body-const-changed` ERR `the response body const value changed from %s to %s`

Diff field: `SchemaDiff.ConstDiff` (`*ValueDiff`)

2.2 `exclusiveMinimum` / `exclusiveMaximum` numeric rules

In 3.1 these are numbers, not booleans. The existing `ExclusiveMinDiff` / `ExclusiveMaxDiff` already capture changes via the `ExclusiveBound` union type. The existing min/max checkers may need adjustment to handle the numeric form correctly (currently only check `Min`/`Max`, not `ExclusiveMin`/`ExclusiveMax` as standalone bounds).

Files to modify: `check_breaking_min_max.go` — extend existing min/max comparison logic to also consider exclusive bounds when they are numeric values.

2.3 `prefixItems` rules

Tuple validation. Changes to prefixItems items affect the contract. Uses `SubschemasDiff` (same structure as oneOf/anyOf/allOf).

New change IDs:

ID Level Message
`request-property-prefix-items-added` ERR `added prefix items constraint to request property %s`
`request-property-prefix-items-removed` INFO `removed prefix items constraint from request property %s`
`request-property-prefix-items-updated` ERR `prefix items of request property %s changed`
`response-property-prefix-items-added` INFO `added prefix items constraint to response property %s`
`response-property-prefix-items-removed` ERR `removed prefix items constraint from response property %s`
`response-property-prefix-items-updated` ERR `prefix items of response property %s changed`

Diff field: `SchemaDiff.PrefixItemsDiff` (`*SubschemasDiff`)

2.4 `contains` / `minContains` / `maxContains` rules

Array containment constraints. Adding `contains` to a request is breaking (new constraint). Removing from response is breaking.

New change IDs:

ID Level Message
`request-property-contains-added` ERR `added contains constraint to request property %s`
`request-property-contains-removed` INFO `removed contains constraint from request property %s`
`request-property-contains-updated` ERR `contains constraint of request property %s changed`
`request-property-min-contains-increased` ERR `min contains of request property %s increased`
`request-property-min-contains-decreased` INFO `min contains of request property %s decreased`
`request-property-max-contains-decreased` ERR `max contains of request property %s decreased`
`request-property-max-contains-increased` INFO `max contains of request property %s increased`
(mirror for response)

Diff fields: `SchemaDiff.ContainsDiff`, `MinContainsDiff`, `MaxContainsDiff`


Priority 3 — Nice to have

3.1 `patternProperties` rules

Pattern-based property schemas. Adding a new pattern is tightening for requests, loosening for responses.

Diff field: `SchemaDiff.PatternPropertiesDiff` (`*SchemasDiff` — same structure as `PropertiesDiff`)

3.2 `dependentSchemas` / `dependentRequired` rules

Conditional dependencies. Adding a `dependentRequired` entry on a request property is breaking (new constraint).

Diff fields: `SchemaDiff.DependentSchemasDiff` (`*SchemasDiff`), `DependentRequiredDiff` (`*ValueDiff`)

3.3 `propertyNames` rules

Property name validation. Adding/tightening `propertyNames` on a request is breaking.

Diff field: `SchemaDiff.PropertyNamesDiff` (`*SchemaDiff`)

3.4 `unevaluatedItems` / `unevaluatedProperties` rules

Adding these on a request is a new constraint (breaking). Removing from a response may be breaking.

Diff fields: `SchemaDiff.UnevaluatedItemsDiff`, `UnevaluatedPropertiesDiff` (`*SchemaDiff`)

3.5 `if` / `then` / `else` rules

Conditional schema changes. Complex to analyze breaking-ness — treat any change as a potential breaking change.

Diff fields: `SchemaDiff.IfDiff`, `ThenDiff`, `ElseDiff` (`*SchemaDiff`)

3.6 `contentMediaType` / `contentEncoding` / `contentSchema` rules

Changes to content encoding affect how string values are interpreted.

Diff fields: `SchemaDiff.ContentMediaTypeDiff`, `ContentEncodingDiff` (`*ValueDiff`), `ContentSchemaDiff` (`*SchemaDiff`)


Priority 4 — Informational only

These fields don't affect API contracts and only need INFO-level changelog entries:

Field Diff Note
`$id` `SchemaIDDiff` Identity metadata
`$anchor` `AnchorDiff` Fragment identifier
`$dynamicRef` / `$dynamicAnchor` `DynamicRefDiff` / `DynamicAnchorDiff` Dynamic referencing
`$schema` `SchemaDialectDiff` Dialect declaration
`$comment` `CommentDiff` Human-readable comment
`$defs` `DefsDiff` Local schema definitions (only breaking if referenced schemas change, which would be caught by existing property diff rules)
`examples` `ExamplesDiff` Documentation only

Implementation order

  1. 1.2 Webhooks — unblocks all existing rules for webhooks via the merge approach
  2. 1.1 Nullable type arrays — highest user impact, most requested
  3. 2.1 const — straightforward ValueDiff checker
  4. 2.2 exclusiveMin/Max — extend existing min/max checkers
  5. 2.3-2.4 prefixItems, contains — new SubschemasDiff/SchemaDiff checkers
  6. P3 items — incremental additions following established patterns

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions