org.openapitools.codegen.languages.ScalaHttp4sServerCodegen Maven / Gradle / Ivy
The newest version!
/*
* 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 io.swagger.v3.oas.models.media.Schema;
import org.openapitools.codegen.*;
import org.openapitools.codegen.meta.features.*;
import org.openapitools.codegen.model.*;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
public class ScalaHttp4sServerCodegen extends DefaultCodegen implements CodegenConfig {
private final Logger LOGGER = LoggerFactory.getLogger(ScalaHttp4sServerCodegen.class);
protected String artifactId = "http4s-server";
protected String artifactVersion = "1.0.0";
protected String sourceFolder = "scala";
protected String sourceSubFolder = "main";
private String packageName = "org.openapitools";
public static final String EXCLUDE_SBT = "excludeSbt"; // generate as whole project
public static final String SOURCE_SUBFOLDER = "sourceSubfolder"; // generate as whole project
public ScalaHttp4sServerCodegen() {
super();
modifyFeatureSet(features -> features
.wireFormatFeatures(EnumSet.of(WireFormatFeature.JSON, WireFormatFeature.XML, WireFormatFeature.Custom))
.securityFeatures(EnumSet.noneOf(SecurityFeature.class))
.excludeGlobalFeatures(
GlobalFeature.XMLStructureDefinitions,
GlobalFeature.Callbacks,
GlobalFeature.LinkObjects,
GlobalFeature.ParameterStyling
)
.excludeSchemaSupportFeatures(
SchemaSupportFeature.Polymorphism
)
.excludeParameterFeatures(
ParameterFeature.Cookie
)
);
embeddedTemplateDir = templateDir = "scala-http4s-server";
apiPackage = packageName + ".apis";
modelPackage = packageName + ".models";
useOneOfInterfaces = true;
supportsMultipleInheritance = true;
supportsInheritance = true;
supportsMixins = true;
addOneOfInterfaceImports = true;
setReservedWordsLowerCase(
Arrays.asList(
// Scala
"abstract", "case", "catch", "class", "def",
"do", "else", "extends", "false", "final",
"finally", "for", "forSome", "if", "implicit",
"import", "lazy", "match", "new", "null",
"object", "override", "package", "private", "protected",
"return", "sealed", "super", "this", "throw",
"trait", "try", "true", "type", "val",
"var", "while", "with", "yield",
// Scala-interop languages keywords
"abstract", "continue", "switch", "assert",
"default", "synchronized", "goto",
"break", "double", "implements", "byte",
"public", "throws", "enum", "instanceof", "transient",
"int", "short", "char", "interface", "static",
"void", "finally", "long", "strictfp", "volatile", "const", "float",
"native")
);
defaultIncludes = new HashSet<>(
Arrays.asList("double",
"Int",
"Long",
"Float",
"Double",
"char",
"float",
"String",
"boolean",
"Boolean",
"Double",
"Integer",
"Long",
"Float",
"List",
"Set",
"Map")
);
typeMapping = new HashMap<>();
typeMapping.put("string", "String");
typeMapping.put("boolean", "Boolean");
typeMapping.put("integer", "Int");
typeMapping.put("long", "Long");
typeMapping.put("float", "Float");
typeMapping.put("double", "Double");
typeMapping.put("number", "BigDecimal");
typeMapping.put("decimal", "BigDecimal");
typeMapping.put("date-time", "ZonedDateTime");
typeMapping.put("offset-date-time", "OffsetDateTime");
typeMapping.put("date", "LocalDate");
typeMapping.put("file", "File");
typeMapping.put("array", "List");
typeMapping.put("list", "List");
typeMapping.put("map", "Map");
typeMapping.put("object", "Object");
typeMapping.put("binary", "Array[Byte]");
typeMapping.put("Date", "LocalDate");
typeMapping.put("DateTime", "ZonedDateTime");
typeMapping.put("OffsetDateTime", "OffsetDateTime");
typeMapping.put("uuid", "UUID");
additionalProperties.put("modelPackage", modelPackage());
additionalProperties.put("apiPackage", apiPackage());
additionalProperties.put("infoUrl", "http://org.openapitools");
additionalProperties.put("infoEmail", "[email protected]");
additionalProperties.put("licenseInfo", "Apache 2.0");
additionalProperties.put("licenseUrl", "http://apache.org/licenses/LICENSE-2.0.html");
languageSpecificPrimitives = new HashSet<>(
Arrays.asList(
"String",
"Boolean",
"Double",
"Int",
"Integer",
"Long",
"Float",
"Any",
"AnyVal",
"AnyRef",
"Object",
"BigDecimal"
)
);
instantiationTypes.put("array", "ArrayList");
instantiationTypes.put("map", "HashMap");
importMapping = new HashMap<>();
importMapping.put("UUID", "java.util.UUID");
importMapping.put("URI", "java.net.URI");
importMapping.put("File", "java.io.File");
importMapping.put("Date", "java.util.Date");
importMapping.put("Timestamp", "java.sql.Timestamp");
importMapping.put("Map", "scala.collection.immutable.Map");
importMapping.put("HashMap", "scala.collection.immutable.HashMap");
importMapping.put("Seq", "scala.collection.immutable.Seq");
importMapping.put("ArrayBuffer", "scala.collection.mutable.ArrayBuffer");
importMapping.put("DateTime", "java.time.LocalDateTime");
importMapping.put("LocalDateTime", "java.time.LocalDateTime");
importMapping.put("LocalDate", "java.time.LocalDate");
importMapping.put("LocalTime", "java.time.LocalTime");
importMapping.put("ZonedDateTime", "java.time.ZonedDateTime");
importMapping.put("OffsetDateTime", "java.time.OffsetDateTime");
//refined
importMapping.put("Refined", "eu.timepit.refined.api.Refined");
importMapping.put("And", "eu.timepit.refined.boolean.And");
importMapping.put("MinSize", "eu.timepit.refined.collection.MinSize");
importMapping.put("MaxSize", "eu.timepit.refined.collection.MaxSize");
importMapping.put("MatchesRegex", "eu.timepit.refined.string.MatchesRegex");
importMapping.put("Greater", "eu.timepit.refined.numeric.Greater");
importMapping.put("GreaterEqual", "eu.timepit.refined.numeric.GreaterEqual");
importMapping.put("Less", "eu.timepit.refined.numeric.Less");
importMapping.put("LessEqual", "eu.timepit.refined.numeric.LessEqual");
cliOptions.add(new CliOption(EXCLUDE_SBT, "exclude sbt from generation"));
cliOptions.add(new CliOption(SOURCE_SUBFOLDER, "name of subfolder, for example to generate code in src/scala/generated"));
inlineSchemaOption.put("SKIP_SCHEMA_REUSE", "true");
inlineSchemaOption.put("REFACTOR_ALLOF_INLINE_SCHEMAS", "true");
}
private final static Map locationStatusToResponse = new HashMap<>();
static {
locationStatusToResponse.put("300", "MultipleChoices");
locationStatusToResponse.put("301", "MovedPermanently");
locationStatusToResponse.put("302", "Found");
locationStatusToResponse.put("303", "SeeOther");
locationStatusToResponse.put("307", "TemporaryRedirect");
locationStatusToResponse.put("308", "PermanentRedirect");
}
private final static Map wwwAuthStatusToResponse = new HashMap<>();
static {
wwwAuthStatusToResponse.put("401", "Unauthorized");
}
private final static Map allowStatusToResponse = new HashMap<>();
static {
allowStatusToResponse.put("405", "MethodNotAllowed");
}
private final static Map proxyAuthStatusToResponse = new HashMap<>();
static {
proxyAuthStatusToResponse.put("407", "ProxyAuthenticationRequired");
}
private final static Map statusToResponse = new HashMap<>();
static {
statusToResponse.put("100", "Continue");
statusToResponse.put("101", "SwitchingProtocols");
statusToResponse.put("102", "Processing");
statusToResponse.put("103", "EarlyHints");
statusToResponse.put("200", "Ok");
statusToResponse.put("201", "Created");
statusToResponse.put("202", "Accepted");
statusToResponse.put("203", "NonAuthoritativeInformation");
statusToResponse.put("204", "NoContent");
statusToResponse.put("205", "ResetContent");
statusToResponse.put("206", "PartialContent");
statusToResponse.put("207", "MultiStatus");
statusToResponse.put("208", "AlreadyReported");
statusToResponse.put("226", "IMUsed");
statusToResponse.put("304", "NotModified");
statusToResponse.put("305", "UseProxy");
statusToResponse.put("400", "BadRequest");
statusToResponse.put("402", "PaymentRequired");
statusToResponse.put("403", "Forbidden");
statusToResponse.put("404", "NotFound");
statusToResponse.put("406", "NotAcceptable");
statusToResponse.put("408", "RequestTimeout");
statusToResponse.put("409", "Conflict");
statusToResponse.put("410", "Gone");
statusToResponse.put("411", "LengthRequired");
statusToResponse.put("412", "PreconditionFailed");
statusToResponse.put("413", "PayloadTooLarge");
statusToResponse.put("414", "UriTooLong");
statusToResponse.put("415", "UnsupportedMediaType");
statusToResponse.put("416", "RangeNotSatisfiable");
statusToResponse.put("417", "ExpectationFailed");
statusToResponse.put("418", "ImATeapot");
statusToResponse.put("421", "MisdirectedRequest");
statusToResponse.put("422", "UnprocessableEntity");
statusToResponse.put("423", "Locked");
statusToResponse.put("424", "FailedDependency");
statusToResponse.put("425", "TooEarly");
statusToResponse.put("426", "UpgradeRequired");
statusToResponse.put("428", "PreconditionRequired");
statusToResponse.put("429", "TooManyRequests");
statusToResponse.put("431", "RequestHeaderFieldsTooLarge");
statusToResponse.put("451", "UnavailableForLegalReasons");
statusToResponse.put("500", "InternalServerError");
statusToResponse.put("501", "NotImplemented");
statusToResponse.put("502", "BadGateway");
statusToResponse.put("503", "ServiceUnavailable");
statusToResponse.put("504", "GatewayTimeout");
statusToResponse.put("505", "HttpVersionNotSupported");
statusToResponse.put("506", "VariantAlsoNegotiates");
statusToResponse.put("507", "InsufficientStorage");
statusToResponse.put("508", "LoopDetected");
statusToResponse.put("510", "NotExtended");
statusToResponse.put("511", "NetworkAuthenticationRequired");
}
@Override
public void processOpts() {
super.processOpts();
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) {
packageName = (String) additionalProperties.get(CodegenConstants.PACKAGE_NAME);
setApiPackage(packageName + ".apis");
additionalProperties.put(CodegenConstants.API_PACKAGE, apiPackage());
setModelPackage(packageName + ".models");
additionalProperties.put(CodegenConstants.PACKAGE_NAME, modelPackage());
} else {
additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName);
}
if (additionalProperties.containsKey(SOURCE_SUBFOLDER)) {
sourceSubFolder = (String) additionalProperties.get(SOURCE_SUBFOLDER);
}
sourceFolder = "src" + File.separator + sourceSubFolder + File.separator + sourceFolder;
supportingFiles.add(new SupportingFile("types.mustache", modelFileFolderRelative(), "types.scala"));
supportingFiles.add(new SupportingFile("path.mustache", apiFileFolderRelative(), "path.scala"));
supportingFiles.add(new SupportingFile("query.mustache", apiFileFolderRelative(), "query.scala"));
supportingFiles.add(new SupportingFile("apis.mustache", packageFileFolderRelative(), "api.scala"));
apiTemplateFiles.put("api.mustache", ".scala");
if (!additionalProperties.containsKey(EXCLUDE_SBT) && !Boolean.parseBoolean((String) additionalProperties.get(EXCLUDE_SBT))) {
supportingFiles.add(new SupportingFile("build.sbt", "", "build.sbt"));
supportingFiles.add(new SupportingFile("build.properties", "project", "build.properties"));
}
}
@Override
public Map inlineSchemaOption() {
return super.inlineSchemaOption();
}
@Override
public boolean isEnablePostProcessFile() {
return true;
}
@Override
public void postProcessFile(File file, String fileType) {
LOGGER.debug("postprocess " + file.toString());
super.postProcessFile(file, fileType);
}
@Override
public Map postProcessAllModels(Map objs) {
Map modelsMap = super.postProcessAllModels(objs);
for (ModelsMap mm : modelsMap.values()) {
for (ModelMap model : mm.getModels()) {
// model oneOf as sealed trait
CodegenModel cModel = model.getModel();
cModel.getVendorExtensions().put("x-isSealedTrait", !cModel.oneOf.isEmpty());
if (cModel.discriminator != null) {
cModel.getVendorExtensions().put("x-use-discr", true);
if (cModel.discriminator.getMapping() != null) {
cModel.getVendorExtensions().put("x-use-discr-mapping", true);
}
}
//
try {
List exts = (List) cModel.getVendorExtensions().get("x-implements");
if (exts != null) {
cModel.getVendorExtensions().put("x-extends", exts.subList(0, 1));
cModel.getVendorExtensions().put("x-extendsWith", exts.subList(1, exts.size()));
}
} catch (IndexOutOfBoundsException ignored) {
}
// add refined constraints
for (CodegenProperty prop : cModel.vars) {
Set imports = new TreeSet<>();
prop.getVendorExtensions().putAll(refineProp(prop, imports));
cModel.imports.addAll(imports);
}
}
}
return modelsMap;
}
private Map makeRefiined(Set imports, String dataType, ArrayList refined) {
Map vendorExtensions = new HashMap<>();
if (!refined.isEmpty()) {
imports.add("And");
imports.add("Refined");
String refinedRgt = String.join(" And ", refined);
vendorExtensions.put("x-type", "Refined[" + dataType + ", " + refinedRgt + "]");
vendorExtensions.put("x-refined-lft", dataType);
vendorExtensions.put("x-refined-rgt", refinedRgt);
vendorExtensions.put("x-refined", true);
} else {
vendorExtensions.put("x-type", dataType);
}
return vendorExtensions;
}
private Map refineProp(IJsonSchemaValidationProperties prop, Set imports) {
Map vendorExtensions = new HashMap<>();
vendorExtensions.put("x-type", prop.getDataType());
if (prop.getIsString()) {
ArrayList refined = new ArrayList<>();
if (prop.getMinLength() != null) {
refined.add("MinSize[" + prop.getMinLength() + "]");
imports.add("MinSize");
}
if (prop.getMaxLength() != null) {
refined.add("MaxSize[" + prop.getMaxLength() + "]");
imports.add("MaxSize");
}
if (prop.getPattern() != null) {
try {
String fixedPattern = prop.getPattern().substring(1, prop.getPattern().length() - 1);
refined.add("MatchesRegex[\"" + fixedPattern + "\"]");
imports.add("MatchesRegex");
} catch (IndexOutOfBoundsException ignored) {
}
}
vendorExtensions.putAll(makeRefiined(imports, prop.getDataType(), refined));
}
if ("Int".equals(prop.getDataType())
|| "Long".equals(prop.getDataType())
|| "Float".equals(prop.getDataType())
|| "Double".equals(prop.getDataType())
|| "BigDecimal".equals(prop.getDataType())
) {
ArrayList refined = new ArrayList<>();
if (prop.getMinimum() != null) {
if (prop.getExclusiveMinimum()) {
refined.add("Greater[" + prop.getMinimum() + "]");
imports.add("Greater");
} else {
refined.add("GreaterEqual[" + prop.getMinimum() + "]");
imports.add("GreaterEqual");
}
}
if (prop.getMaximum() != null) {
if (prop.getExclusiveMaximum()) {
refined.add("Less[" + prop.getMaximum() + "]");
imports.add("Less");
} else {
refined.add("LessEqual[" + prop.getMaximum() + "]");
imports.add("LessEqual");
}
}
vendorExtensions.putAll(makeRefiined(imports, prop.getDataType(), refined));
}
if (prop.getIsUuid() || "Uuid".equals(prop.getDataType())) {
prop.setDataType("UUID");
}
if (prop.getIsArray() && prop.getItems() != null) {
Map subVendorExtensions = refineProp(prop.getItems(), imports);
prop.getItems().getVendorExtensions().putAll(subVendorExtensions);
ArrayList refined = new ArrayList<>();
if (prop.getMinItems() != null) {
refined.add("MinSize[" + prop.getMinItems() + "]");
imports.add("MinSize");
}
if (prop.getMaxItems() != null) {
refined.add("MaxSize[" + prop.getMaxItems() + "]");
imports.add("MaxSize");
}
vendorExtensions.putAll(makeRefiined(imports, prop.getDataType(), refined));
}
return vendorExtensions;
}
@Override
public Map postProcessSupportingFileData(Map objs) {
Map bundle = super.postProcessSupportingFileData(objs);
List models = (List) bundle.get("models");
TreeSet allImports = new TreeSet<>();
for (ModelMap mm : models) {
for (String nextImport : mm.getModel().imports) {
String mapping = importMapping().get(nextImport);
if (mapping != null && !defaultIncludes().contains(mapping)) {
allImports.add(mapping);
}
// add instantiation types
mapping = instantiationTypes().get(nextImport);
if (mapping != null && !defaultIncludes().contains(mapping)) {
allImports.add(mapping);
}
}
}
bundle.put("imports", allImports);
bundle.put("packageName", packageName);
ApiInfoMap apiInfoMap = (ApiInfoMap) bundle.get("apiInfo");
Map> authToOperationMap = new TreeMap<>();
for (OperationsMap op : apiInfoMap.getApis()) {
List> opsByAuth = (List>) op.get("operationsByAuth");
for (HashMap auth : opsByAuth) {
String autName = (String) auth.get("auth");
String classname = (String) op.get("classname");
List classnames = authToOperationMap.computeIfAbsent(autName, k -> new ArrayList<>());
classnames.add(classname);
}
}
bundle.put("authToOperationMap",
authToOperationMap.entrySet().stream().map(ent -> {
Map tuple = new HashMap<>();
String auth = ent.getKey();
tuple.put("auth", auth);
tuple.put("ops", ent.getValue());
tuple.put("addMiddleware", !"".equals(auth));
return tuple;
}).collect(Collectors.toList())
);
return bundle;
}
@Override
public CodegenType getTag() {
return CodegenType.SERVER;
}
@Override
public String getName() {
return "scala-http4s-server";
}
@Override
public String getHelp() {
return "Generates a Scala http4s server bindings.";
}
@Override
public String escapeReservedWord(String name) {
return "_" + name;
}
@Override
public String apiFileFolder() {
return outputFolder + File.separator + apiFileFolderRelative();
}
private String apiFileFolderRelative() {
return sourceFolder + File.separator + apiPackage().replace('.', File.separatorChar);
}
@Override
public String modelFileFolder() {
return outputFolder + File.separator + modelFileFolderRelative();
}
public String modelFileFolderRelative() {
return sourceFolder + File.separator + modelPackage().replace('.', File.separatorChar);
}
public String packageFileFolderRelative() {
return sourceFolder + File.separator + packageName.replace('.', File.separatorChar);
}
@Override
public OperationsMap postProcessOperationsWithModels(OperationsMap objsI, List allModels) {
OperationsMap objs = super.postProcessOperationsWithModels(objsI, allModels);
OperationMap operations = objs.getOperations();
List operationList = operations.getOperation();
Set allAuth = new HashSet<>();
Map> opsByAuth = new HashMap<>();
for (CodegenOperation op : operationList) {
// Converts GET /foo/bar => GET / foo / bar =>
generateScalaPath(op);
// :? fooQueryParam
generateQueryParameters(op);
// decide wat methods do we need in delegate:
if (op.consumes == null || op.consumes.size() == 0) {
op.vendorExtensions.put("x-generic-body", true);
} else {
if (op.consumes.stream().anyMatch(x -> x.containsKey("isJson"))) {
op.vendorExtensions.put("x-json-body", true);
}
if (op.consumes.stream().anyMatch(x -> !x.containsKey("isJson"))) {
op.vendorExtensions.put("x-generic-body", true);
}
}
// decide wat methods do we need in responses:
for (CodegenResponse resp : op.responses) {
if (resp.code.equals("0"))
resp.code = "200"; // 200 by default
String responseName;
responseName = locationStatusToResponse.get(resp.code);
if (responseName != null) {
resp.vendorExtensions.put("x-response-location", true);
} else {
responseName = wwwAuthStatusToResponse.get(resp.code);
if (responseName != null) {
resp.vendorExtensions.put("x-response-www-auth", true);
} else {
responseName = allowStatusToResponse.get(resp.code);
if (responseName != null) {
resp.vendorExtensions.put("x-response-allow", true);
} else {
responseName = proxyAuthStatusToResponse.get(resp.code);
if (responseName != null) {
resp.vendorExtensions.put("x-response-proxy-auth", true);
} else {
responseName = statusToResponse.get(resp.code);
if (responseName != null) {
resp.vendorExtensions.put("x-response-standard", true);
} else {
throw new IllegalArgumentException("unsupported status " + resp.code);
}
}
}
}
}
resp.vendorExtensions.put("x-response", responseName);
if (resp.getContent() == null) {
resp.vendorExtensions.put("x-generic-response", true); // non json resp
} else {
if (resp.getContent().containsKey("application/json")) {
resp.vendorExtensions.put("x-json-response", true); // json resp
} else {
resp.vendorExtensions.put("x-generic-response", true); // non json resp
}
if (resp.getContent().size() > 1) {
resp.vendorExtensions.put("x-generic-response", true); // non json resp
}
}
}
if (op.authMethods != null) {
for (CodegenSecurity cs : op.authMethods) {
allAuth.add(cs.name);
}
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy