org.openapitools.codegen.languages.RustAxumServerCodegen Maven / Gradle / Ivy
/*
* Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openapitools.codegen.languages;
import com.samskivert.mustache.Mustache;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.oas.models.tags.Tag;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.*;
import org.openapitools.codegen.meta.GeneratorMetadata;
import org.openapitools.codegen.meta.Stability;
import org.openapitools.codegen.meta.features.*;
import org.openapitools.codegen.model.ModelMap;
import org.openapitools.codegen.model.ModelsMap;
import org.openapitools.codegen.model.OperationMap;
import org.openapitools.codegen.model.OperationsMap;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.math.BigInteger;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import static org.openapitools.codegen.utils.StringUtils.camelize;
import static org.openapitools.codegen.utils.StringUtils.underscore;
public class RustAxumServerCodegen extends AbstractRustCodegen implements CodegenConfig {
public static final String PROJECT_NAME = "openapi-server";
private String packageName;
private String packageVersion;
private Boolean disableValidator = false;
private Boolean allowBlockingValidator = false;
private Boolean allowBlockingResponseSerialize = false;
private String externCrateName;
// Types
private static final String uuidType = "uuid::Uuid";
private static final String bytesType = "ByteArray";
private static final String dateType = "chrono::naive::NaiveDate";
private static final String dateTimeType = "chrono::DateTime::";
private static final String stringType = "String";
private static final String objectType = "crate::types::Object";
private static final String mapType = "std::collections::HashMap";
private static final String vecType = "Vec";
// Mime
private static final String octetMimeType = "application/octet-stream";
private static final String plainTextMimeType = "text/plain";
private static final String xmlMimeType = "application/xml";
private static final String textXmlMimeType = "text/xml";
private static final String formUrlEncodedMimeType = "application/x-www-form-urlencoded";
private static final String jsonMimeType = "application/json";
// RFC 7386 support
private static final String mergePatchJsonMimeType = "application/merge-patch+json";
// RFC 7807 Support
private static final String problemJsonMimeType = "application/problem+json";
private static final String problemXmlMimeType = "application/problem+xml";
// Grouping (Method, Operation) by Path.
private final Map> pathMethodOpMap = new HashMap<>();
private boolean havingAuthMethods = false;
// Logger
private final Logger LOGGER = LoggerFactory.getLogger(RustAxumServerCodegen.class);
public RustAxumServerCodegen() {
super();
modifyFeatureSet(features -> features
.wireFormatFeatures(EnumSet.of(
WireFormatFeature.JSON,
WireFormatFeature.Custom
))
.securityFeatures(EnumSet.of(
SecurityFeature.ApiKey
))
.excludeGlobalFeatures(
GlobalFeature.Info,
GlobalFeature.ExternalDocumentation,
GlobalFeature.Examples,
GlobalFeature.XMLStructureDefinitions,
GlobalFeature.MultiServer,
GlobalFeature.ParameterizedServer,
GlobalFeature.ParameterStyling,
GlobalFeature.Callbacks,
GlobalFeature.LinkObjects
)
.excludeSchemaSupportFeatures(
SchemaSupportFeature.Polymorphism
)
);
generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
.stability(Stability.BETA)
.build();
// Show the generation timestamp by default
hideGenerationTimestamp = Boolean.FALSE;
// set the output folder here
outputFolder = Path.of("generated-code", "rust-axum").toString();
embeddedTemplateDir = templateDir = "rust-axum";
importMapping = new HashMap<>();
modelTemplateFiles.clear();
apiTemplateFiles.put("apis.mustache", ".rs");
// types
defaultIncludes = new HashSet<>(
Set.of("map", "array")
);
languageSpecificPrimitives = new HashSet<>(
Set.of(
"bool",
"char",
"i8",
"i16",
"i32",
"i64",
"u8",
"u16",
"u32",
"u64",
"isize",
"usize",
"f32",
"f64",
"str",
stringType)
);
assert languageSpecificPrimitives.size() == 16;
instantiationTypes = new HashMap<>(
Map.of(
"array", vecType,
"map", mapType
)
);
assert instantiationTypes.size() == 2;
typeMapping = new HashMap<>(Map.ofEntries(
new AbstractMap.SimpleEntry<>("number", "f64"),
new AbstractMap.SimpleEntry<>("integer", "i32"),
new AbstractMap.SimpleEntry<>("long", "i64"),
new AbstractMap.SimpleEntry<>("float", "f32"),
new AbstractMap.SimpleEntry<>("double", "f64"),
new AbstractMap.SimpleEntry<>("string", stringType),
new AbstractMap.SimpleEntry<>("UUID", uuidType),
new AbstractMap.SimpleEntry<>("URI", stringType),
new AbstractMap.SimpleEntry<>("byte", "u8"),
new AbstractMap.SimpleEntry<>("ByteArray", bytesType),
new AbstractMap.SimpleEntry<>("binary", bytesType),
new AbstractMap.SimpleEntry<>("boolean", "bool"),
new AbstractMap.SimpleEntry<>("date", dateType),
new AbstractMap.SimpleEntry<>("DateTime", dateTimeType),
new AbstractMap.SimpleEntry<>("password", stringType),
new AbstractMap.SimpleEntry<>("File", bytesType),
new AbstractMap.SimpleEntry<>("file", bytesType),
new AbstractMap.SimpleEntry<>("array", vecType),
new AbstractMap.SimpleEntry<>("map", mapType),
new AbstractMap.SimpleEntry<>("object", objectType),
new AbstractMap.SimpleEntry<>("AnyType", objectType)
));
assert typeMapping.size() == 21;
// cli options
CliOption optDisableValidator = new CliOption("disableValidator", "Disable validating request-data (header, path, query, body) " +
"against OpenAPI Schema Specification.");
optDisableValidator.setType("bool");
optDisableValidator.defaultValue(disableValidator.toString());
CliOption optAllowBlockingValidator = new CliOption("allowBlockingValidator",
String.join("",
"By default, validation process, which might perform a lot of compute in a ",
"future without yielding, is executed on a blocking thread via tokio::task::spawn_blocking. ",
"Set this option to true will override this behaviour and allow blocking call to happen. ",
"It helps to improve the performance when validating request-data (header, path, query, body) ",
"is low cost."));
optAllowBlockingValidator.setType("bool");
optAllowBlockingValidator.defaultValue(allowBlockingValidator.toString());
CliOption optAllowBlockingResponseSerialize = new CliOption("allowBlockingResponseSerialize",
String.join("", "By default, json/form-urlencoded response serialization, which might ",
"perform a lot of compute in a future without yielding, is executed on a blocking thread ",
"via tokio::task::spawn_blocking. Set this option to true will override this behaviour and ",
"allow blocking call to happen. It helps to improve the performance when response ",
"serialization (e.g. returns tiny data) is low cost."));
optAllowBlockingResponseSerialize.setType("bool");
optAllowBlockingResponseSerialize.defaultValue(allowBlockingResponseSerialize.toString());
cliOptions = new ArrayList<>(
List.of(
new CliOption(CodegenConstants.PACKAGE_NAME,
"Rust crate name (convention: snake_case).")
.defaultValue("openapi"),
new CliOption(CodegenConstants.PACKAGE_VERSION,
"Rust crate version."),
optDisableValidator,
optAllowBlockingValidator,
optAllowBlockingResponseSerialize
)
);
supportingFiles.add(new SupportingFile("Cargo.mustache", "", "Cargo.toml"));
supportingFiles.add(new SupportingFile("gitignore", "", ".gitignore"));
supportingFiles.add(new SupportingFile("lib.mustache", "src", "lib.rs"));
supportingFiles.add(new SupportingFile("models.mustache", "src", "models.rs"));
supportingFiles.add(new SupportingFile("types.mustache", "src", "types.rs"));
supportingFiles.add(new SupportingFile("header.mustache", "src", "header.rs"));
supportingFiles.add(new SupportingFile("server-mod.mustache", "src/server", "mod.rs"));
supportingFiles.add(new SupportingFile("apis-mod.mustache", apiPackage().replace('.', File.separatorChar), "mod.rs"));
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")
.doNotOverwrite());
}
@Override
public CodegenType getTag() {
return CodegenType.SERVER;
}
@Override
public String getName() {
return "rust-axum";
}
@Override
public String getHelp() {
return "Generates a Rust server library which bases on Axum.";
}
@Override
public Mustache.Compiler processCompiler(Mustache.Compiler compiler) {
return compiler
.emptyStringIsFalse(true)
.zeroIsFalse(true);
}
@Override
public void processOpts() {
super.processOpts();
if (StringUtils.isEmpty(System.getenv("RUST_POST_PROCESS_FILE"))) {
LOGGER.info("Environment variable RUST_POST_PROCESS_FILE not defined. rustfmt will be used" +
" by default. To choose a different tool, try" +
" 'export RUST_POST_PROCESS_FILE=\"/usr/local/bin/rustfmt\"' (Linux/Mac)");
LOGGER.info("NOTE: To enable file post-processing, 'enablePostProcessFile' must be set to `true` " +
" (--enable-post-process-file for CLI).");
} else if (!this.isEnablePostProcessFile()) {
LOGGER.info("Warning: Environment variable 'RUST_POST_PROCESS_FILE' is set but file post-processing is not enabled. To enable file post-processing, 'enablePostProcessFile' must be set to `true` (--enable-post-process-file for CLI).");
}
if (!Boolean.TRUE.equals(ModelUtils.isGenerateAliasAsModel())) {
LOGGER.warn("generateAliasAsModel is set to false, which means array/map will be generated as model instead and the resulting code may have issues. Please enable `generateAliasAsModel` to address the issue.");
}
setPackageName((String) additionalProperties.getOrDefault(CodegenConstants.PACKAGE_NAME, "openapi"));
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) {
setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION));
}
additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName);
additionalProperties.put("externCrateName", externCrateName);
if (additionalProperties.containsKey("disableValidator")) {
disableValidator = convertPropertyToBooleanAndWriteBack("disableValidator");
} else {
additionalProperties.put("disableValidator", disableValidator);
}
if (additionalProperties.containsKey("allowBlockingValidator")) {
allowBlockingValidator = convertPropertyToBooleanAndWriteBack("allowBlockingValidator");
} else {
additionalProperties.put("allowBlockingValidator", allowBlockingValidator);
}
if (additionalProperties.containsKey("allowBlockingResponseSerialize")) {
allowBlockingResponseSerialize = convertPropertyToBooleanAndWriteBack("allowBlockingResponseSerialize");
} else {
additionalProperties.put("allowBlockingResponseSerialize", allowBlockingResponseSerialize);
}
}
private void setPackageName(String packageName) {
this.packageName = packageName;
// Also set the extern crate name, which has any '-' replace with a '_'.
this.externCrateName = packageName.replace('-', '_');
}
private void setPackageVersion(String packageVersion) {
this.packageVersion = packageVersion;
}
@Override
public String apiPackage() {
return "src" + File.separator + "apis";
}
@Override
public void preprocessOpenAPI(OpenAPI openAPI) {
Info info = openAPI.getInfo();
if (packageVersion == null || packageVersion.isEmpty()) {
List versionComponents = new ArrayList<>(Arrays.asList(info.getVersion().split("[.]")));
if (versionComponents.isEmpty()) {
versionComponents.add("1");
}
while (versionComponents.size() < 3) {
versionComponents.add("0");
}
setPackageVersion(String.join(".", versionComponents));
}
additionalProperties.put(CodegenConstants.PACKAGE_VERSION, packageVersion);
}
@Override
public String toApiName(String name) {
return name.isEmpty() ?
"default" :
sanitizeIdentifier(name, CasingType.SNAKE_CASE, "api", "API", true);
}
@Override
public String toApiFilename(String name) {
return toApiName(name);
}
/**
* Location to write api files. You can use the apiPackage() as defined when the class is
* instantiated
*/
@Override
public String apiFileFolder() {
return Path.of(outputFolder, apiPackage().replace('.', File.separatorChar)).toString();
}
@Override
public String toOperationId(String operationId) {
return sanitizeIdentifier(operationId, CasingType.CAMEL_CASE, "call", "method", true);
}
@Override
public String toEnumValue(String value, String datatype) {
return "\"" + super.toEnumValue(value, datatype) + "\"";
}
private boolean isObjectType(String type) {
return "object".equals(type);
}
private boolean isMimetypeXml(String mimetype) {
return mimetype.toLowerCase(Locale.ROOT).startsWith(xmlMimeType) ||
mimetype.toLowerCase(Locale.ROOT).startsWith(problemXmlMimeType) ||
mimetype.toLowerCase(Locale.ROOT).startsWith(textXmlMimeType);
}
private boolean isMimetypeJson(String mimetype) {
return mimetype.toLowerCase(Locale.ROOT).startsWith(jsonMimeType) ||
mimetype.toLowerCase(Locale.ROOT).startsWith(mergePatchJsonMimeType) ||
mimetype.toLowerCase(Locale.ROOT).startsWith(problemJsonMimeType);
}
private boolean isMimetypeWwwFormUrlEncoded(String mimetype) {
return mimetype.toLowerCase(Locale.ROOT).startsWith(formUrlEncodedMimeType);
}
private boolean isMimetypeMultipartFormData(String mimetype) {
return mimetype.toLowerCase(Locale.ROOT).startsWith("multipart/form-data");
}
private boolean isMimetypeMultipartRelated(String mimetype) {
return mimetype.toLowerCase(Locale.ROOT).startsWith("multipart/related");
}
private boolean isMimetypeUnknown(String mimetype) {
return "*/*".equals(mimetype);
}
boolean isMimetypePlain(String mimetype) {
return !(isMimetypeUnknown(mimetype) ||
isMimetypeJson(mimetype) ||
isMimetypeWwwFormUrlEncoded(mimetype) ||
isMimetypeMultipartFormData(mimetype) ||
isMimetypeMultipartRelated(mimetype));
}
@Override
public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, List servers) {
CodegenOperation op = super.fromOperation(path, httpMethod, operation, servers);
String underscoredOperationId = underscore(op.operationId);
op.vendorExtensions.put("x-operation-id", underscoredOperationId);
op.vendorExtensions.put("x-uppercase-operation-id", underscoredOperationId.toUpperCase(Locale.ROOT));
if (!op.isCallbackRequest) {
// group route by path
String axumPath = op.path;
for (CodegenParameter param : op.pathParams) {
// Replace {baseName} with {paramName} for format string
String paramSearch = "{" + param.baseName + "}";
String paramReplace = ":" + param.paramName;
axumPath = axumPath.replace(paramSearch, paramReplace);
}
pathMethodOpMap
.computeIfAbsent(axumPath, (key) -> new ArrayList<>())
.add(new MethodOperation(
op.httpMethod.toLowerCase(Locale.ROOT),
underscoredOperationId,
op.vendorExtensions));
}
// Determine the types that this operation produces. `getProducesInfo`
// simply lists all the types, and then we add the correct imports to
// the generated library.
Set producesInfo = getProducesInfo(openAPI, operation);
boolean producesPlainText = false;
boolean producesFormUrlEncoded = false;
if (producesInfo != null && !producesInfo.isEmpty()) {
List
© 2015 - 2024 Weber Informatics LLC | Privacy Policy