Skip to content

fix(spec): convert non-null type arrays and audit inline schemas for OpenAPI 3.0 - #863

Merged
yeongseon merged 10 commits into
mainfrom
fix/spec-3-0-bridge-valid
Oct 30, 2026
Merged

yeongseon merged 10 commits into
mainfrom
fix/spec-3-0-bridge-valid

Conversation

@yeongseon

@yeongseon yeongseon commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

Summary

OpenAPI 3.0 output for routes whose metadata comes from @validate_http was invalid and nothing reported it. The standard 422 response lists loc items as type: [string, integer], a 3.1-only construct; conversion left it alone, and the 3.0 audit only looked at components.schemas.

Changes

  • _convert_schema_to_3_0 rewrites a type array without null to an anyOf of single-type schemas (a one-element array becomes a plain type). Sibling keywords stay on the schema, an existing anyOf moves the union into allOf, and a multi-type array beside a discriminator is left unchanged because adding a combinator would activate it. Schemas under not are converted as well.
  • Each generated branch stays valid under 3.0: an array branch gets an empty items, a nullable union carries nullable: true on its first branch (this also fixes type: [string, integer, null], which rejected null on main), and a $ref beside a rewritten type array moves into allOf because 3.0 ignores the siblings of a reference.
  • A schema that accepts only null (type: "null", type: ["null"], or a union of null members alone) converts to enum: [null] with nullable: true. A type-less nullable schema would accept every value, and a not over it would reject every value. A sibling enum or const is intersected with it.
  • The residual 3.0 audit now also covers inline operation schemas under paths (including response header schemas) and the values of $defs, and each finding names the construct and, for inline schemas, the operation and location.
  • Non-strict audit findings are reported as structured version-downgrade-drop warnings, so --fail-on-warnings can gate on them. Strict mode still raises OpenAPISpecConfigError.
  • Conversion and audit share one traversal of inline schema positions (_operation_schema_slots), which keeps the visiting order of the previous code and now includes response headers.
  • DESIGN.md and docs/configuration.md describe the new behavior.

Validation

Run locally on 52f8f5d:

  • make check-all: exit 0 (1852 passed, 30 skipped). The skipped tests need the optional azure-functions-validation package, which the Hatch environment does not install; they pass with azure-functions-validation 0.16.0 in a separate environment. Coverage is 96.03% (gate: 95%) and every line and branch this PR adds to the conversion module is covered.
  • The reproduction from fix(spec): make OpenAPI 3.0 output valid for @validate_http bridge routes #858 raises OpenAPIValidationError on origin/main and validates on this branch, with and without hoist_flat_schemas=True and under strict=True.
  • A differential check with realistic @validate_http models (Optional, Union, Literal, constraints, Enum, dates, collections, nested models) in four input shapes: all 8 OpenAPI 3.0 documents were invalid on origin/main, and all 16 documents (3.0 and 3.1) are valid here, without warnings.
  • A differential check of the null-only conversion compares each original schema with its 3.0 form through the reference validators (OAS31Validator and OAS30Validator): 1440 combinations of null-only forms, sibling keywords and nesting contexts, including not and $ref. The only differences involve schemas that contradict themselves and sit directly in a union, which the existing nullable collapse already treated as plain null.
  • Eleven mutation checks: each fix, reverted alone, makes the new tests fail. The conversion tests also pass on Python 3.14, and mkdocs build --strict exits 0.
  • GitHub CI on 52f8f5d: all check runs completed with no failures, including the required Analyze, bandit, ci-required, PR title and semgrep contexts.
  • Review: GitHub's automated review of the first head raised seven findings. Six are fixed and their threads resolved; the seventh (the nullable-union merge dropping an allOf) is tracked in fix(spec): keep the surrounding schema's keys when 3.0 conversion inlines a nullable reference #870 and its thread is left open for you. Independent AI reviews (advisory) of the follow-up commits ended with an approval of 52f8f5d without blocking issues; DESIGN.md is policy-bearing, so a maintainer review of the final head is still needed.

Compatibility

Related Issues

Closes #858

yeongseon and others added 2 commits October 30, 2026 19:34
A type array without null, such as the loc items of the 422 envelope a validation producer publishes, stayed in OpenAPI 3.0 output where it is invalid. The residual 3.0 audit only looked at components.schemas, so the invalid document was produced without any signal.

