Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions bin/configs/csharp-generichost-net10.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ additionalProperties:
operationParameterSorting: alphabetical
treatWarningsAsErrors: true
warningsNotAsErrors: CS0612
injectModelVendorExtensions:
InjectedVendorExtensionsTest.potentiallyOverriddenPropertyToPrivate.x-setter-visibility: private
InjectedVendorExtensionsTest.potentiallyOverriddenPropertyToInternal.x-setter-visibility: internal
InjectedVendorExtensionsTest.potentiallyOverriddenPropertyToPublic.x-setter-visibility: public
1 change: 1 addition & 0 deletions docs/generators/kotlin-spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|x-discriminator-value|Used with model inheritance to specify value for discriminator that identifies current model|MODEL|
|x-field-extra-annotation|Custom annotation(s) to be added to property; accepts a string or list of strings|FIELD, OPERATION_PARAMETER|null
|x-operation-extra-annotation|Custom annotation(s) to be added to operation; accepts a string or list of strings|OPERATION|null
|x-extra-imports|Custom import(s) to add to the generated file that declares the annotated model, property, operation, or parameter (e.g. so custom annotations can be referenced by their short name); accepts a string or list of strings. Values are emitted verbatim (Kotlin alias imports supported) and only exact duplicates are removed|MODEL, FIELD, OPERATION, OPERATION_PARAMETER|null
|x-pattern-message|Add this property whenever you need to customize the invalidation error message for the regex pattern of a variable|FIELD, OPERATION_PARAMETER|null
|x-size-message|Add this property whenever you need to customize the invalidation error message for the size or length of a variable|FIELD, OPERATION_PARAMETER|null
|x-minimum-message|Add this property whenever you need to customize the invalidation error message for the minimum value of a variable|FIELD, OPERATION_PARAMETER|null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,15 @@ public class Generate extends OpenApiGeneratorCommand {
+ " You can also have multiple occurrences of this option.")
private List<String> operationIdNameMappings = new ArrayList<>();

@Option(
name = {"--inject-model-vendor-extensions"},
title = "inject model vendor extensions",
description = "injects vendor extensions into model classes or their properties."
+ " Class-level format: ModelName.x-extension-name=value."
+ " Property-level format: ModelName.propertyBaseName.x-extension-name=value."
+ " You can also have multiple occurrences of this option.")
private List<String> injectModelVendorExtensions = new ArrayList<>();

