[generators] Give clients a way to inject vendor overrides - #24571
[generators] Give clients a way to inject vendor overrides#24571devhl-labs wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
8 issues found across 166 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/net8/Petstore/src/Org.OpenAPITools/Model/InjectedVendorExtensionsTest.cs">
<violation number="1" location="samples/client/petstore/csharp/generichost/net8/Petstore/src/Org.OpenAPITools/Model/InjectedVendorExtensionsTest.cs:88">
P2: The newly added net8 InjectedVendorExtensionsTest sample does not actually exercise the injectModelVendorExtensions feature this PR introduces. Every property is generated get-only (readonly), so none of the x-setter-visibility overrides (public/internal/private setter) that the PR is meant to demonstrate are present — compare the net10 sample, which correctly emits public `set`, `internal set`, and `private set` for these three properties. The reason is that bin/configs/csharp-generichost-net8.yaml was left without the injectModelVendorExtensions block that csharp-generichost-net10.yaml has, so the sample was regenerated as a plain read-only model. Consider updating the net8 config and regenerating so the sample actually validates the feature, and keep it consistent with the net10 sample.</violation>
</file>
<file name="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractCSharpCodegen.java">
<violation number="1" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractCSharpCodegen.java:894">
P2: The model's mutability flag (X_MODEL_IS_MUTABLE), which controls whether the generated C# model gets a public or internal constructor, is computed in postProcessModels() at line 647 before patchProperty() runs, so the new x-setter-visibility normalization that flips property.isReadOnly isn't reflected in that decision. For the 'public' override direction (a spec readOnly property turned into a public setter), the model can still be generated with an internal constructor even though its property now has a public setter, preventing external callers from actually using it. Consider recomputing model mutability after this normalization (or computing it after the per-property patches) so the constructor visibility matches the effective setter access.</violation>
</file>
<file name="samples/client/petstore/csharp/generichost/net10/NullReferenceTypes/src/Org.OpenAPITools/Model/InjectedVendorExtensionsTest.cs">
<violation number="1" location="samples/client/petstore/csharp/generichost/net10/NullReferenceTypes/src/Org.OpenAPITools/Model/InjectedVendorExtensionsTest.cs:90">
P2: The regenerated sample for this new feature doesn't actually demonstrate it. The config injects `x-setter-visibility: private/internal/public` onto three properties, and the PR says public should emit a normal public setter while private/internal should emit restricted setters. But in the added `InjectedVendorExtensionsTest.cs` all four properties (including PotentiallyOverriddenPropertyToPublic) are emitted as get-only `{ get { return this.XOption.Value; } }` with no setter at all, and the model doc still lists all as `[readonly]`. So the regenerated output doesn't reflect the injected vendor extensions — either the sample was generated without the feature actually applying, or the setter-visibility handling isn't reaching these properties. Worth confirming by regenerating with the new code and verifying the public/internal/private setters appear as intended before merging.</violation>
</file>
<file name="samples/client/petstore/csharp/generichost/net10/SourceGeneration/src/Org.OpenAPITools/Model/InjectedVendorExtensionsTest.cs">
<violation number="1" location="samples/client/petstore/csharp/generichost/net10/SourceGeneration/src/Org.OpenAPITools/Model/InjectedVendorExtensionsTest.cs:91">
P2: In the regenerated SourceGeneration sample, the three properties that were injected with x-setter-visibility (private/internal/public) are all emitted as get-only with no setter, so the new feature has no observable effect in this sample. The same config's non-SourceGeneration Petstore sample correctly emits `private set`, `internal set`, and public `set`, while `unalteredProperty` stays get-only. This suggests either the SourceGeneration sample was regenerated before the setter template change, or the SourceGeneration code path isn't honoring the injected extension. Please regenerate with the updated template and confirm the SourceGeneration output also shows private/internal/public setters so the sample actually exercises the new behavior.</violation>
</file>
<file name="samples/client/petstore/csharp/restsharp/net8/Petstore/src/Org.OpenAPITools/Model/InjectedVendorExtensionsTest.cs">
<violation number="1" location="samples/client/petstore/csharp/restsharp/net8/Petstore/src/Org.OpenAPITools/Model/InjectedVendorExtensionsTest.cs:89">
P2: This net8/restsharp sample output renders potentiallyOverriddenPropertyToInternal and potentiallyOverriddenPropertyToPublic as `private set` with a `ShouldSerialize*()` returning false, which contradicts the setter-visibility behavior described in this PR (public → standard set with isReadOnly=false; internal → internal modifier). Either the sample was regenerated before the feature landed or the restsharp template is not wired to the x-setter-visibility normalization; either way the added sample does not actually verify the public/internal cases it is intended to cover. Consider regenerating this sample with injectModelVendorExtensions applied (or removing it if restsharp isn't part of the feature) so the committed output matches the generator's actual behavior.</violation>
</file>
<file name="samples/client/petstore/csharp/generichost/net8/FormModels/src/Org.OpenAPITools/Model/InjectedVendorExtensionsTest.cs">
<violation number="1" location="samples/client/petstore/csharp/generichost/net8/FormModels/src/Org.OpenAPITools/Model/InjectedVendorExtensionsTest.cs:88">
P2: The generated sample does not reflect the intended x-setter-visibility behavior, which points at a post-processing ordering problem in the new feature. These properties are injected with x-setter-visibility, but the regenerated output shows no setter at all (e.g. PotentiallyOverriddenPropertyToPublic should end up with an effective public/default setter, and PotentiallyOverriddenPropertyToPrivate with a `private set`). The cause: the injection runs in DefaultCodegen.postProcessAllModels(), which is invoked after AbstractCSharpCodegen analyzes properties in postProcessModelsProperty() where x-setter-visibility is normalized to set/clear isReadOnly (AbstractCSharpCodegen ~line 887). Because that normalization has already run before the extensions are injected, the injected values never drive isReadOnly, so the C# setter-visibility feature cannot take effect for config-injected extensions. Since this file is presented as the confirmation sample, the sample either needs regenerating after fixing the pipeline order, or the injection must occur before the C# visibility normalization so injected x-setter-visibility also flips isReadOnly (currently a `private`/`internal` injection would also leave isReadOnly=false, causing the template to emit both a default `set {...}` and a `private set {...}`, i.e. duplicate setters).</violation>
</file>
<file name="samples/client/petstore/csharp/generichost/net10/FormModels/src/Org.OpenAPITools/Model/InjectedVendorExtensionsTest.cs">
<violation number="1" location="samples/client/petstore/csharp/generichost/net10/FormModels/src/Org.OpenAPITools/Model/InjectedVendorExtensionsTest.cs:88">
P2: The generated InjectedVendorExtensionsTest.cs doesn't reflect the x-setter-visibility feature from this PR: all four properties (including potentiallyOverriddenPropertyToPublic, which per the PR should get a standard public setter, and the internal/private ones, which should get restricted setters) are emitted as read-only get-only properties with no setter. This suggests the sample wasn't regenerated with the new logic, or the setter-generation path isn't emitting the expected `public set`/`internal set`/`private set` for these cases. Please regenerate the net10 Petstore sample and confirm the overridden properties actually carry their setters, otherwise the sample provides no coverage for the new option.</violation>
</file>
<file name="samples/client/petstore/csharp/generichost/net4.8/FormModels/src/Org.OpenAPITools/Model/InjectedVendorExtensionsTest.cs">
<violation number="1" location="samples/client/petstore/csharp/generichost/net4.8/FormModels/src/Org.OpenAPITools/Model/InjectedVendorExtensionsTest.cs:107">
P2: This generated model declares support for additional properties via `[JsonExtensionData]`, but its custom deserialization converter never populates that dictionary — the `default: break;` branch in `Read` silently discards every JSON field that isn't one of the four known properties. Because the custom converter suppresses System.Text.Json's automatic extension-data handling, any payload carrying extra fields will have them dropped rather than preserved in `AdditionalProperties`. Consider routing unknown property values into `AdditionalProperties` in the `Read` loop.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
thanks for the enhancement as always
quick feedback on this. should this be better done in the openapi normalizer, which intends to modify the incoming openapi spec without having the users manually editing it? in other words, add normalizer rules to allows users inject extensions in models, properties, parameters, operations, etc |
|
Doesn't matter to me, but this isn't exactly "normalizing". Ill defer to you. |
|
if possible, i would suggest starting as a generator's option instead of global option as only csharp (generichost) supports this new feature. if it's a lot of work to make it a generator's option, we can go with what you've so far but suggest documenting it clearly only csharp (generaichost) supports it so far (a user trying this option with python client generator would have no clue this new feature is not yet supported) |
|
As written, every generator is supported. The only c# specific thing is the specific vendor extension i added here. But users could inject any vendor extension. |
Add
--inject-model-vendor-extensionsCLI optionSummary
Adds a new
--inject-model-vendor-extensionsCLI option (and equivalent config file key) that injects vendor extensions into model classes or their properties at code generation time, without requiring modifications to the OpenAPI spec.Motivation
Generated REST clients often need to patch server response data after deserialization. For example, a server may omit a field in certain scenarios where the value is actually known and derivable. Without a setter on the generated model, the client library has no way to correct the data.
Previously the only option was to add
x-*extensions directly to the OpenAPI spec, which is not always possible (e.g. third-party specs, specs under separate ownership). This option allows injecting extensions as part of the generator invocation.Key Format
Extensions are specified as
key=valuepairs passed to--inject-model-vendor-extensions. Two formats are supported:Class-level (2-part key) — injects into
model.vendorExtensions:Property-level (3-part key) — injects into a specific property's
vendorExtensions, matched bybaseName:The option can be specified multiple times or as a comma-separated list.
Example CLI usage
Example config file usage
Changes
Core plumbing (all generators)
GeneratorSettings.java— AddedinjectModelVendorExtensionsfield with builder support and config file deserialization.CodegenConfig.java— AddedinjectModelVendorExtensions()interface method.DefaultCodegen.java— Added field + getter. Injection logic runs inpostProcessAllModels()after all models have been processed: iterates all models and their property lists (vars,allVars,readWriteVars,requiredVars,optionalVars,parentVars,readOnlyVars,nonNullableVars) to ensure all property instances are updated.CodegenConfigurator.java— Added field,addInjectModelVendorExtension()/setInjectModelVendorExtensions()methods, and wiring intoClientOptInput().CodegenConfiguratorUtils.java— AddedapplyInjectModelVendorExtensionsKvpList()andapplyInjectModelVendorExtensionsKvp()helper methods.Generate.java— Added--inject-model-vendor-extensionsCLI option with description, and callsapplyInjectModelVendorExtensionsKvpList()inrun().C# generichost template —
x-setter-visibilitysupportThe
x-setter-visibilityvendor extension is now supported by the C# generichost generator. It controls the setter visibility on a generated property, with valid values:private,internal,protected,public.Primary use case: Allowing client library code to patch server response data post-deserialization (e.g. in a
partial void OnCreated()method) while keeping the property effectively read-only from external consumers' perspective.AbstractCSharpCodegen.patchProperty()Normalizes
x-setter-visibilityas part of C#-specific property processing:"public"→ removes the extension and setsisReadOnly = false. This avoids the C# compiler error CS0274 (public seton apublicproperty is invalid) and produces the standardset;accessor."private","internal","protected") → setsisReadOnly = true. This ensures the template's{{^isReadOnly}}set;{{/isReadOnly}}default path is suppressed, and the restricted setter is emitted instead.Setting
isReadOnly = trueis semantically appropriate for these properties: from external consumers' perspective they are read-only (populated by the server); the library patching them is an implementation detail. Note thatreadOnlyVars/readWriteVarslists are pre-computed beforepatchPropertyruns and are unaffected.modelGeneric.mustacheUpdated all 5 public property definition locations to use simplified setter logic. Old pattern (double-branch inversion):
New pattern (two independent, mutually exclusive branches):
The two branches are guaranteed mutually exclusive by the Java normalization: when
x-setter-visibilityis present (non-public),isReadOnlyistrue; when absent orpublic,isReadOnlyreflects its original value.Option backing properties (
FooOption) always useprivate setand are unaffected.Test coverage
InjectedVendorExtensionsTestschema topetstore-with-fake-endpoints-models-for-testing-with-http-signature.yamlwith properties covering all scenarios:unalteredProperty— no extension, trulyreadOnly: true→ no setterpotentiallyOverriddenPropertyToPrivate—x-setter-visibility: privatein YAML →private setpotentiallyOverriddenPropertyToInternal— injected via config withx-setter-visibility: internal→internal setpotentiallyOverriddenPropertyToPublic— injected via config withx-setter-visibility: public(overrides spec-definedprivate) → standardsetbin/configs/csharp-generichost-net10.yamlwithinjectModelVendorExtensionsentries for the test schema.InjectedVendorExtensionsTest.csin the net10 Petstore sample confirming correct output for all 4 cases.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.
Summary by cubic
Adds
--inject-model-vendor-extensions(andinjectModelVendorExtensionsconfig key) to inject vendor extensions into generated models or properties without changing the OpenAPI spec. Adds C# generichost support forx-setter-visibilityto control property setter visibility.New Features
x-setter-visibility(private,internal,protected,public). Normalizes sopublicyields the standard setter, others emit restricted setters; template updated accordingly.Migration
--inject-model-vendor-extensions ModelName.propertyBaseName.x-setter-visibility=internal(repeatable or comma-separated). Class-level format isModelName.x-extension-name=value; property-level isModelName.propertyBaseName.x-extension-name=value.injectModelVendorExtensions: { ModelName.propertyBaseName.x-setter-visibility: internal }. No changes needed if you don’t use this feature.Written for commit 90ced51. Summary will update on new commits.