Skip to content

fix(spec): keep the surrounding schema's keys when 3.0 conversion inlines a nullable reference #870

Open

Description

@yeongseon

Context

When OpenAPI 3.0 conversion collapses a nullable union whose only non-null member is a $ref (anyOf: [{"$ref": ...}, {"type": "null"}]), it inlines the referenced schema and merges the keys of the surrounding schema into it. On a conflict the referenced schema's value wins and the surrounding one is dropped without a warning, although the code comments say the field-level value wins. A realistic trigger is Optional[Model] = Field(default=None, description="..."): the 3.1 output shows the field description, the 3.0 output shows the model's docstring instead.

The automated review of #863 found related cases with allOf. They reproduce without any type array and on main.

Current State

Measured on main (be77a15) and with #863 applied, Python 3.11, Pydantic 2.14.2, openapi_version="3.0.0". Generation succeeds, no structured warning is emitted, and strict=True does not raise.

Field description (Pydantic model Account with home: Address | None = Field(default=None, description="Where the account holder lives"), where Address has the docstring """A postal address."""):

OpenAPI version components.schemas.Account.properties.home.description
3.1 Where the account holder lives
3.0 A postal address.

Structural cases, measured with openapi-schema-validator 0.9.0 (OAS31Validator on the original schema, OAS30Validator on the converted one) and the referenced schema T = {"allOf": [{"type": "object"}]}:

Original schema Converted for 3.0 Accepted by 3.0, rejected by the original Rejected by 3.0, accepted by the original
{"allOf": [{"minProperties": 1}], "anyOf": [{"$ref": T}, {"type": "null"}]} (also on main) {"allOf": [{"type": "object"}], "nullable": true} {} null
{"type": ["string", "integer"], "anyOf": [{"$ref": T}, {"type": "null"}]} (needs #863) {"allOf": [{"type": "object"}], "nullable": true} {} none

Both rows lose the surrounding allOf because the merge keeps the referenced schema's own allOf; the first row also rejects null because nullable never reaches the allOf members of the inlined schema.

Acceptance Checklist

  • When a field-level annotation (description, title, default, ...) conflicts with the referenced schema's, the converted 3.0 schema keeps the field-level value, as the 3.1 output does.
  • When the surrounding schema and the referenced schema both carry allOf, the converted schema keeps both lists.
  • An inlined schema whose null acceptance depends on an allOf member accepts null, or the loss is reported through the structured version-downgrade-drop channel and raises under strict=True.
  • Regression tests cover the Pydantic description case and compare the instances accepted by the original (OAS31Validator) and by the converted schema (OAS30Validator) for the structural rows, inline and as a component.
  • make check-all passes and coverage stays at or above 95%.

Out of scope

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingneeds-triageNot yet reviewed for validity, scope, type, and priority.priority: mediumsize: S

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions