org.openapitools.codegen.languages.ScalaSttp4ClientCodegen Maven / Gradle / Ivy
package org.openapitools.codegen.languages;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
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.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ScalaSttp4ClientCodegen extends AbstractScalaCodegen implements CodegenConfig {
private static final StringProperty STTP_CLIENT_VERSION = new StringProperty("sttpClientVersion", "The version of " +
"sttp client", "4.0.0-M1");
private static final BooleanProperty USE_SEPARATE_ERROR_CHANNEL = new BooleanProperty("separateErrorChannel",
"Whether to return response as " +
"F[Either[ResponseError[ErrorType], ReturnType]]] or to flatten " +
"response's error raising them through enclosing monad (F[ReturnType]).", true);
private static final StringProperty JODA_TIME_VERSION = new StringProperty("jodaTimeVersion", "The version of " +
"joda-time library", "2.10.13");
private static final StringProperty JSON4S_VERSION = new StringProperty("json4sVersion", "The version of json4s " +
"library", "4.0.6");
private static final JsonLibraryProperty JSON_LIBRARY_PROPERTY = new JsonLibraryProperty();
public static final String DEFAULT_PACKAGE_NAME = "org.openapitools.client";
private static final PackageProperty PACKAGE_PROPERTY = new PackageProperty();
private static final List> properties = Arrays.asList(
STTP_CLIENT_VERSION, USE_SEPARATE_ERROR_CHANNEL, JODA_TIME_VERSION,
JSON4S_VERSION, JSON_LIBRARY_PROPERTY, PACKAGE_PROPERTY);
private final Logger LOGGER = LoggerFactory.getLogger(ScalaSttp4ClientCodegen.class);
protected String groupId = "org.openapitools";
protected String artifactId = "openapi-client";
protected String artifactVersion = "1.0.0";
protected boolean registerNonStandardStatusCodes = true;
protected boolean renderJavadoc = true;
protected boolean removeOAuthSecurities = true;
Map enumRefs = new HashMap<>();
public ScalaSttp4ClientCodegen() {
super();
generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
.stability(Stability.BETA)
.build();
modifyFeatureSet(features -> features
.includeDocumentationFeatures(DocumentationFeature.Readme)
.wireFormatFeatures(EnumSet.of(WireFormatFeature.JSON, WireFormatFeature.XML, WireFormatFeature.Custom))
.securityFeatures(EnumSet.of(
SecurityFeature.BasicAuth,
SecurityFeature.ApiKey,
SecurityFeature.BearerToken
))
.excludeGlobalFeatures(
GlobalFeature.XMLStructureDefinitions,
GlobalFeature.Callbacks,
GlobalFeature.LinkObjects,
GlobalFeature.ParameterStyling
)
.excludeSchemaSupportFeatures(
SchemaSupportFeature.Polymorphism
)
.excludeParameterFeatures(
ParameterFeature.Cookie
)
.includeClientModificationFeatures(
ClientModificationFeature.BasePath,
ClientModificationFeature.UserAgent
)
);
outputFolder = "generated-code/scala-sttp4";
modelTemplateFiles.put("model.mustache", ".scala");
apiTemplateFiles.put("api.mustache", ".scala");
embeddedTemplateDir = templateDir = "scala-sttp4";
String jsonLibrary = JSON_LIBRARY_PROPERTY.getValue(additionalProperties);
String jsonValueClass = "circe".equals(jsonLibrary) ? "io.circe.Json" : "org.json4s.JValue";
additionalProperties.put(CodegenConstants.GROUP_ID, groupId);
additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId);
additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion);
if (renderJavadoc) {
additionalProperties.put("javadocRenderer", new JavadocLambda());
}
additionalProperties.put("fnCapitalize", new CapitalizeLambda());
additionalProperties.put("fnCamelize", new CamelizeLambda(false));
additionalProperties.put("fnEnumEntry", new EnumEntryLambda());
// importMapping.remove("Seq");
// importMapping.remove("List");
// importMapping.remove("Set");
// importMapping.remove("Map");
// TODO: there is no specific sttp mapping. All Scala Type mappings should be in AbstractScala
typeMapping = new HashMap<>();
typeMapping.put("array", "Seq");
typeMapping.put("set", "Set");
typeMapping.put("boolean", "Boolean");
typeMapping.put("string", "String");
typeMapping.put("int", "Int");
typeMapping.put("integer", "Int");
typeMapping.put("long", "Long");
typeMapping.put("float", "Float");
typeMapping.put("byte", "Byte");
typeMapping.put("short", "Short");
typeMapping.put("char", "Char");
typeMapping.put("double", "Double");
typeMapping.put("object", "Any");
typeMapping.put("file", "File");
typeMapping.put("binary", "File");
typeMapping.put("number", "Double");
typeMapping.put("decimal", "BigDecimal");
typeMapping.put("ByteArray", "Array[Byte]");
typeMapping.put("AnyType", jsonValueClass);
instantiationTypes.put("array", "ListBuffer");
instantiationTypes.put("map", "Map");
properties.stream()
.map(Property::toCliOptions)
.flatMap(Collection::stream)
.forEach(option -> cliOptions.add(option));
}
@Override
public void processOpts() {
super.processOpts();
properties.forEach(p -> p.updateAdditionalProperties(additionalProperties));
invokerPackage = PACKAGE_PROPERTY.getInvokerPackage(additionalProperties);
apiPackage = PACKAGE_PROPERTY.getApiPackage(additionalProperties);
modelPackage = PACKAGE_PROPERTY.getModelPackage(additionalProperties);
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
supportingFiles.add(new SupportingFile("build.sbt.mustache", "", "build.sbt"));
final String invokerFolder = (sourceFolder + File.separator + invokerPackage).replace(".", File.separator);
supportingFiles.add(new SupportingFile("jsonSupport.mustache", invokerFolder, "JsonSupport.scala"));
supportingFiles.add(new SupportingFile("additionalTypeSerializers.mustache", invokerFolder, "AdditionalTypeSerializers.scala"));
supportingFiles.add(new SupportingFile("project/build.properties.mustache", "project", "build.properties"));
supportingFiles.add(new SupportingFile("dateSerializers.mustache", invokerFolder, "DateSerializers.scala"));
}
@Override
public String getName() {
return "scala-sttp4";
}
@Override
public String getHelp() {
return "Generates a Scala client library (beta) based on Sttp4.";
}
@Override
public String encodePath(String input) {
String path = super.encodePath(input);
// The parameter names in the URI must be converted to the same case as
// the method parameter.
StringBuffer buf = new StringBuffer(path.length());
Matcher matcher = Pattern.compile("[{](.*?)[}]").matcher(path);
while (matcher.find()) {
matcher.appendReplacement(buf, "\\${" + toParamName(matcher.group(0)) + "}");
}
matcher.appendTail(buf);
return buf.toString();
}
@Override
public CodegenOperation fromOperation(String path,
String httpMethod,
Operation operation,
List servers) {
CodegenOperation op = super.fromOperation(path, httpMethod, operation, servers);
op.path = encodePath(path);
return op;
}
@Override
public CodegenType getTag() {
return CodegenType.CLIENT;
}
@Override
public String escapeReservedWord(String name) {
if (this.reservedWordsMappings().containsKey(name)) {
return this.reservedWordsMappings().get(name);
}
return "`" + name + "`";
}
@Override
public ModelsMap postProcessModels(ModelsMap objs) {
return objs;
}
/**
* Invoked by {@link DefaultGenerator} after all models have been post-processed,
* allowing for a last pass of codegen-specific model cleanup.
*
* @param objs Current state of codegen object model.
* @return An in-place modified state of the codegen object model.
*/
@Override
public Map postProcessAllModels(Map objs) {
final Map processed = super.postProcessAllModels(objs);
postProcessUpdateImports(processed);
return processed;
}
/**
* Update/clean up model imports
*
* append '._" if the import is a Enum class, otherwise
* remove model imports to avoid warnings for importing class in the same package in Scala
*
* @param models processed models to be further processed
*/
@SuppressWarnings("unchecked")
private void postProcessUpdateImports(final Map models) {
final String prefix = modelPackage() + ".";
enumRefs = getEnumRefs(models);
for (String openAPIName : models.keySet()) {
CodegenModel model = ModelUtils.getModelByName(openAPIName, models);
if (model == null) {
LOGGER.warn("Expected to retrieve model {} by name, but no model was found. Check your -Dmodels inclusions.", openAPIName);
continue;
}
ModelsMap objs = models.get(openAPIName);
List