Honor OpenAPI required + nullable for non-nullable models (TypeScript, Python & Java, opt-in)#7795
Open
kev-flex wants to merge 9 commits into
Open
Conversation
3906ac9 to
128295e
Compare
Author
|
@microsoft-github-policy-service agree |
…neOf nullability
Addresses issues found in review of the MakeRequiredPropertiesNonNullable flag:
- TypeScript: a required reference to a scalar alias (e.g. a oneOf of
string-enums that collapses to `type X = string`) could flip the
discriminator factory's composed ReturnType to non-nullable, emitting
`: X` over a `parseNode?.get*Value()` body (`X | undefined`) -> tsc error.
CodeFunctionWriter now forces primitive-composed factory return types
nullable; no-op when the flag is off. Covered by a new regression test.
- Workspace flow: persist MakeRequiredPropertiesNonNullable in
ApiClientConfiguration (constructor, update, clone) and expose it on
`kiota client add` / `kiota client edit` so the flag round-trips through
kiota-workspace.json instead of silently reverting on regeneration.
- OpenApiSchemaExtensions.IsExplicitlyNullable now also recognizes the OAS
3.1 `oneOf: [{ type: null }, ...]` nullability pattern (previously only
anyOf), so such required properties are correctly left nullable. Covered
by a new unit test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.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
This adds an opt-in generation flag,
--make-required-properties-non-nullable(alias--mrpnn), that makes Kiota honor the OpenAPIrequiredarray alongside thenullable/type: [..., "null"]markers. With it on, a property that is required and not explicitly nullable is generated as non-optional and non-nullable, so consumers don't need to write?? "",!, orassert x is not Nonefor fields the server always sends.Refs #3911. It builds on the groundwork from #7585 and turns the optimization on for TypeScript, Python, and Java.
The flag defaults to
false, so existing output stays byte-for-byte identical for every language unless someone opts in. That keeps current clients safe and lets us enable languages one at a time.How it works
The pipeline builds a single language-agnostic CodeDOM and each writer renders it. Most writers already derive nullability from
Type.IsNullable, so the change comes down to one signal:CodeProperty.IsRequiredis populated from the parent schema'srequiredarray, regardless of the flag.OpenApiSchemaExtensions.IsExplicitlyNullable()detects OAS 3.0nullable: trueand OAS 3.1type: [..., "null"]/anyOf: [{ type: null }].KiotaBuildersetsType.IsNullable = falsefor required, non-explicitly-nullable, non-collection custom properties. Collections are left alone, since theirIsNullablealso drives element nullability and the serializer API.The flip lives in the builder but is gated to the languages whose writers handle it and that are covered by tests here: TypeScript, Python, and Java. Every other language keeps its current nullable output, so turning the flag on is a no-op for them until support lands.
Per language:
CodePropertyWriter): the interface writer used to hardcode?: T | null. It now drops the?for required properties and drops| nullunless the schema is explicitly nullable. The flag is threaded throughTypeScriptConventionService, because otherwise a required-but-nullable property looks identical in the CodeDOM to a flag-off required property.Optional[...]offIsNullable. Required primitives get a type-correct zero default ('',0,0.0,False) so the field stays sound; required objects and enums have no zero literal, so they keep aNonedefault behind the non-optional type. A default is needed either way for the no-arg construction increate_from_discriminator_value()and for valid dataclass field ordering.@jakarta.annotation.Nonnullinstead of@NullablewhenIsNullableis false, and no Java refiner re-nullifies model properties.On the CLI side the flag is available on
kiota generate,kiota client add, andkiota client edit. Edit uses a nullablebool?so it won't overwrite a previously persisted value. The setting is saved toworkspace.jsonthroughApiClientConfigurationand read back intoGenerationConfiguration, the same wayExcludeBackwardCompatibleis wired, and it emits a telemetry tag like the other generation toggles.Generated output (flag on)
TypeScript:
Python:
For Python, required primitives use a real zero-value default (
'',0,0.0,False), so the field is type-correct. Required objects and enums have no zero-value literal, so they render non-optional with aNonedefault. ThatNoneis an advisory non-null that the deserializer always overwrites, which is the same compile-time-only guarantee Java's@Nonnulland TypeScript's erased types already give.Java:
Configuration and docs
CHANGELOG.md: entry added under theAddedheading of[Unreleased].specs/cli/client-add.mdandspecs/cli/client-edit.md: the new option is listed in the parameter tables. Thegeneratecommand reads it fromworkspace.json, so its table is unchanged.specs/schemas/workspace.json:makeRequiredPropertiesNonNullableadded to the client schema so authored or edited workspaces validate.KiotaGenerateCommandHandleremitscommand.params.make_required_properties_non_nullable, matching the existing per-flag tags.Scope and limitations
These are intentional, and called out so reviewers don't have to guess.
MakeModelPropertiesNullablepass). New languages can be added later.@Nonnullis an annotation, and Python annotations aren't enforced. A non-conformant server that drops arequiredfield will leaveundefined/null/Nonebehind a non-null type. No runtime check is added.allOfmember is not tightened. Required-ness is read from a schema's own (allOf-merged)requiredarray, andMergeIntersectionSchemaEntriesmerges member properties but not theirrequiredarrays. So a property marked required only inside anallOfmember is treated as not required and stays nullable. This never marks an omittable field as non-null, and it matches the existing required handling.Testing
Kiota.Builder.TestsandKiota.Testssuites pass unchanged, which confirms the default-off no-op.KiotaBuilderTests, theIssue-3911region): the full on/off matrix at the CodeDOM level across scalar, integer, enum, object ref, collection, required-nullable, and optional, plus OAS 3.1 cases wheretype: [string, "null"]stays nullable and a 3.1 required scalar is made non-nullable.CodePropertyWriterTests,CodeFunctionWriterTests), Python dataclass field rendering (CodeMethodWriterTests), and Java@Nonnullrendering (CodePropertyWriterTests).OpenApiSchemaExtensionsTests:IsExplicitlyNullable()across OAS 3.0nullable: true, the OAS 3.1typenull union,anyOf/oneOfnull members, and the non-nullable and null-receiver cases.ApiClientConfigurationTests): the flag is asserted through all three persistence paths (config to client config, the update path, andClone()).