Repository navigation
fix(spec): convert non-null type arrays and audit inline schemas for OpenAPI 3.0 - #863
Merged
Merged
Conversation
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>
There was a problem hiding this comment.
🟡 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
Add items to generated array type branches · New Preserve null acceptance in generated type branches · New Preserve type restrictions alongside sibling references · New Preserve constraints when collapsing nullable combinators · New Preserve null-only predicates under not · New Traverse response header schemas during conversion · New Allow singleton type arrays through discriminator guard · New
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
…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>
Open
5 tasks
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
OpenAPI 3.0 output for routes whose metadata comes from
@validate_httpwas invalid and nothing reported it. The standard422response listslocitems astype: [string, integer], a 3.1-only construct; conversion left it alone, and the 3.0 audit only looked atcomponents.schemas.Changes
_convert_schema_to_3_0rewrites atypearray withoutnullto ananyOfof single-type schemas (a one-element array becomes a plaintype). Sibling keywords stay on the schema, an existinganyOfmoves the union intoallOf, and a multi-type array beside adiscriminatoris left unchanged because adding a combinator would activate it. Schemas undernotare converted as well.arraybranch gets an emptyitems, a nullable union carriesnullable: trueon its first branch (this also fixestype: [string, integer, null], which rejectednullonmain), and a$refbeside a rewrittentypearray moves intoallOfbecause 3.0 ignores the siblings of a reference.null(type: "null",type: ["null"], or a union of null members alone) converts toenum: [null]withnullable: true. A type-lessnullableschema would accept every value, and anotover it would reject every value. A siblingenumorconstis intersected with it.paths(including response header schemas) and the values of$defs, and each finding names the construct and, for inline schemas, the operation and location.version-downgrade-dropwarnings, so--fail-on-warningscan gate on them. Strict mode still raisesOpenAPISpecConfigError._operation_schema_slots), which keeps the visiting order of the previous code and now includes response headers.DESIGN.mdanddocs/configuration.mddescribe the new behavior.Validation
Run locally on
52f8f5d:make check-all: exit 0 (1852 passed, 30 skipped). The skipped tests need the optionalazure-functions-validationpackage, which the Hatch environment does not install; they pass withazure-functions-validation0.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.OpenAPIValidationErroronorigin/mainand validates on this branch, with and withouthoist_flat_schemas=Trueand understrict=True.@validate_httpmodels (Optional, Union, Literal, constraints, Enum, dates, collections, nested models) in four input shapes: all 8 OpenAPI 3.0 documents were invalid onorigin/main, and all 16 documents (3.0 and 3.1) are valid here, without warnings.OAS31ValidatorandOAS30Validator): 1440 combinations of null-only forms, sibling keywords and nesting contexts, includingnotand$ref. The only differences involve schemas that contradict themselves and sit directly in a union, which the existing nullable collapse already treated as plainnull.mkdocs build --strictexits 0.52f8f5d: all check runs completed with no failures, including the requiredAnalyze,bandit,ci-required,PR titleandsemgrepcontexts.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 of52f8f5dwithout blocking issues;DESIGN.mdis policy-bearing, so a maintainer review of the final head is still needed.Compatibility
WarningCodechange.anyOfwhere a non-nulltypearray was emitted before; those documents were invalid.typeisnullnow accepts onlynullin 3.0 output; it used to become a type-lessnullableschema that accepted any value.Literal[None]already used this form.strict=Truenow audits inline operation schemas as well, so a residual 3.1-only construct there raises instead of passing silently.nullableandexamplerewrites there as for other schemas.OpenAPI 3.0 compatibility:toOpenAPI spec validation:, and its text now names the construct.propertyNames, which Pydantic emits fordict[Literal[...], V]) are not handled here; that is tracked in fix(spec): report JSON Schema keywords that OpenAPI 3.0 does not define #867. Overlapping keywords that lose a constraint when folded for 3.0 are tracked in fix(spec): keep the stricter constraint when 3.0 conversion folds overlapping keywords #868, and the nullable-union merge defects in fix(spec): keep the surrounding schema's keys when 3.0 conversion inlines a nullable reference #870.Related Issues
Closes #858