Feat/normalizer default propagation#23821
Conversation
…g defaults in allOf schemas
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…LTS rule Change 1 (always-on): when a property schema is a pure ref with no direct default, copy the default value from the referenced schema before normalizeSchema runs. The existing normalizeReferenceSchema logic then rewrites the property as allOf+default, making the default visible as schema.getDefault() to all generators. Change 2 (opt-in): new normalizer rule RESOLVE_SCHEMA_DEFAULTS=<strategy> where <strategy> is LAST_WINS | NEAREST_WINS | ROOT_WINS | STRICT. When enabled, ModelUtils.resolveDefault() is called after normalization for every component schema and every property schema with no direct default. The resolved value (if any) is written back via schema.setDefault(). Change 3: document both mechanisms in docs/default-resolution-strategy.md under a new section 'How defaults are surfaced at codegen time'. These changes cover ALL generators - the normalizer modifies the spec model before any generator's toDefaultValue(Schema) runs, including the ~60 generators that override that method and do not call super. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… mutation
- Remove Change 1 normalizer block that copied defaults onto \ properties.
It caused normalizeReferenceSchema to wrap as allOf, breaking typed generators
like C# whose toDefaultValue cannot handle untyped allOf schemas.
- Add deep \ traversal loop in DefaultCodegen.fromProperty before calling
toDefaultValue. ModelUtils.getReferencedSchema is single-level, so the loop
iterates until stable, handling chains of arbitrary depth
(A -> \ -> \ -> {type: string, default: x}).
- Remove now-redundant getReferencedSchema lines from AbstractJavaCodegen and
AbstractKotlinCodegen toDefaultValue overrides; fromProperty already resolves
the \ before calling toDefaultValue.
- Guard RESOLVE_SCHEMA_DEFAULTS rule in normalizeProperties to skip pure \
schemas, preventing invalid OAS 3.0 \-with-sibling-default constructs.
- Update OpenAPINormalizerTest: testRefDefaultPropagation -> testRefDefaultNotPropagatedByNormalizer.
- Add two DefaultCodegenTest cases: single and chained \ default propagation.
- Update docs/default-resolution-strategy.md to reflect codegen-layer approach.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
DefaultCodegen.fromProperty now always resolves \ chains before calling toDefaultValue, so generators receive a concrete typed schema. Generators that need type-name-based initialization (not OAS default) must use the two-arg toDefaultValue(CodegenProperty, Schema) override and cp.isModel. - CppTinyClientCodegen: add two-arg override returning cp.dataType + '()' for model types (C++ default construction). Remove dead \ branch from the single-arg method. - CppTizenClientCodegen: same, returning 'new ' + cp.dataType + '()' for heap-allocated default construction. - AbstractPythonCodegen: remove the \-detection block at the top of toDefaultValue(Schema p) -- it is now dead code since fromProperty pre-resolves \ chains before calling toDefaultValue. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The block was mistakenly removed as 'dead code'. While DefaultCodegen.fromProperty pre-resolves \ chains before calling toDefaultValue, the single-arg toDefaultValue(Schema) is also called directly from fromOperation (line 4666: op.defaultResponse = toDefaultValue(responseSchema)) without any \ pre-resolution. If responseSchema is a \, the block is necessary for Python to follow the ref and obtain the correct default value. Restore the block with an explanatory comment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ModelUtils.getReferencedSchema returns the input schema unchanged when a ref cannot be resolved, making 'referenced == pForDefault' the natural loop terminator. The extra prevSchema sentinel guarded against circular \ which are invalid OpenAPI and do not need application-level protection. Remove it for clarity. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ion after ref pre-resolution fromProperty now pre-resolves \ chains before calling toDefaultValue, so model property schemas arrive fully resolved (no \). The old \ detection branch in toDefaultValue(Schema) no longer fires, leaving defaultValue as null for model types. Fix: in processModelVariable, when a property isModel and defaultValue is null, set defaultValue = datatypeWithEnum + '()' so the C++ value-type member appears in the constructor initializer list (e.g. 'category(Category())'). Also remove the now-dead \ branch from toDefaultValue(Schema) that was returning std::make_shared<TypeName>() — processModelVariable was already converting it to value-type initialization, but the source of truth is now the two-step pattern: toDefaultValue returns null for unresolvable types, processModelVariable fills in the C++ default construction. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
3 issues found across 141 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="samples/client/petstore/csharp/generichost/net10/NullReferenceTypes/src/Org.OpenAPITools/Model/EnumTest.cs">
<violation number="1" location="samples/client/petstore/csharp/generichost/net10/NullReferenceTypes/src/Org.OpenAPITools/Model/EnumTest.cs:43">
P2: Constructor XML docs claim enum defaults are applied, but the constructor passes through `Option<T> = default` without applying them, creating a doc/behavior mismatch.</violation>
</file>
<file name="modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java">
<violation number="1" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java:348">
P1: RESOLVE_SCHEMA_DEFAULTS rule can be enabled with a null strategy when the user provides an invalid value (e.g. `true`), leading to a NullPointerException at runtime in ModelUtils.resolveDefault</violation>
<violation number="2" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java:670">
P1: Component schemas which are pure $ref may get a sibling `default` written, creating an invalid OAS 3.0 Reference Object. The `normalizeComponentsSchemas()` method resolves and writes defaults back onto normalized component schemas without checking whether the schema is a pure `$ref`, unlike the property-level normalization which correctly skips pure `$ref` schemas to avoid invalid OAS 3.0 Reference Objects.</violation>
</file>
Note: This PR contains a large number of files. cubic only reviews up to 100 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.
On a pro plan you can use ultrareview for larger PRs.
Re-trigger cubic
|
|
||
| // if RESOLVE_SCHEMA_DEFAULTS is enabled, compute and write back the effective | ||
| // default for allOf-composed component schemas that have no direct default. | ||
| if (getRule(RESOLVE_SCHEMA_DEFAULTS) && normalizedSchema.getDefault() == null) { |
There was a problem hiding this comment.
P1: Component schemas which are pure $ref may get a sibling default written, creating an invalid OAS 3.0 Reference Object. The normalizeComponentsSchemas() method resolves and writes defaults back onto normalized component schemas without checking whether the schema is a pure $ref, unlike the property-level normalization which correctly skips pure $ref schemas to avoid invalid OAS 3.0 Reference Objects.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java, line 670:
<comment>Component schemas which are pure $ref may get a sibling `default` written, creating an invalid OAS 3.0 Reference Object. The `normalizeComponentsSchemas()` method resolves and writes defaults back onto normalized component schemas without checking whether the schema is a pure `$ref`, unlike the property-level normalization which correctly skips pure `$ref` schemas to avoid invalid OAS 3.0 Reference Objects.</comment>
<file context>
@@ -643,7 +662,17 @@ protected void normalizeComponentsSchemas() {
+
+ // if RESOLVE_SCHEMA_DEFAULTS is enabled, compute and write back the effective
+ // default for allOf-composed component schemas that have no direct default.
+ if (getRule(RESOLVE_SCHEMA_DEFAULTS) && normalizedSchema.getDefault() == null) {
+ Object resolved = ModelUtils.resolveDefault(openAPI, normalizedSchema, resolveSchemaDefaultsStrategy);
+ if (resolved != null) {
</file context>
| resolveSchemaDefaultsStrategy = DefaultResolutionStrategy.valueOf(resolveSchemaDefaultsValue.trim().toUpperCase(java.util.Locale.ROOT)); | ||
| rules.put(RESOLVE_SCHEMA_DEFAULTS, true); | ||
| } catch (IllegalArgumentException e) { | ||
| LOGGER.error("RESOLVE_SCHEMA_DEFAULTS rule must be one of LAST_WINS, NEAREST_WINS, ROOT_WINS, STRICT. Got: {}", resolveSchemaDefaultsValue); |
There was a problem hiding this comment.
P1: RESOLVE_SCHEMA_DEFAULTS rule can be enabled with a null strategy when the user provides an invalid value (e.g. true), leading to a NullPointerException at runtime in ModelUtils.resolveDefault
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java, line 348:
<comment>RESOLVE_SCHEMA_DEFAULTS rule can be enabled with a null strategy when the user provides an invalid value (e.g. `true`), leading to a NullPointerException at runtime in ModelUtils.resolveDefault</comment>
<file context>
@@ -329,6 +338,16 @@ public void processRules(Map<String, String> inputRules) {
+ resolveSchemaDefaultsStrategy = DefaultResolutionStrategy.valueOf(resolveSchemaDefaultsValue.trim().toUpperCase(java.util.Locale.ROOT));
+ rules.put(RESOLVE_SCHEMA_DEFAULTS, true);
+ } catch (IllegalArgumentException e) {
+ LOGGER.error("RESOLVE_SCHEMA_DEFAULTS rule must be one of LAST_WINS, NEAREST_WINS, ROOT_WINS, STRICT. Got: {}", resolveSchemaDefaultsValue);
+ }
+ }
</file context>
| LOGGER.error("RESOLVE_SCHEMA_DEFAULTS rule must be one of LAST_WINS, NEAREST_WINS, ROOT_WINS, STRICT. Got: {}", resolveSchemaDefaultsValue); | |
| LOGGER.error("RESOLVE_SCHEMA_DEFAULTS rule must be one of LAST_WINS, NEAREST_WINS, ROOT_WINS, STRICT. Got: {}", resolveSchemaDefaultsValue); | |
| rules.put(RESOLVE_SCHEMA_DEFAULTS, false); |
| /// <param name="enumString">enumString</param> | ||
| /// <param name="outerEnum">outerEnum</param> | ||
| /// <param name="outerEnumDefaultValue">outerEnumDefaultValue</param> | ||
| /// <param name="outerEnumDefaultValue">outerEnumDefaultValue (default to OuterEnumDefaultValue.Placed)</param> |
There was a problem hiding this comment.
P2: Constructor XML docs claim enum defaults are applied, but the constructor passes through Option<T> = default without applying them, creating a doc/behavior mismatch.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/petstore/csharp/generichost/net10/NullReferenceTypes/src/Org.OpenAPITools/Model/EnumTest.cs, line 43:
<comment>Constructor XML docs claim enum defaults are applied, but the constructor passes through `Option<T> = default` without applying them, creating a doc/behavior mismatch.</comment>
<file context>
@@ -40,9 +40,9 @@ public partial class EnumTest : IValidatableObject
/// <param name="enumString">enumString</param>
/// <param name="outerEnum">outerEnum</param>
- /// <param name="outerEnumDefaultValue">outerEnumDefaultValue</param>
+ /// <param name="outerEnumDefaultValue">outerEnumDefaultValue (default to OuterEnumDefaultValue.Placed)</param>
/// <param name="outerEnumInteger">outerEnumInteger</param>
- /// <param name="outerEnumIntegerDefaultValue">outerEnumIntegerDefaultValue</param>
</file context>
PR checklist
Commit all changed files.
This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
These must match the expectations made by your contribution.
You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example
./bin/generate-samples.sh bin/configs/java*.IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
master(upcoming7.x.0minor release - breaking changes with fallbacks),8.0.x(breaking changes without fallbacks)"fixes #123"present in the PR description)Summary by cubic
Introduces a configurable
DefaultResolutionStrategyto resolvedefaultvalues inallOfschemas and makes$refdefaults reliably visible to generators by pre-resolving refs inDefaultCodegen.fromProperty. Also adds an opt-in normalizer ruleRESOLVE_SCHEMA_DEFAULTSto write resolved defaults back to schemas.New Features
ModelUtils.resolveDefault()with strategies:LAST_WINS,NEAREST_WINS,ROOT_WINS,STRICT(seedocs/default-resolution-strategy.md).RESOLVE_SCHEMA_DEFAULTS=<strategy>resolves and appliesdefaultto component/property schemas; skips pure$refto keep OAS validity.allOfconflicts and shadowed defaults inOpenApiSchemaValidations.Refactors
$refdefault propagation moved toDefaultCodegen.fromProperty(deep$reftraversal); removed redundant ref resolution inAbstractJavaCodegen/AbstractKotlinCodegen; restored follow-ref inAbstractPythonCodegen.toDefaultValue(Schema)for direct calls.CppHttplibServerCodegen,CppTinyClientCodegen,CppTizenClientCodegen).Written for commit 6177051. Summary will update on new commits. Review in cubic