Skip to content
Open
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
2 changes: 2 additions & 0 deletions docs/generators/dart-dio.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|equalityCheckMethod|Specify equality check method. Takes effect only in case if serializationLibrary is json_serializable.|<dl><dt>**default**</dt><dd>[DEFAULT] Built in hash code generation method</dd><dt>**equatable**</dt><dd>Uses equatable library for equality checking</dd></dl>|default|
|finalProperties|Whether properties are marked as final when using Json Serializable for serialization| |true|
|legacyDiscriminatorBehavior|Set to false for generators with better support for discriminators. (Python, Java, Go, PowerShell, C# have this enabled by default).|<dl><dt>**true**</dt><dd>The mapping in the discriminator includes descendent schemas that allOf inherit from self and the discriminator mapping schemas in the OAS document.</dd><dt>**false**</dt><dd>The mapping in the discriminator includes any descendent schemas that allOf inherit from self, any oneOf schemas, any anyOf schemas, any x-discriminator-values, and the discriminator mapping schemas in the OAS document AND Codegen validates that oneOf and anyOf schemas contain the required discriminator and throws an error if the discriminator is missing.</dd></dl>|true|
|patchOnly|Only apply Optional&lt;T&gt; to PATCH operation request bodies (requires useOptional=true)| |false|
|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false|
|pubAuthor|Author name in generated pubspec| |Author|
|pubAuthorEmail|Email address of the author in generated pubspec| |author@homepage|
Expand All @@ -42,6 +43,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
|sourceFolder|source folder for generated code| |src|
|useEnumExtension|Allow the 'x-enum-values' extension for enums| |false|
|useOptional|Use Optional&lt;T&gt; to distinguish absent, null, and present for optional fields (Dart 3+)| |false|

## IMPORT MAPPING

Expand Down
2 changes: 2 additions & 0 deletions docs/generators/dart.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
|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.|<dl><dt>**false**</dt><dd>No changes to the enum's are made, this is the default option.</dd><dt>**true**</dt><dd>With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the enum case sent by the server is not known by the client/spec, can safely be decoded to this case.</dd></dl>|false|
|legacyDiscriminatorBehavior|Set to false for generators with better support for discriminators. (Python, Java, Go, PowerShell, C# have this enabled by default).|<dl><dt>**true**</dt><dd>The mapping in the discriminator includes descendent schemas that allOf inherit from self and the discriminator mapping schemas in the OAS document.</dd><dt>**false**</dt><dd>The mapping in the discriminator includes any descendent schemas that allOf inherit from self, any oneOf schemas, any anyOf schemas, any x-discriminator-values, and the discriminator mapping schemas in the OAS document AND Codegen validates that oneOf and anyOf schemas contain the required discriminator and throws an error if the discriminator is missing.</dd></dl>|true|
|patchOnly|Only apply Optional&lt;T&gt; to PATCH operation request bodies (requires useOptional=true)| |false|
|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false|
|pubAuthor|Author name in generated pubspec| |Author|
|pubAuthorEmail|Email address of the author in generated pubspec| |author@homepage|
Expand All @@ -38,6 +39,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
|sourceFolder|source folder for generated code| |src|
|useEnumExtension|Allow the 'x-enum-values' extension for enums| |false|
|useOptional|Use Optional&lt;T&gt; to distinguish absent, null, and present for optional fields (Dart 3+)| |false|

## IMPORT MAPPING

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package org.openapitools.codegen.languages;

import com.google.common.collect.Sets;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.servers.Server;
import lombok.Setter;
import org.apache.commons.io.FilenameUtils;
Expand Down Expand Up @@ -45,6 +48,8 @@ public abstract class AbstractDartCodegen extends DefaultCodegen {
public static final String PUB_REPOSITORY = "pubRepository";
public static final String PUB_PUBLISH_TO = "pubPublishTo";
public static final String USE_ENUM_EXTENSION = "useEnumExtension";
public static final String USE_OPTIONAL = "useOptional";
public static final String PATCH_ONLY = "patchOnly";

@Setter protected String pubLibrary = "openapi.api";
@Setter protected String pubName = "openapi";
Expand All @@ -56,8 +61,12 @@ public abstract class AbstractDartCodegen extends DefaultCodegen {
@Setter protected String pubRepository = null;
@Setter protected String pubPublishTo = null;
@Setter protected boolean useEnumExtension = false;
@Setter protected boolean useOptional = false;
@Setter protected boolean patchOnly = false;
@Setter protected String sourceFolder = "src";
protected String libPath = "lib" + File.separator;

protected Set<String> patchRequestSchemas = new HashSet<>();
protected String apiDocPath = "doc/";
protected String modelDocPath = "doc/";
protected String apiTestPath = "test" + File.separator;
Expand Down Expand Up @@ -195,6 +204,8 @@ public AbstractDartCodegen() {
addOption(PUB_REPOSITORY, "Repository in generated pubspec", pubRepository);
addOption(PUB_PUBLISH_TO, "Publish_to in generated pubspec", pubPublishTo);
addOption(USE_ENUM_EXTENSION, "Allow the 'x-enum-values' extension for enums", String.valueOf(useEnumExtension));
addOption(USE_OPTIONAL, "Use Optional<T> to distinguish absent, null, and present for optional fields (Dart 3+)", String.valueOf(useOptional));
addOption(PATCH_ONLY, "Only apply Optional<T> to PATCH operation request bodies (requires useOptional=true)", String.valueOf(patchOnly));
addOption(CodegenConstants.SOURCE_FOLDER, CodegenConstants.SOURCE_FOLDER_DESC, sourceFolder);
}

Expand Down Expand Up @@ -301,6 +312,24 @@ public void processOpts() {
additionalProperties.put(USE_ENUM_EXTENSION, useEnumExtension);
}

if (additionalProperties.containsKey(USE_OPTIONAL)) {
this.setUseOptional(convertPropertyToBooleanAndWriteBack(USE_OPTIONAL));
} else {
additionalProperties.put(USE_OPTIONAL, useOptional);
}

if (additionalProperties.containsKey(PATCH_ONLY)) {
this.setPatchOnly(convertPropertyToBooleanAndWriteBack(PATCH_ONLY));
} else {
additionalProperties.put(PATCH_ONLY, patchOnly);
}

if (patchOnly && !useOptional) {
LOGGER.warn("patchOnly=true requires useOptional=true. Setting useOptional=true.");
this.setUseOptional(true);
additionalProperties.put(USE_OPTIONAL, true);
}

if (additionalProperties.containsKey(CodegenConstants.SOURCE_FOLDER)) {
String srcFolder = (String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER);
this.setSourceFolder(srcFolder.replace('/', File.separatorChar));
Expand Down Expand Up @@ -544,6 +573,35 @@ public String getTypeDeclaration(Schema p) {
return super.getTypeDeclaration(p);
}

@Override
public void preprocessOpenAPI(OpenAPI openAPI) {
super.preprocessOpenAPI(openAPI);

if (patchOnly && openAPI.getPaths() != null) {
openAPI.getPaths().forEach((path, pathItem) -> {
if (pathItem.getPatch() != null) {
Operation patchOp = pathItem.getPatch();
if (patchOp.getRequestBody() != null) {
RequestBody requestBody = ModelUtils.getReferencedRequestBody(openAPI, patchOp.getRequestBody());
if (requestBody != null && requestBody.getContent() != null) {
requestBody.getContent().forEach((mediaType, content) -> {
if (content.getSchema() != null) {
String ref = content.getSchema().get$ref();
if (ref != null) {
String schemaName = ModelUtils.getSimpleRef(ref);
String modelName = toModelName(schemaName);
patchRequestSchemas.add(modelName);
LOGGER.info("Identified '{}' as PATCH request schema (will use Optional<T>)", modelName);
}
}
});
}
}
}
});
}
}

@Override
public String getSchemaType(Schema p) {
String openAPIType = super.getSchemaType(p);
Expand All @@ -558,7 +616,49 @@ public String getSchemaType(Schema p) {

@Override
public ModelsMap postProcessModels(ModelsMap objs) {
return postProcessModelsEnum(objs);
objs = postProcessModelsEnum(objs);

if (useOptional) {
for (ModelMap modelMap : objs.getModels()) {
CodegenModel model = modelMap.getModel();

boolean shouldUseOptional;

if (patchOnly) {
shouldUseOptional = patchRequestSchemas.contains(model.classname);
} else {
Boolean schemaUseOptional = (Boolean) model.vendorExtensions.get("x-use-optional");
shouldUseOptional = schemaUseOptional != null && schemaUseOptional;
}

if (shouldUseOptional) {
for (CodegenProperty prop : model.vars) {
if (!prop.required && !prop.dataType.startsWith("Optional<")) {
wrapPropertyWithOptional(prop);
}
}
}
}
}

return objs;
}

private void wrapPropertyWithOptional(CodegenProperty property) {
property.vendorExtensions.put("x-unwrapped-datatype", property.dataType);
property.vendorExtensions.put("x-is-optional", true);
property.vendorExtensions.put("x-original-is-number", property.isNumber);
property.vendorExtensions.put("x-original-is-integer", property.isInteger);

boolean hasNullableSuffix = property.dataType.endsWith("?");
String baseType = hasNullableSuffix ? property.dataType.substring(0, property.dataType.length() - 1) : property.dataType;
property.dataType = "Optional<" + baseType + "?" + ">";

if (property.datatypeWithEnum != null && !property.datatypeWithEnum.startsWith("Optional<")) {
hasNullableSuffix = property.datatypeWithEnum.endsWith("?");
baseType = hasNullableSuffix ? property.datatypeWithEnum.substring(0, property.datatypeWithEnum.length() - 1) : property.datatypeWithEnum;
property.datatypeWithEnum = "Optional<" + baseType + "?" + ">";
}
}

@Override
Expand Down Expand Up @@ -623,6 +723,19 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required) {
return property;
}

@Override
public CodegenParameter fromParameter(Parameter parameter, Set<String> imports) {
final CodegenParameter param = super.fromParameter(parameter, imports);

if (useOptional && param.dataType != null && param.dataType.startsWith("Optional<")) {
param.dataType = param.dataType.substring("Optional<".length(), param.dataType.length() - 1);
param.vendorExtensions.remove("x-is-optional");
param.vendorExtensions.remove("x-unwrapped-datatype");
}

return param;
}

@Override
public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, List<Server> servers) {
final CodegenOperation op = super.fromOperation(path, httpMethod, operation, servers);
Expand Down Expand Up @@ -659,6 +772,21 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
if (operations != null) {
List<CodegenOperation> ops = operations.getOperation();
for (CodegenOperation op : ops) {
if (patchOnly && "PATCH".equalsIgnoreCase(op.httpMethod)) {
if (op.bodyParam != null && op.bodyParam.dataType != null) {
String modelName = getString(op);
patchRequestSchemas.add(modelName);
LOGGER.debug("Marked schema '{}' for Optional wrapping (PATCH request body)", modelName);
}
}

if (useOptional) {
unwrapOptionalFromParameters(op.pathParams);
unwrapOptionalFromParameters(op.queryParams);
unwrapOptionalFromParameters(op.headerParams);
unwrapOptionalFromParameters(op.formParams);
}

if (op.hasConsumes) {
if (!op.formParams.isEmpty() || op.isMultipart) {
// DefaultCodegen only sets this if the first consumes mediaType
Expand All @@ -680,6 +808,29 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
return objs;
}

private static String getString(CodegenOperation op) {
String modelName = op.bodyParam.dataType;
if (modelName.startsWith("List<") || modelName.startsWith("Map<")) {
int start = modelName.indexOf('<') + 1;
int end = modelName.lastIndexOf('>');
if (start > 0 && end > start) {
modelName = modelName.substring(start, end);
}
}
modelName = modelName.replace("?", "");
return modelName;
}

private void unwrapOptionalFromParameters(List<CodegenParameter> params) {
if (params == null) return;
for (CodegenParameter param : params) {
if (param.dataType != null && param.dataType.startsWith("Optional<")) {
param.dataType = param.dataType.substring("Optional<".length(), param.dataType.length() - 1);
param.vendorExtensions.remove("x-is-optional");
}
}
}

private List<Map<String, String>> prioritizeContentTypes(List<Map<String, String>> consumes) {
if (consumes.size() <= 1) {
// no need to change any order
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ public void processOpts() {
supportingFiles.add(new SupportingFile("auth/http_bearer_auth.mustache", authFolder, "http_bearer_auth.dart"));
supportingFiles.add(new SupportingFile("auth/api_key_auth.mustache", authFolder, "api_key_auth.dart"));
supportingFiles.add(new SupportingFile("auth/oauth.mustache", authFolder, "oauth.dart"));

if (useOptional) {
supportingFiles.add(new SupportingFile("optional.mustache", libPath, "optional.dart"));
}

supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh"));
supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore"));
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ public void processOpts() {
supportingFiles.add(new SupportingFile("auth/oauth.mustache", authFolder, "oauth.dart"));
supportingFiles.add(new SupportingFile("auth/auth.mustache", authFolder, "auth.dart"));

if (useOptional) {
supportingFiles.add(new SupportingFile("optional.mustache", srcFolder, "optional.dart"));
}

configureSerializationLibrary(srcFolder);
configureEqualityCheckMethod(srcFolder);
configureDateLibrary(srcFolder);
Expand Down
Loading