Skip to content

flatten/allof: resolveNumberRange ignores OpenAPI 3.1 numeric exclusive bounds #868

@reuvenharrison

Description

@reuvenharrison

Context

After #867 bumps kin-openapi to v0.136.0, resolveNumberRange in flatten/allof/merge_allof.go correctly preserves OpenAPI 3.0 exclusive-bound behavior but does not handle OpenAPI 3.1 numeric exclusiveMinimum / exclusiveMaximum values when merging allOf schemas.

In v0.136.0, Schema.ExclusiveMin and Schema.ExclusiveMax are now ExclusiveBound (a union of *bool for 3.0 style and *float64 for 3.1 style). The merge function still iterates only over the Min / Max slices and ignores the ExclusiveMin.Value / ExclusiveMax.Value axis.

Failure cases

Case 1: 3.1 spec with exclusiveMinimum only (no minimum)

allOf:
  - exclusiveMinimum: 10
  - exclusiveMinimum: 5

Current behavior: collection.Min[0] and collection.Min[1] are both nil, so the merge loop body never runs. Output has Min == nil and ExclusiveMin == ExclusiveBound{}. Both exclusive minimums are silently lost. The merged schema would accept any number, even ones below 10.

Expected behavior: merged schema should have exclusiveMinimum: 10 (the more restrictive of the two).

Case 2: 3.1 spec mixing minimum and exclusiveMinimum

allOf:
  - minimum: 0
    exclusiveMinimum: 5    # effective min: > 5
  - minimum: 10            # effective min: >= 10

Current behavior: merge picks Min: 10 from schema 2 and copies its empty ExclusiveMin. The result happens to be correct (>= 10 is more restrictive than > 5), but only by coincidence; the comparison logic isn't actually consulting the exclusive bound values.

The mirror problem applies to ExclusiveMax.

Proposed fix

resolveNumberRange should compare across both axes:

  1. Translate each schema's effective minimum to a single comparable value (treating exclusiveMinimum: x as x + ε).
  2. Pick the maximum effective minimum across the candidate set.
  3. Emit the result in 3.1 form (ExclusiveMin.Value) when the most-restrictive bound came from a numeric exclusive, or 3.0 form (Min + ExclusiveMin.Bool) otherwise.

Mirror logic for ExclusiveMax.

A more conservative approach: keep 3.0 logic unchanged, and add a separate 3.1-aware merge path triggered when any input has ExclusiveBound.Value set. Either approach is valid; the cleaner option is probably to unify them.

Test cases to add

  • allOf with two exclusiveMinimum values, both 3.1-style numeric. Expect the higher one in the result.
  • allOf mixing minimum: x and exclusiveMinimum: y where y > x. Expect exclusiveMinimum: y in the result.
  • allOf mixing minimum: x and exclusiveMinimum: y where x > y. Expect minimum: x in the result.
  • All three above mirrored for exclusiveMaximum and maximum.
  • Ensure existing 3.0-style tests (boolean exclusiveMinimum/exclusiveMaximum) still pass.

Related

  • Surfaced during review of chore: bump kin-openapi to v0.136.0 #867 (the v0.136.0 bump).
  • Affects only flatten/allof so far. Other parts of oasdiff that read ExclusiveMin/ExclusiveMax should be audited for similar 3.1 gaps.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions