Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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