Rewrite such an array to an anyOf of single-type schemas. Sibling keywords stay on the schema, and a node with a discriminator is left for the audit because a combinator would activate it. Also convert schemas under not, audit inline operation schemas and $defs values, name the construct and its location in each finding, and send non-strict findings through the structured downgrade channel so --fail-on-warnings can gate on them.

Refs #858

Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
Update the schema version conversion design notes and the 3.0 mode section of the configuration guide for the type-array rewrite, the inline-schema audit and the structured downgrade warnings. Refs #858

Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Type-union and nullability conversions can silently change schema semantics or produce invalid OpenAPI 3.0 documents, while response headers bypass conversion and auditing.

7 open issues
What changed in this PR

This PR addresses #858 by improving OpenAPI 3.0 conversion for validation-producer schemas, including the standard 422 response, and making residual incompatibilities visible through structured warnings.

Changes:

  • Converts non-null type arrays and schemas nested under not.
  • Shares inline-schema traversal between conversion and auditing, with structured warnings and strict-mode errors.
  • Adds conversion and bridge regression tests and documents the behavior and limits.
File Description
tests/​test_spec_conversion_characterization.py Adds conversion, audit, warning, and traversal coverage.
tests/​test_bridge_endpoint.py Validates converted 422 envelopes with and without hoisting.
src/​azure_functions_openapi/​spec.py Integrates inline auditing and structured compatibility warnings.
src/​azure_functions_openapi/​_schema_version_conversion.py Implements type-array conversion and shared schema traversal.
docs/​configuration.md Explains conversion behavior and diagnostic limits.
DESIGN.md Updates the conversion and compatibility-audit design.

Review effort: Balanced

yeongseon and others added 5 commits October 30, 2026 21:10
…rays

Expanding a multi-type array into an anyOf left three gaps under OpenAPI 3.0. A nullable union lost null because nullable sat beside no type, so the first branch now carries it. An array branch had no items, which 3.0 requires, so it gets an empty one. A $ref beside the array lost the type because 3.0 ignores the siblings of a reference, so the reference moves into allOf. A single-type array beside a discriminator adds no combinator, so it is converted too. Refs #858

Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
Response headers declare a schema directly or through content, but the shared inline-schema traversal skipped them, so a type array there reached an OpenAPI 3.0 document unconverted and strict mode reported nothing. Visit each header's schema and content schemas for conversion and for the audit. Refs #858

Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
OpenAPI 3.0 can only approximate a null-only schema as a type-less nullable schema, which accepts every value, so under not the negation rejected every value without a word. Report it as a lossy conversion, which is a structured warning and an error in strict mode. Share the nested-schema iteration with the existing audit. Refs #858

Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
Document that a nullable union carries nullable on its first branch, that an array branch gains an empty items, that a reference beside a type array moves into allOf, that response header schemas are converted and audited, and that a null-only schema under not is reported. Keep the design notes in step. Refs #858

Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
Looking for a bare nullable in the converted output reported a nullable schema already written for 3.0, which conversion leaves exact, and missed a null-only schema with a constraint or extension keyword beside it. Read the negated schema before conversion instead, follow references into the component map, ignore null members that collapse exactly into nullable, and say that the negation rejects values the original accepts. Refs #858

Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
yeongseon and others added 3 commits October 30, 2026 21:34
A schema that accepts only null lost its type and was left as a type-less nullable schema, which accepts every value in OpenAPI 3.0, so a not over it rejected every value. Keep an enum of null on it instead, for a type of null, a type array of only null and a union of null members alone. This replaces the detector that reported a null-only schema under not: it warned for schemas that convert exactly and missed others. It also makes a null-typed property accept only null. Refs #858

Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
The collapse docstring enumerates every form it produces; add the one that keeps a union of null members alone null-only. Refs #858

Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
Keeping a null-only schema null-only added an enum of null only when none existed, so type null beside enum [null, "x"] accepted "x" once the type was gone, and type null beside const "x" accepted "x" although it accepts nothing. Intersect with the existing enum: narrow a list that includes null to null, and add the null-only predicate as an allOf conjunct when the list excludes null, so the schema still accepts nothing. Refs #858

Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
@yeongseon
yeongseon merged commit cf1bff8 into main Oct 30, 2026
33 checks passed
@yeongseon
yeongseon deleted the fix/spec-3-0-bridge-valid branch October 30, 2026 13: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.

fix(spec): make OpenAPI 3.0 output valid for @validate_http bridge routes

2 participants