@Option(
name = {"--openapi-normalizer"},
title = "OpenAPI normalizer rules",
Expand Down Expand Up @@ -606,6 +615,7 @@ public void execute() {
applyModelNameMappingsKvpList(modelNameMappings, configurator);
applyEnumNameMappingsKvpList(enumNameMappings, configurator);
applyOperationIdNameMappingsKvpList(operationIdNameMappings, configurator);
applyInjectModelVendorExtensionsKvpList(injectModelVendorExtensions, configurator);
applyOpenapiNormalizerKvpList(openapiNormalizer, configurator);
applyTypeMappingsKvpList(typeMappings, configurator);
applyAdditionalPropertiesKvpList(additionalProperties, configurator);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public final class GeneratorSettings implements Serializable {
private final Map<String, String> modelNameMappings;
private final Map<String, String> enumNameMappings;
private final Map<String, String> operationIdNameMappings;
private final Map<String, String> injectModelVendorExtensions;
private final Map<String, String> openapiNormalizer;
private final Set<String> languageSpecificPrimitives;
private final Set<String> openapiGeneratorIgnoreList;
Expand Down Expand Up @@ -327,6 +328,15 @@ public Map<String, String> getOperationIdNameMappings() {
return operationIdNameMappings;
}

/**
* Gets inject model vendor extensions.
*
* @return a map of ModelName.x-extension-name or ModelName.propertyBaseName.x-extension-name to extension value
*/
public Map<String, String> getInjectModelVendorExtensions() {
return injectModelVendorExtensions;
}

/**
* Gets OpenAPI normalizer rules
*
Expand Down Expand Up @@ -469,6 +479,7 @@ private GeneratorSettings(Builder builder) {
modelNameMappings = Collections.unmodifiableMap(builder.modelNameMappings);
enumNameMappings = Collections.unmodifiableMap(builder.enumNameMappings);
operationIdNameMappings = Collections.unmodifiableMap(builder.operationIdNameMappings);
injectModelVendorExtensions = Collections.unmodifiableMap(builder.injectModelVendorExtensions);
openapiNormalizer = Collections.unmodifiableMap(builder.openapiNormalizer);
languageSpecificPrimitives = Collections.unmodifiableSet(builder.languageSpecificPrimitives);
openapiGeneratorIgnoreList = Collections.unmodifiableSet(builder.openapiGeneratorIgnoreList);
Expand Down Expand Up @@ -550,6 +561,7 @@ public GeneratorSettings() {
modelNameMappings = Collections.unmodifiableMap(new HashMap<>(0));
enumNameMappings = Collections.unmodifiableMap(new HashMap<>(0));
operationIdNameMappings = Collections.unmodifiableMap(new HashMap<>(0));
injectModelVendorExtensions = Collections.unmodifiableMap(new HashMap<>(0));
openapiNormalizer = Collections.unmodifiableMap(new HashMap<>(0));
languageSpecificPrimitives = Collections.unmodifiableSet(new HashSet<>(0));
openapiGeneratorIgnoreList = Collections.unmodifiableSet(new HashSet<>(0));
Expand Down Expand Up @@ -630,6 +642,9 @@ public static Builder newBuilder(GeneratorSettings copy) {
if (copy.getOperationIdNameMappings() != null) {
builder.operationIdNameMappings.putAll(copy.getOperationIdNameMappings());
}
if (copy.getInjectModelVendorExtensions() != null) {
builder.injectModelVendorExtensions.putAll(copy.getInjectModelVendorExtensions());
}
if (copy.getOpenapiNormalizer() != null) {
builder.openapiNormalizer.putAll(copy.getOpenapiNormalizer());
}
Expand Down Expand Up @@ -684,6 +699,7 @@ public static final class Builder {
private Map<String, String> modelNameMappings;
private Map<String, String> enumNameMappings;
private Map<String, String> operationIdNameMappings;
private Map<String, String> injectModelVendorExtensions;
private Map<String, String> openapiNormalizer;
private Set<String> languageSpecificPrimitives;
private Set<String> openapiGeneratorIgnoreList;
Expand Down Expand Up @@ -712,6 +728,7 @@ public Builder() {
modelNameMappings = new HashMap<>();
enumNameMappings = new HashMap<>();
operationIdNameMappings = new HashMap<>();
injectModelVendorExtensions = new HashMap<>();
openapiNormalizer = new HashMap<>();
languageSpecificPrimitives = new HashSet<>();
openapiGeneratorIgnoreList = new HashSet<>();
Expand Down Expand Up @@ -1193,6 +1210,32 @@ public Builder withOperationIdNameMapping(String key, String value) {
return this;
}

/**
* Sets the {@code injectModelExtensions} and returns a reference to this Builder so that the methods can be chained together.
*
* @param injectModelExtensions the {@code injectModelExtensions} to set
* @return a reference to this Builder
*/
public Builder withInjectModelVendorExtensions(Map<String, String> injectModelVendorExtensions) {
this.injectModelVendorExtensions = injectModelVendorExtensions;
return this;
}

/**
* Sets a single {@code injectModelVendorExtension} and returns a reference to this Builder so that the methods can be chained together.
*
* @param key A key in the format ModelName.x-extension-name or ModelName.propertyBaseName.x-extension-name
* @param value The extension value
* @return a reference to this Builder
*/
public Builder withInjectModelVendorExtension(String key, String value) {
if (this.injectModelVendorExtensions == null) {
this.injectModelVendorExtensions = new HashMap<>();
}
this.injectModelVendorExtensions.put(key, value);
return this;
}

/**
* Sets the {@code openapiNormalizer} and returns a reference to this Builder so that the methods can be chained together.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ public interface CodegenConfig {

Map<String, String> operationIdNameMapping();

Map<String, String> injectModelVendorExtensions();

Map<String, String> openapiNormalizer();

Map<String, String> apiTemplateFiles();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
import static org.openapitools.codegen.CodegenConstants.*;
import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER;
import static org.openapitools.codegen.utils.DiscriminatorUtils.*;
import static org.openapitools.codegen.utils.EnumUtils.getEnumValues;
import static org.openapitools.codegen.utils.EnumUtils.*;
import static org.openapitools.codegen.utils.OnceLogger.once;
import static org.openapitools.codegen.utils.StringUtils.*;

Expand Down Expand Up @@ -199,6 +199,8 @@ public class DefaultCodegen implements CodegenConfig {
protected Map<String, String> enumNameMapping = new HashMap<>();
// a map to store the mapping between operation id name and the name provided by the user
protected Map<String, String> operationIdNameMapping = new HashMap<>();
// a map to inject vendor extensions into model classes or their properties: key=ModelName.x-extension-name or ModelName.propertyBaseName.x-extension-name, value=extensionValue
protected Map<String, String> injectModelVendorExtensions = new HashMap<>();
// a map to store the rules in OpenAPI Normalizer
protected Map<String, String> openapiNormalizer = new HashMap<>();
@Setter
Expand Down Expand Up @@ -547,6 +549,42 @@ public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs)
}
}

// Inject vendor extensions from --inject-property-extensions into matching schema properties
if (!injectModelVendorExtensions.isEmpty()) {
for (Map.Entry<String, ModelsMap> entry : objs.entrySet()) {
CodegenModel model = ModelUtils.getModelByName(entry.getKey(), objs);
if (model == null) continue;

for (Map.Entry<String, String> extEntry : injectModelVendorExtensions.entrySet()) {
String[] parts = extEntry.getKey().split("\\.", 3);
if (parts.length < 2) continue;
String modelName = parts[0];
String extensionValue = extEntry.getValue();

if (!modelName.equals(entry.getKey())) continue;

if (parts.length == 2) {
// class-level extension: ModelName.x-extension-name
model.vendorExtensions.put(parts[1], extensionValue);
} else {
// property-level extension: ModelName.propertyBaseName.x-extension-name
String propertyBaseName = parts[1];
String extensionName = parts[2];
List<List<CodegenProperty>> allPropertyLists = Arrays.asList(
model.vars, model.allVars, model.readWriteVars, model.requiredVars,
model.optionalVars, model.parentVars, model.readOnlyVars, model.nonNullableVars);
for (List<CodegenProperty> properties : allPropertyLists) {
for (CodegenProperty property : properties) {
if (propertyBaseName.equals(property.baseName)) {
property.vendorExtensions.put(extensionName, extensionValue);
}
}
}
}
}
}
}

if (this.useOneOfInterfaces) {
// First, add newly created oneOf interfaces
for (CodegenModel cm : addOneOfInterfaces) {
Expand Down Expand Up @@ -1366,6 +1404,11 @@ public Map<String, String> operationIdNameMapping() {
return operationIdNameMapping;
}

@Override
public Map<String, String> injectModelVendorExtensions() {
return injectModelVendorExtensions;
}

@Override
public Map<String, String> openapiNormalizer() {
return openapiNormalizer;
Expand Down Expand Up @@ -6799,31 +6842,59 @@ protected List<Map<String, Object>> buildEnumVars(List<Object> values, String da
}

if (enumUnknownDefaultCase) {
// If the server adds new enum cases, that are unknown by an old spec/client, the client will fail to parse the network response.
// With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the server sends an enum case that is not known by the client/spec, they can safely fallback to this case.
Map<String, Object> enumVar = new HashMap<>();
String enumName = enumUnknownDefaultCaseName;

String enumValue = isDataTypeString(dataType)
? enumUnknownDefaultCaseName
: // This is a dummy value that attempts to avoid collisions with previously specified cases.
// Int.max / 192
// The number 192 that is used to calculate this random value, is the Swift Evolution proposal for frozen/non-frozen enums.
// [SE-0192](https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md)
// Since this functionality was born in the Swift 5 generator and latter on broth to all generators
// https://github.com/OpenAPITools/openapi-generator/pull/11013
String.valueOf(11184809);

enumVar.put(ENUM_NAME, toEnumVarName(enumName, dataType));
enumVar.put(ENUM_VALUE, toEnumValue(enumValue, dataType));
enumVar.put(ENUM_IS_STRING, isDataTypeString(dataType));
// TODO: add isNumeric
enumVars.add(enumVar);
injectEnumUnknownDefaultCase(enumVars, dataType);
}

return enumVars;
}

/**
* If the server adds new enum cases, that are unknown by an old spec/client, the client will fail to parse the network response.
* This adds a default case to the enum, {@link DefaultCodegen#enumUnknownDefaultCaseName}, that can be used as a fallback for unknown values.
*
* @param enumVars the enumVars
* @param dataType the data type of the enum parameter
*/
private void injectEnumUnknownDefaultCase(List<Map<String, Object>> enumVars, String dataType) {
Map<String, Object> enumVar = new HashMap<>();
String enumName = enumUnknownDefaultCaseName;

String enumValue = isDataTypeString(dataType)
? enumUnknownDefaultCaseName
: // This is a dummy value that attempts to avoid collisions with previously specified cases.
// Int.max / 192
// The number 192 that is used to calculate this random value, is the Swift Evolution proposal for frozen/non-frozen enums.
// [SE-0192](https://github.com/apple/swift-evolution/blob/master/proposals/0192-non-exhaustive-enums.md)
// Since this functionality was born in the Swift 5 generator and latter on broth to all generators
// https://github.com/OpenAPITools/openapi-generator/pull/11013
String.valueOf(11184809);

enumVar.put(ENUM_NAME, toEnumVarName(enumName, dataType));
enumVar.put(ENUM_VALUE, toEnumValue(enumValue, dataType));
enumVar.put(ENUM_IS_STRING, isDataTypeString(dataType));
// TODO: add isNumeric
enumVars.add(enumVar);
}

/**
* Removes any injected default enum value that was created with {@link DefaultCodegen#injectEnumUnknownDefaultCase}
* from an operation's non-body enum parameters. This can for example be of interest when generating client code
* where a fallback is superfluous for a value that is only sent and never received.
*
* @param operation operation to be processed
*/
protected void removeEnumUnknownDefaultCase(CodegenOperation operation) {
for (CodegenParameter param : operation.allParams) {
if (!param.isBodyParam && param.isEnum && hasEnumVars(param.allowableValues)) {
List<Map<String, Object>> enumVars = getEnumVars(param.allowableValues);
if (enumVars != null) {
String unknownName = toEnumVarName(enumUnknownDefaultCaseName, param.dataType);
enumVars.removeIf(ev -> unknownName.equals(ev.get(ENUM_NAME)));
}
}
}
}

protected void postProcessEnumVars(List<Map<String, Object>> enumVars) {
Collections.reverse(enumVars);
enumVars.forEach(v -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public enum VendorExtension {
X_CLASS_EXTRA_ANNOTATION("x-class-extra-annotation", ExtensionLevel.MODEL, "Custom annotation(s) to be added to model; accepts a string or list of strings", null),
X_FIELD_EXTRA_ANNOTATION("x-field-extra-annotation", Arrays.asList(ExtensionLevel.FIELD, ExtensionLevel.OPERATION_PARAMETER), "Custom annotation(s) to be added to property; accepts a string or list of strings", null),
X_OPERATION_EXTRA_ANNOTATION("x-operation-extra-annotation", ExtensionLevel.OPERATION, "Custom annotation(s) to be added to operation; accepts a string or list of strings", null),
X_EXTRA_IMPORTS("x-extra-imports", Arrays.asList(ExtensionLevel.MODEL, ExtensionLevel.FIELD, ExtensionLevel.OPERATION, ExtensionLevel.OPERATION_PARAMETER), "Custom import(s) to add to the generated file that declares the annotated model, property, operation, or parameter (e.g. so custom annotations can be referenced by their short name); accepts a string or list of strings. Values are emitted verbatim (Kotlin alias imports supported) and only exact duplicates are removed", null),
X_VERSION_PARAM("x-version-param", ExtensionLevel.OPERATION_PARAMETER, "Marker property that tells that this parameter would be used for endpoint versioning. Applicable for headers & query params. true/false", null),
X_PATTERN_MESSAGE("x-pattern-message", Arrays.asList(ExtensionLevel.FIELD, ExtensionLevel.OPERATION_PARAMETER), "Add this property whenever you need to customize the invalidation error message for the regex pattern of a variable", null),
X_SIZE_MESSAGE("x-size-message", Arrays.asList(ExtensionLevel.FIELD, ExtensionLevel.OPERATION_PARAMETER), "Add this property whenever you need to customize the invalidation error message for the size or length of a variable", null),
Expand Down
Loading
Loading