Implement scala http4s server generator (#17430)

* Implement scala http4s server generator

* Fix types and auth

* Add proper handling of various responses

* Fix configs

* Drop null values in json encoder

* Add sample files

---------

Co-authored-by: m.tkachev <m.tkachev@tinkoff.ru>
This commit is contained in:
mikkka 2024-01-10 08:40:31 +05:00 committed by GitHub
parent 11caad92df
commit 455add6d80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 2713 additions and 0 deletions

View File

@ -31,6 +31,7 @@ jobs:
- samples/server/petstore/scala-pekko-http-server
- samples/server/petstore/scalatra
- samples/server/petstore/scala-finch # cannot be tested with jdk11
- samples/server/petstore/scala-http4s-server
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4

View File

@ -0,0 +1,6 @@
generatorName: scala-http4s-server
outputDir: samples/server/petstore/scala-http4s-server
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/scala-http4s-server
additionalProperties:
artifactId: openapi-scala-http4s-server

View File

@ -0,0 +1,852 @@
/*
* 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.ArraySchema;
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", "team@openapitools.org");
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<String, String> 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<String, String> wwwAuthStatusToResponse = new HashMap<>();
static {
wwwAuthStatusToResponse.put("401", "Unauthorized");
}
private final static Map<String, String> allowStatusToResponse = new HashMap<>();
static {
allowStatusToResponse.put("405", "MethodNotAllowed");
}
private final static Map<String, String> proxyAuthStatusToResponse = new HashMap<>();
static {
proxyAuthStatusToResponse.put("407", "ProxyAuthenticationRequired");
}
private final static Map<String, String> 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<String, String> inlineSchemaOption() {
return super.inlineSchemaOption();
}
@Override
public boolean isEnablePostProcessFile() {
return true;
}
@Override
public void postProcessFile(File file, String fileType) {
System.out.println("postprocess " + file.toString());
super.postProcessFile(file, fileType);
}
@Override
public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs) {
Map<String, ModelsMap> 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<String> exts = (List<String>) 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<String> imports = new TreeSet<>();
prop.getVendorExtensions().putAll(refineProp(prop, imports));
cModel.imports.addAll(imports);
}
}
}
return modelsMap;
}
private Map<String, Object> makeRefiined(Set<String> imports, String dataType, ArrayList<String> refined) {
Map<String, Object> 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<String, Object> refineProp(IJsonSchemaValidationProperties prop, Set<String> imports) {
Map<String, Object> vendorExtensions = new HashMap<>();
vendorExtensions.put("x-type", prop.getDataType());
if (prop.getIsString()) {
ArrayList<String> 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<String> 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<String, Object> subVendorExtensions = refineProp(prop.getItems(), imports);
prop.getItems().getVendorExtensions().putAll(subVendorExtensions);
ArrayList<String> 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<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
Map<String, Object> bundle = super.postProcessSupportingFileData(objs);
List<ModelMap> models = (List<ModelMap>) bundle.get("models");
TreeSet<String> 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<String, List<String>> authToOperationMap = new TreeMap<>();
for (OperationsMap op: apiInfoMap.getApis()) {
List<HashMap<String, Object>> opsByAuth = (List<HashMap<String, Object>>) op.get("operationsByAuth");
for (HashMap<String, Object> auth: opsByAuth) {
String autName = (String) auth.get("auth");
String classname = (String) op.get("classname");
List<String> classnames = authToOperationMap.computeIfAbsent(autName, k -> new ArrayList<>());
classnames.add(classname);
}
}
bundle.put("authToOperationMap",
authToOperationMap.entrySet().stream().map(ent -> {
Map<String, Object> 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<ModelMap> allModels) {
OperationsMap objs = super.postProcessOperationsWithModels(objsI, allModels);
OperationMap operations = objs.getOperations();
List<CodegenOperation> operationList = operations.getOperation();
Set<String> allAuth = new HashSet<>();
Map<String, List<String>> 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<Map<String, Object>> authDup = new ArrayList<>();
for (CodegenSecurity authMeth: op.authMethods) {
Map<String, Object> vals = new HashMap<>();
vals.put("authName", authMeth.name);
vals.put("operation", op);
authDup.add(vals);
opsByAuth.computeIfAbsent(authMeth.name, k -> new ArrayList<>()).add(op.operationId);
}
op.vendorExtensions.put("x-authed", authDup);
} else {
opsByAuth.computeIfAbsent("", k -> new ArrayList<>()).add(op.operationId);
}
}
TreeSet<String> allImports = new TreeSet<>();
List<String> currentImports = objs.getImports().stream().flatMap(m -> m.values().stream()).collect(Collectors.toList());
for (CodegenOperation op : operationList) {
for (String nextImport : op.imports) {
String mapping = importMapping().get(nextImport);
if (mapping != null && !defaultIncludes().contains(mapping)) {
if (!currentImports.contains(mapping)) {
allImports.add(mapping);
}
}
// add instantiation types
mapping = instantiationTypes().get(nextImport);
if (mapping != null && !currentImports.contains(mapping)) {
if (!currentImports.contains(mapping)) {
allImports.add(mapping);
}
}
}
}
objs.put("operationsByAuth", opsByAuth.entrySet().stream().map(ent -> {
HashMap<String, Object> tuple = new HashMap<>();
tuple.put("auth", ent.getKey());
tuple.put("ops", ent.getValue());
return tuple;
}
).collect(Collectors.toList()));
objs.put("extraImports", allImports);
objs.put("allAuth", allAuth);
return objs;
}
@SuppressWarnings("Duplicates")
@Override
public String getTypeDeclaration(Schema p) {
if (ModelUtils.isArraySchema(p)) {
ArraySchema ap = (ArraySchema) p;
Schema inner = ap.getItems();
return getSchemaType(p) + "[" + getTypeDeclaration(inner) + "]";
} else if (ModelUtils.isMapSchema(p)) {
Schema inner = ModelUtils.getAdditionalProperties(p);
return getSchemaType(p) + "[String, " + getTypeDeclaration(inner) + "]";
}
return super.getTypeDeclaration(p);
}
@Override
public String getSchemaType(Schema p) {
String schemaType = super.getSchemaType(p);
String type;
if (typeMapping.containsKey(schemaType)) {
type = typeMapping.get(schemaType);
if (languageSpecificPrimitives.contains(type)) {
return toModelName(type);
}
} else {
type = schemaType;
}
return toModelName(type);
}
@Override
public String escapeQuotationMark(String input) {
// remove " to avoid code injection
return input.replace("\"", "");
}
@Override
public String escapeUnsafeCharacters(String input) {
return input.replace("*/", "*_/").replace("/*", "/_*");
}
private void generateScalaPath(CodegenOperation op) {
Set<String> imports = new HashSet<>();
String path = op.path;
// remove first /
if (path.startsWith("/")) {
path = path.substring(1);
}
// remove last /
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
String[] items = path.split("/", -1);
String scalaPath = "";
int pathParamIndex = 0;
for (String item : items) {
if (item.matches("^\\{(.*)}$")) { // wrap in {}
// find the datatype of the parameter
final CodegenParameter cp = op.pathParams.get(pathParamIndex);
// TODO: Handle non-primitives
scalaPath = scalaPath + " / " + cpToPathParameter(cp, imports, cp.vendorExtensions);
pathParamIndex++;
} else {
scalaPath = scalaPath + " / " + "\"" + item + "\"";
}
}
op.vendorExtensions.put("x-codegen-path", scalaPath);
op.imports.addAll(imports);
}
private String cpToPathParameter(CodegenParameter cp, Set<String> imports, Map<String, Object> vendorExtensions) {
// don't support containers and arrays yet, reset to string
if (cp.isContainer || cp.isArray) {
cp.setDataType("String");
cp.setIsArray(false);
cp.setIsString(true);
cp.isContainer = false;
}
Map<String, Object> _vendorExtensions = refineProp(cp, imports);
vendorExtensions.putAll(_vendorExtensions);
if (_vendorExtensions.size() == 1) { // only `x-type`
if ("String".equals(cp.getDataType())) {
return cp.baseName;
} else {
return cp.dataType + "Varr(" + cp.baseName + ")";
}
} else {
return cp.baseName + "Varr(" + cp.baseName + ")";
}
}
private void generateQueryParameters(CodegenOperation op) {
Set<String> imports = new HashSet<>();
String queryString = "";
for (CodegenParameter cp : op.queryParams) {
if (queryString.isEmpty()) {
queryString = queryString + " :? ";
} else {
queryString = queryString + " +& ";
}
queryString = queryString + cpToQueryParameter(cp, imports, cp.vendorExtensions);
}
op.vendorExtensions.put("x-codegen-query", queryString);
op.imports.addAll(imports);
}
private String cpToQueryParameter(CodegenParameter cp, Set<String> imports, Map<String, Object> vendorExtensions) {
// don't support containers and arrays yet, reset to string
if (cp.isContainer && !cp.isArray) {
cp.setDataType("String");
cp.setIsArray(false);
cp.setIsString(true);
cp.isContainer = false;
}
vendorExtensions.putAll(refineProp(cp, imports));
return cp.baseName + "QueryParam(" + cp.baseName + ")";
}
@Override
public void postProcess() {
System.out.println("################################################################################");
System.out.println("# Thanks for using OpenAPI Generator. #");
System.out.println("# Please consider donation to help us maintain this project \uD83D\uDE4F #");
System.out.println("# https://opencollective.com/openapi_generator/donate #");
System.out.println("# #");
System.out.println("# This generator's contributed by Jim Schubert (https://github.com/jimschubert)#");
System.out.println("# Please support his work directly via https://patreon.com/jimschubert \uD83D\uDE4F #");
System.out.println("################################################################################");
}
@Override
public GeneratorLanguage generatorLanguage() { return GeneratorLanguage.SCALA; }
}

View File

@ -118,6 +118,7 @@ org.openapitools.codegen.languages.ScalaPekkoClientCodegen
org.openapitools.codegen.languages.ScalaAkkaHttpServerCodegen
org.openapitools.codegen.languages.ScalaFinchServerCodegen
org.openapitools.codegen.languages.ScalaGatlingCodegen
org.openapitools.codegen.languages.ScalaHttp4sServerCodegen
org.openapitools.codegen.languages.ScalaLagomServerCodegen
org.openapitools.codegen.languages.ScalaPlayFrameworkServerCodegen
org.openapitools.codegen.languages.ScalaSttpClientCodegen

View File

@ -0,0 +1,255 @@
package {{apiPackage}}
import {{apiPackage}}.path._
import {{apiPackage}}.query._
{{#imports}}import {{import}}
{{/imports}}
{{#extraImports}}import {{.}}
{{/extraImports}}
import cats.Monad
import cats.syntax.all._
import org.http4s._
import org.http4s.circe._
import org.http4s.server._
import org.http4s.headers._
import org.http4s.dsl.Http4sDsl
import org.http4s.circe.CirceEntityEncoder._
final case class {{classname}}Routes[
F[_]: JsonDecoder: Monad{{#allAuth}}, {{.}}{{/allAuth}}
](delegate: {{classname}}Delegate[F{{#allAuth}}, {{.}}{{/allAuth}}]) extends Http4sDsl[F] {
{{#operations}}
{{#operation}}
object {{operationId}} {
import {{classname}}Delegate.{{operationId}}Responses
{{#pathParams}}
{{#vendorExtensions.x-refined}}
object {{baseName}}Varr extends RefinedVarr[{{vendorExtensions.x-refined-lft}}, {{{vendorExtensions.x-refined-rgt}}}]
{{/vendorExtensions.x-refined}}
{{/pathParams}}
{{#queryParams}}
{{#isArray}}
{{#required}}
object {{baseName}}QueryParam extends QuerySeqParamDecoderMatcher[{{{items.vendorExtensions.x-type}}}]("{{baseName}}")
{{/required}}
{{^required}}
object {{baseName}}QueryParam extends OptionalQuerySeqParamDecoderMatcher[{{{items.vendorExtensions.x-type}}}]("{{baseName}}")
{{/required}}
{{/isArray}}
{{^isArray}}
{{#required}}
object {{baseName}}QueryParam extends QueryParamDecoderMatcher[{{{vendorExtensions.x-type}}}]("{{baseName}}")
{{/required}}
{{^required}}
object {{baseName}}QueryParam extends OptionalQueryParamDecoderMatcher[{{{vendorExtensions.x-type}}}]("{{baseName}}")
{{/required}}
{{/isArray}}
{{/queryParams}}
{{^vendorExtensions.x-authed}}
val route = HttpRoutes.of[F] {
case req @ {{{httpMethod}}} -> Root{{{vendorExtensions.x-codegen-path}}}{{{vendorExtensions.x-codegen-query}}} =>
{{#vendorExtensions.x-json-body}}
{{#vendorExtensions.x-generic-body}}
req.contentType match {
case Some(`Content-Type`(MediaType.application.json, _)) =>
{{>delegateCallJson}}
case _ =>
{{>delegateCallGeneric}}
}
{{/vendorExtensions.x-generic-body}}
{{^vendorExtensions.x-generic-body}}
{{>delegateCallJson}}
{{/vendorExtensions.x-generic-body}}
{{/vendorExtensions.x-json-body}}
{{^vendorExtensions.x-json-body}}
{{>delegateCallGeneric}}
{{/vendorExtensions.x-json-body}}
}
{{/vendorExtensions.x-authed}}
{{#vendorExtensions.x-authed}}
val route{{authName}} = AuthedRoutes.of[{{authName}}, F] {
case (req @ {{{httpMethod}}} -> Root{{{vendorExtensions.x-codegen-path}}}{{{vendorExtensions.x-codegen-query}}}) as auth =>
{{#vendorExtensions.x-json-body}}
{{#vendorExtensions.x-generic-body}}
req.contentType match {
case Some(`Content-Type`(MediaType.application.json, _)) =>
{{>delegateCallJson}}
case _ =>
{{>delegateCallGeneric}}
}
{{/vendorExtensions.x-generic-body}}
{{^vendorExtensions.x-generic-body}}
{{>delegateCallJson}}
{{/vendorExtensions.x-generic-body}}
{{/vendorExtensions.x-json-body}}
{{^vendorExtensions.x-json-body}}
{{>delegateCallGeneric}}
{{/vendorExtensions.x-json-body}}
}
{{/vendorExtensions.x-authed}}
val responses: {{operationId}}Responses[F] = new {{operationId}}Responses[F] {
{{#responses}}
{{#vendorExtensions.x-response-location}}
{{#vendorExtensions.x-json-response}}
def resp{{code}}(location: Location, value: {{{dataType}}}): F[Response[F]] = {{vendorExtensions.x-response}}(location, value)
{{/vendorExtensions.x-json-response}}
{{#vendorExtensions.x-generic-response}}
def resp{{code}}(location: Location): F[Response[F]] = {{vendorExtensions.x-response}}(location)
{{/vendorExtensions.x-generic-response}}
{{/vendorExtensions.x-response-location}}
{{#vendorExtensions.x-response-www-auth}}
{{#vendorExtensions.x-json-response}}
def resp{{code}}(authenticate: `WWW-Authenticate`, value: {{{dataType}}}): F[Response[F]] = {{vendorExtensions.x-response}}(authenticate, value)
{{/vendorExtensions.x-json-response}}
{{#vendorExtensions.x-generic-response}}
def resp{{code}}(authenticate: `WWW-Authenticate`): F[Response[F]] = {{vendorExtensions.x-response}}(authenticate)
{{/vendorExtensions.x-generic-response}}
{{/vendorExtensions.x-response-www-auth}}
{{#vendorExtensions.x-response-allow}}
{{#vendorExtensions.x-json-response}}
def resp{{code}}(allow: Allow, value: {{{dataType}}}): F[Response[F]] = {{vendorExtensions.x-response}}(allow, value)
{{/vendorExtensions.x-json-response}}
{{#vendorExtensions.x-generic-response}}
def resp{{code}}(allow: Allow): F[Response[F]] = {{vendorExtensions.x-response}}(allow)
{{/vendorExtensions.x-generic-response}}
{{/vendorExtensions.x-response-allow}}
{{#vendorExtensions.x-response-proxy-auth}}
{{#vendorExtensions.x-json-response}}
def resp{{code}}(authenticate: `Proxy-Authenticate`, value: {{{dataType}}}): F[Response[F]] = {{vendorExtensions.x-response}}(value, authenticate)
{{/vendorExtensions.x-json-response}}
{{#vendorExtensions.x-generic-response}}
def resp{{code}}(authenticate: `Proxy-Authenticate`): F[Response[F]] = {{vendorExtensions.x-response}}(authenticate)
{{/vendorExtensions.x-generic-response}}
{{/vendorExtensions.x-response-proxy-auth}}
{{#vendorExtensions.x-response-standard}}
{{#vendorExtensions.x-json-response}}
def resp{{code}}(value: {{{dataType}}}): F[Response[F]] = {{vendorExtensions.x-response}}(value)
{{/vendorExtensions.x-json-response}}
{{#vendorExtensions.x-generic-response}}
def resp{{code}}(): F[Response[F]] = {{vendorExtensions.x-response}}()
{{/vendorExtensions.x-generic-response}}
{{/vendorExtensions.x-response-standard}}
{{/responses}}
}
}
{{/operation}}
{{/operations}}
{{#operationsByAuth}}
val routes{{auth}} =
{{#ops}}
{{.}}.route{{auth}}{{^-last}} <+>{{/-last}}
{{/ops}}
{{/operationsByAuth}}
}
object {{classname}}Delegate {
{{#operations}}
{{#operation}}
trait {{operationId}}Responses[F[_]] {
{{#responses}}
{{#vendorExtensions.x-response-location}}
{{#vendorExtensions.x-json-response}}
def resp{{code}}(location: Location, value: {{{dataType}}}): F[Response[F]]
{{/vendorExtensions.x-json-response}}
{{#vendorExtensions.x-generic-response}}
def resp{{code}}(location: Location): F[Response[F]]
{{/vendorExtensions.x-generic-response}}
{{/vendorExtensions.x-response-location}}
{{#vendorExtensions.x-response-www-auth}}
{{#vendorExtensions.x-json-response}}
def resp{{code}}(authenticate: `WWW-Authenticate`, value: {{{dataType}}}): F[Response[F]]
{{/vendorExtensions.x-json-response}}
{{#vendorExtensions.x-generic-response}}
def resp{{code}}(authenticate: `WWW-Authenticate`): F[Response[F]]
{{/vendorExtensions.x-generic-response}}
{{/vendorExtensions.x-response-www-auth}}
{{#vendorExtensions.x-response-allow}}
{{#vendorExtensions.x-json-response}}
def resp{{code}}(allow: Allow, value: {{{dataType}}}): F[Response[F]]
{{/vendorExtensions.x-json-response}}
{{#vendorExtensions.x-generic-response}}
def resp{{code}}(allow: Allow): F[Response[F]]
{{/vendorExtensions.x-generic-response}}
{{/vendorExtensions.x-response-allow}}
{{#vendorExtensions.x-response-proxy-auth}}
{{#vendorExtensions.x-json-response}}
def resp{{code}}(authenticate: `Proxy-Authenticate`, value: {{{dataType}}}): F[Response[F]]
{{/vendorExtensions.x-json-response}}
{{#vendorExtensions.x-generic-response}}
def resp{{code}}(authenticate: `Proxy-Authenticate`): F[Response[F]]
{{/vendorExtensions.x-generic-response}}
{{/vendorExtensions.x-response-proxy-auth}}
{{#vendorExtensions.x-response-standard}}
{{#vendorExtensions.x-json-response}}
def resp{{code}}(value: {{{dataType}}}): F[Response[F]]
{{/vendorExtensions.x-json-response}}
{{#vendorExtensions.x-generic-response}}
def resp{{code}}(): F[Response[F]]
{{/vendorExtensions.x-generic-response}}
{{/vendorExtensions.x-response-standard}}
{{/responses}}
}
{{/operation}}
{{/operations}}
}
trait {{classname}}Delegate[F[_]{{#allAuth}}, {{.}}{{/allAuth}}] {
{{#operations}}
{{#operation}}
trait {{operationId}} {
import {{classname}}Delegate.{{operationId}}Responses
{{#vendorExtensions.x-json-body}}
{{^vendorExtensions.x-authed}}
def handle(
req: Request[F],
{{operationId}}: F[{{{bodyParam.dataType}}}],
{{> delegateArgs}} responses: {{operationId}}Responses[F]
): F[Response[F]]
{{/vendorExtensions.x-authed}}
{{#vendorExtensions.x-authed}}
def handle_{{authName}}(
auth: {{authName}},
req: Request[F],
{{operationId}}: F[{{{bodyParam.dataType}}}],
{{> delegateArgs}} responses: {{operationId}}Responses[F]
): F[Response[F]]
{{/vendorExtensions.x-authed}}
{{/vendorExtensions.x-json-body}}
{{#vendorExtensions.x-generic-body}}
{{^vendorExtensions.x-authed}}
def handle(
req: Request[F],
{{> delegateArgs}} responses: {{operationId}}Responses[F]
): F[Response[F]]
{{/vendorExtensions.x-authed}}
{{#vendorExtensions.x-authed}}
def handle_{{authName}}(
auth: {{authName}},
req: Request[F],
{{> delegateArgs}} responses: {{operationId}}Responses[F]
): F[Response[F]]
{{/vendorExtensions.x-authed}}
{{/vendorExtensions.x-generic-body}}
}
def {{operationId}}: {{operationId}}
{{/operation}}
{{/operations}}
}

View File

@ -0,0 +1,53 @@
package {{packageName}}
import org.http4s.circe._
import cats.Monad
import cats.syntax.all._
import cats.data.OptionT
import cats.data.Kleisli
import org.http4s._
import org.http4s.server._
import {{apiPackage}}._
final case class API [
F[_]: JsonDecoder: Monad{{#authMethods}}, {{name}}{{/authMethods}}
](
{{#authMethods}}
{{#lambda.camelcase}}{{name}}{{/lambda.camelcase}}: Kleisli[OptionT[F, *], Request[F], {{name}}],
{{/authMethods}}
)(
{{#apiInfo}}
{{#apis}}
{{#operations}}
delegate{{classname}}: {{classname}}Delegate[F{{#allAuth}}, {{.}}{{/allAuth}}],
{{/operations}}
{{/apis}}
{{/apiInfo}}
){
{{#authToOperationMap}}
{{#addMiddleware}}
val {{#lambda.camelcase}}{{auth}}{{/lambda.camelcase}}Middleware = AuthMiddleware{{^-last}}.withFallThrough{{/-last}}({{#lambda.camelcase}}{{auth}}{{/lambda.camelcase}})
{{/addMiddleware}}
{{/authToOperationMap}}
{{#apiInfo}}
{{#apis}}
{{#operations}}
val {{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}Routes = new {{classname}}Routes(delegate{{classname}})
{{/operations}}
{{/apis}}
{{/apiInfo}}
{{#authToOperationMap}}
val routes{{auth}} = {{#addMiddleware}}{{#lambda.camelcase}}{{auth}}{{/lambda.camelcase}}Middleware({{/addMiddleware}}
{{#ops}}
{{#lambda.camelcase}}{{.}}{{/lambda.camelcase}}Routes.routes{{auth}}{{^-last}} <+>{{/-last}}
{{/ops}}{{#addMiddleware}}){{/addMiddleware}}
{{/authToOperationMap}}
val routesAll =
{{#authToOperationMap}}
routes{{auth}}{{^-last}} <+>{{/-last}}
{{/authToOperationMap}}
}

View File

@ -0,0 +1 @@
sbt.version=1.8.2

View File

@ -0,0 +1,27 @@
scalaVersion := "2.13.11"
scalacOptions += "-Ymacro-annotations"
val circeVersion = "0.14.5"
def circe(artifact: String): ModuleID = "io.circe" %% s"circe-$artifact" % circeVersion
val http4sVersion = "0.23.23"
def http4s(artifact: String): ModuleID = "org.http4s" %% s"http4s-$artifact" % http4sVersion
val refinedVersion = "0.9.29"
val refined = Seq(
"eu.timepit" %% "refined" % refinedVersion,
"eu.timepit" %% "refined-cats" % refinedVersion
)
val catsVersion = "2.10.0"
val cats = Seq("org.typelevel" %% "cats-core" % catsVersion)
lazy val compilerPlugins = Seq(
compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1"),
compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.2" cross CrossVersion.full)
)
libraryDependencies ++= (Seq(
http4s("core"), http4s("ember-server"), http4s("circe"), http4s("dsl"),
circe("core"), circe("generic"), circe("parser"), circe("refined")
) ++ refined ++ cats ++ compilerPlugins)

View File

@ -0,0 +1,21 @@
{{#pathParams}}
{{baseName}}: {{{vendorExtensions.x-type}}},
{{/pathParams}}
{{#queryParams}}
{{#isArray}}
{{#required}}
{{baseName}}: List[{{{items.vendorExtensions.x-type}}}],
{{/required}}
{{^required}}
{{baseName}}: Option[List[{{{items.vendorExtensions.x-type}}}]],
{{/required}}
{{/isArray}}
{{^isArray}}
{{#required}}
{{baseName}}: {{{vendorExtensions.x-type}}},
{{/required}}
{{^required}}
{{baseName}}: Option[{{{vendorExtensions.x-type}}}],
{{/required}}
{{/isArray}}
{{/queryParams}}

View File

@ -0,0 +1,6 @@
{{^authName}}
delegate.{{operationId}}.handle(req, {{#pathParams}}{{baseName}}, {{/pathParams}}{{#queryParams}}{{baseName}}, {{/queryParams}}responses)
{{/authName}}
{{#authName}}
delegate.{{operationId}}.handle_{{authName}}(auth, req, {{#pathParams}}{{baseName}}, {{/pathParams}}{{#queryParams}}{{baseName}}, {{/queryParams}}responses)
{{/authName}}

View File

@ -0,0 +1,6 @@
{{^authName}}
delegate.{{operationId}}.handle(req, req.asJsonDecode[{{{bodyParam.dataType}}}] , {{#pathParams}}{{baseName}}, {{/pathParams}}{{#queryParams}}{{baseName}}, {{/queryParams}}responses)
{{/authName}}
{{#authName}}
delegate.{{operationId}}.handle_{{authName}}(auth, req, req.asJsonDecode[{{{bodyParam.dataType}}}] , {{#pathParams}}{{baseName}}, {{/pathParams}}{{#queryParams}}{{baseName}}, {{/queryParams}}responses)
{{/authName}}

View File

@ -0,0 +1,62 @@
package {{apiPackage}}
import cats.syntax.all._
import cats.data.ValidatedNel
import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.api.Validate
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZonedDateTime
import java.util.UUID
import scala.util.Try
object path {
trait Varrr[T] {
def unapply(str: String): Option[T]
}
implicit val LocalDateVarr: Varrr[LocalDate] = new Varrr[LocalDate] {
def unapply(str: String): Option[LocalDate] = Try(LocalDate.parse(str)).toOption
}
implicit val LocalDateTimeVarr: Varrr[LocalDateTime] = new Varrr[LocalDateTime] {
def unapply(str: String): Option[LocalDateTime] = Try(LocalDateTime.parse(str)).toOption
}
implicit val ZonedDateTimeVarr: Varrr[ZonedDateTime] = new Varrr[ZonedDateTime] {
def unapply(str: String): Option[ZonedDateTime] = Try(ZonedDateTime.parse(str)).toOption
}
implicit val UUIDVarr: Varrr[UUID] = new Varrr[UUID] {
def unapply(str: String): Option[UUID] = Try(java.util.UUID.fromString(str)).toOption
}
implicit val IntVarr: Varrr[Int] = new Varrr[Int] {
def unapply(str: String): Option[Int] = Try(str.toInt).toOption
}
implicit val LongVarr: Varrr[Long] = new Varrr[Long] {
def unapply(str: String): Option[Long] = Try(str.toLong).toOption
}
implicit val DoubleVarr: Varrr[Double] = new Varrr[Double] {
def unapply(str: String): Option[Double] = Try(str.toDouble).toOption
}
implicit val BigDecimalVarr: Varrr[BigDecimal] = new Varrr[BigDecimal] {
def unapply(str: String): Option[BigDecimal] = Try(BigDecimal(str)).toOption
}
implicit val StringVarr: Varrr[String] = new Varrr[String] {
def unapply(str: String): Option[String] = str.some
}
abstract class RefinedVarr[T, P](implicit varrr: Varrr[T], validate: Validate[T, P]) extends Varrr[Refined[T, P]] {
def unapply(str: String): Option[Refined[T, P]] =
varrr.unapply(str).flatMap(x => refineV(x).toOption)
}
}

View File

@ -0,0 +1,59 @@
package {{apiPackage}}
import cats.data.NonEmptyList
import cats.data.ValidatedNel
import cats.syntax.all._
import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.api.Validate
import org.http4s.ParseFailure
import org.http4s.QueryParamDecoder
import org.http4s.QueryParameterValue
import java.time._
import java.util.UUID
object query {
implicit def rrrefinedQueryParamDecoder[T, P](
implicit tDecoder: QueryParamDecoder[T], validate: Validate[T, P]
): QueryParamDecoder[Refined[T, P]] = new QueryParamDecoder[Refined[T, P]] {
def decode(value: QueryParameterValue): ValidatedNel[ParseFailure,Refined[T, P]] =
tDecoder.decode(value).withEither(t => t.flatMap(x =>
refineV(x).leftMap(err => NonEmptyList.one(ParseFailure(err, err)))
))
}
abstract class QuerySeqParamDecoderMatcher[T: QueryParamDecoder](name: String) {
def unapply(params: Map[String, Seq[String]]): Option[List[T]] =
params
.get(name)
.flatMap(values =>
values.toList.traverse(s => QueryParamDecoder[T].decode(QueryParameterValue(s)).toOption))
}
abstract class OptionalQuerySeqParamDecoderMatcher[T: QueryParamDecoder](name: String) {
def unapply(params: Map[String, List[String]]): Option[Option[List[T]]] =
params
.get(name)
.flatMap(values =>
values.toList.traverse(s => QueryParamDecoder[T].decode(QueryParameterValue(s)).toOption))
.fold(List.empty[T].some.some)(_.some.some)
}
implicit lazy val BigDecimalQueryParamDecoder: QueryParamDecoder[BigDecimal] =
QueryParamDecoder.fromUnsafeCast[BigDecimal](x => BigDecimal(x.value))("BigDecimal")
implicit lazy val LocalDateTimeQueryParamDecoder: QueryParamDecoder[LocalDateTime] =
QueryParamDecoder.fromUnsafeCast[LocalDateTime](x => LocalDateTime.parse(x.value))("LocalDateTime")
implicit lazy val LocalDateQueryParamDecoder: QueryParamDecoder[LocalDate] =
QueryParamDecoder.fromUnsafeCast[LocalDate](x => LocalDate.parse(x.value))("LocalDateTime")
implicit lazy val ZonedDateTimeQueryParamDecoder: QueryParamDecoder[ZonedDateTime] =
QueryParamDecoder.fromUnsafeCast[ZonedDateTime](x => ZonedDateTime.parse(x.value))("ZonedDateTime")
implicit lazy val UUIDQueryParamDecoder: QueryParamDecoder[UUID] =
QueryParamDecoder.fromUnsafeCast[UUID](x => UUID.fromString(x.value))("UUID")
}

View File

@ -0,0 +1,98 @@
package {{modelPackage}}
import java.time._
import io.circe.refined._
import io.circe.syntax._
import io.circe.{ Decoder, Encoder }
import io.circe.generic.semiauto.{ deriveDecoder, deriveEncoder }
{{#imports}}
import {{.}}
{{/imports}}
{{#models}}
{{#model}}
/**
* {{{description}}}
{{#vars}}
* @param {{name}} {{{description}}}
{{/vars}}
*/
{{#vendorExtensions.x-isSealedTrait}}
sealed trait {{classname}}
object {{classname}} {
import io.circe.{ Decoder, Encoder }
import io.circe.syntax._
import cats.syntax.functor._
{{^vendorExtensions.x-use-discr}}
// no discriminator
implicit val {{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}Encoder: Encoder[{{classname}}] = Encoder.instance {
{{#oneOf}}
case {{#lambda.camelcase}}{{.}}{{/lambda.camelcase}}: {{.}} => {{#lambda.camelcase}}{{.}}{{/lambda.camelcase}}.asJson
{{/oneOf}}
}
implicit val {{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}Decoder: Decoder[{{classname}}] =
List[Decoder[{{classname}}]](
{{#oneOf}}
Decoder[{{.}}].widen,
{{/oneOf}}
).reduceLeft(_ or _)
{{/vendorExtensions.x-use-discr}}
{{#vendorExtensions.x-use-discr}}
{{^vendorExtensions.x-use-discr-mapping}}
// no discriminator mapping
implicit val {{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}Encoder: Encoder[{{classname}}] = Encoder.instance {
{{#oneOf}}
case {{#lambda.camelcase}}{{.}}{{/lambda.camelcase}}: {{.}} => {{#lambda.camelcase}}{{.}}{{/lambda.camelcase}}.asJson.mapObject(("{{discriminator.propertyName}}" -> "{{.}}".asJson) +: _)
{{/oneOf}}
}
implicit val {{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}Decoder: Decoder[{{classname}}] = Decoder.instance { cursor =>
cursor.downField("{{discriminator.propertyName}}").as[String].flatMap {
{{#oneOf}}
case "{{.}}" =>
cursor.as[{{.}}]
{{/oneOf}}
}
}
{{/vendorExtensions.x-use-discr-mapping}}
{{#vendorExtensions.x-use-discr-mapping}}
// use discriminator mapping
{{#discriminator}}
implicit val {{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}Encoder: Encoder[{{classname}}] = Encoder.instance {
{{#mappedModels}}
case {{#lambda.camelcase}}{{model.classname}}{{/lambda.camelcase}}: {{model.classname}} => {{#lambda.camelcase}}{{model.classname}}{{/lambda.camelcase}}.asJson.mapObject(("{{propertyName}}" -> "{{mappingName}}".asJson) +: _)
{{/mappedModels}}
}
implicit val {{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}Decoder: Decoder[{{classname}}] = Decoder.instance { cursor =>
cursor.downField("{{propertyName}}").as[String].flatMap {
{{#mappedModels}}
case "{{mappingName}}" =>
cursor.as[{{model.classname}}]
{{/mappedModels}}
}
}
{{/discriminator}}
{{/vendorExtensions.x-use-discr-mapping}}
{{/vendorExtensions.x-use-discr}}
}
{{/vendorExtensions.x-isSealedTrait}}
{{^vendorExtensions.x-isSealedTrait}}
case class {{classname}}(
{{#vars}}
{{name}}: {{^required}}Option[{{{vendorExtensions.x-type}}}]{{/required}}{{#required}}{{{vendorExtensions.x-type}}}{{/required}}{{^-last}},{{/-last}}
{{/vars}}
){{#vendorExtensions.x-extends}} extends {{.}}{{/vendorExtensions.x-extends}}{{#vendorExtensions.x-extendsWith}} with {{.}}{{/vendorExtensions.x-extendsWith}}
object {{classname}} {
implicit val encoder{{classname}}: Encoder[{{classname}}] = deriveEncoder[{{classname}}].mapJson(_.dropNullValues)
implicit val decoder{{classname}}: Decoder[{{classname}}] = deriveDecoder[{{classname}}]
}
{{/vendorExtensions.x-isSealedTrait}}
{{/model}}
{{/models}}

View File

@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md

View File

@ -0,0 +1,9 @@
build.sbt
project/build.properties
src/main/scala/org/openapitools/api.scala
src/main/scala/org/openapitools/apis/PetApi.scala
src/main/scala/org/openapitools/apis/StoreApi.scala
src/main/scala/org/openapitools/apis/UserApi.scala
src/main/scala/org/openapitools/apis/path.scala
src/main/scala/org/openapitools/apis/query.scala
src/main/scala/org/openapitools/models/types.scala

View File

@ -0,0 +1,27 @@
scalaVersion := "2.13.11"
scalacOptions += "-Ymacro-annotations"
val circeVersion = "0.14.5"
def circe(artifact: String): ModuleID = "io.circe" %% s"circe-$artifact" % circeVersion
val http4sVersion = "0.23.23"
def http4s(artifact: String): ModuleID = "org.http4s" %% s"http4s-$artifact" % http4sVersion
val refinedVersion = "0.9.29"
val refined = Seq(
"eu.timepit" %% "refined" % refinedVersion,
"eu.timepit" %% "refined-cats" % refinedVersion
)
val catsVersion = "2.10.0"
val cats = Seq("org.typelevel" %% "cats-core" % catsVersion)
lazy val compilerPlugins = Seq(
compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1"),
compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.2" cross CrossVersion.full)
)
libraryDependencies ++= (Seq(
http4s("core"), http4s("ember-server"), http4s("circe"), http4s("dsl"),
circe("core"), circe("generic"), circe("parser"), circe("refined")
) ++ refined ++ cats ++ compilerPlugins)

View File

@ -0,0 +1,34 @@
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.openapitools</groupId>
<artifactId>scala-http4s-server</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<name>Scala http4s server</name>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.5.0</version>
<executions>
<execution>
<id>sbt-test</id>
<phase>integration-test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>sbt</executable>
<arguments>
<argument>-ivy</argument>
<argument>${user.home}/.ivy2</argument>
<argument>test</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1 @@
sbt.version=1.8.2

View File

@ -0,0 +1,47 @@
package org.openapitools
import org.http4s.circe._
import cats.Monad
import cats.syntax.all._
import cats.data.OptionT
import cats.data.Kleisli
import org.http4s._
import org.http4s.server._
import org.openapitools.apis._
final case class API [
F[_]: JsonDecoder: Monad, petstore_auth, api_key
](
petstoreAuth: Kleisli[OptionT[F, *], Request[F], petstore_auth],
apiKey: Kleisli[OptionT[F, *], Request[F], api_key],
)(
delegatePetApi: PetApiDelegate[F, petstore_auth, api_key],
delegateStoreApi: StoreApiDelegate[F, api_key],
delegateUserApi: UserApiDelegate[F, api_key],
){
val apiKeyMiddleware = AuthMiddleware.withFallThrough(apiKey)
val petstoreAuthMiddleware = AuthMiddleware(petstoreAuth)
val petApiRoutes = new PetApiRoutes(delegatePetApi)
val storeApiRoutes = new StoreApiRoutes(delegateStoreApi)
val userApiRoutes = new UserApiRoutes(delegateUserApi)
val routes =
storeApiRoutes.routes <+>
userApiRoutes.routes
val routesapi_key = apiKeyMiddleware(
petApiRoutes.routesapi_key <+>
storeApiRoutes.routesapi_key <+>
userApiRoutes.routesapi_key
)
val routespetstore_auth = petstoreAuthMiddleware(
petApiRoutes.routespetstore_auth
)
val routesAll =
routes <+>
routesapi_key <+>
routespetstore_auth
}

View File

@ -0,0 +1,361 @@
package org.openapitools.apis
import org.openapitools.apis.path._
import org.openapitools.apis.query._
import org.openapitools.models.ApiResponse
import java.io.File
import org.openapitools.models.Pet
import cats.Monad
import cats.syntax.all._
import org.http4s._
import org.http4s.circe._
import org.http4s.server._
import org.http4s.headers._
import org.http4s.dsl.Http4sDsl
import org.http4s.circe.CirceEntityEncoder._
final case class PetApiRoutes[
F[_]: JsonDecoder: Monad, petstore_auth, api_key
](delegate: PetApiDelegate[F, petstore_auth, api_key]) extends Http4sDsl[F] {
object addPet {
import PetApiDelegate.addPetResponses
val routepetstore_auth = AuthedRoutes.of[petstore_auth, F] {
case (req @ POST -> Root / "pet") as auth =>
req.contentType match {
case Some(`Content-Type`(MediaType.application.json, _)) =>
delegate.addPet.handle_petstore_auth(auth, req, req.asJsonDecode[Pet] , responses)
case _ =>
delegate.addPet.handle_petstore_auth(auth, req, responses)
}
}
val responses: addPetResponses[F] = new addPetResponses[F] {
def resp200(value: Pet): F[Response[F]] = Ok(value)
def resp200(): F[Response[F]] = Ok()
def resp405(allow: Allow): F[Response[F]] = MethodNotAllowed(allow)
}
}
object deletePet {
import PetApiDelegate.deletePetResponses
val routepetstore_auth = AuthedRoutes.of[petstore_auth, F] {
case (req @ DELETE -> Root / "pet" / LongVarr(petId)) as auth =>
delegate.deletePet.handle_petstore_auth(auth, req, petId, responses)
}
val responses: deletePetResponses[F] = new deletePetResponses[F] {
def resp400(): F[Response[F]] = BadRequest()
}
}
object findPetsByStatus {
import PetApiDelegate.findPetsByStatusResponses
object statusQueryParam extends QuerySeqParamDecoderMatcher[String]("status")
val routepetstore_auth = AuthedRoutes.of[petstore_auth, F] {
case (req @ GET -> Root / "pet" / "findByStatus" :? statusQueryParam(status)) as auth =>
delegate.findPetsByStatus.handle_petstore_auth(auth, req, status, responses)
}
val responses: findPetsByStatusResponses[F] = new findPetsByStatusResponses[F] {
def resp200(value: List[Pet]): F[Response[F]] = Ok(value)
def resp200(): F[Response[F]] = Ok()
def resp400(): F[Response[F]] = BadRequest()
}
}
object findPetsByTags {
import PetApiDelegate.findPetsByTagsResponses
object tagsQueryParam extends QuerySeqParamDecoderMatcher[String]("tags")
val routepetstore_auth = AuthedRoutes.of[petstore_auth, F] {
case (req @ GET -> Root / "pet" / "findByTags" :? tagsQueryParam(tags)) as auth =>
delegate.findPetsByTags.handle_petstore_auth(auth, req, tags, responses)
}
val responses: findPetsByTagsResponses[F] = new findPetsByTagsResponses[F] {
def resp200(value: List[Pet]): F[Response[F]] = Ok(value)
def resp200(): F[Response[F]] = Ok()
def resp400(): F[Response[F]] = BadRequest()
}
}
object getPetById {
import PetApiDelegate.getPetByIdResponses
val routeapi_key = AuthedRoutes.of[api_key, F] {
case (req @ GET -> Root / "pet" / LongVarr(petId)) as auth =>
delegate.getPetById.handle_api_key(auth, req, petId, responses)
}
val responses: getPetByIdResponses[F] = new getPetByIdResponses[F] {
def resp200(value: Pet): F[Response[F]] = Ok(value)
def resp200(): F[Response[F]] = Ok()
def resp400(): F[Response[F]] = BadRequest()
def resp404(): F[Response[F]] = NotFound()
}
}
object updatePet {
import PetApiDelegate.updatePetResponses
val routepetstore_auth = AuthedRoutes.of[petstore_auth, F] {
case (req @ PUT -> Root / "pet") as auth =>
req.contentType match {
case Some(`Content-Type`(MediaType.application.json, _)) =>
delegate.updatePet.handle_petstore_auth(auth, req, req.asJsonDecode[Pet] , responses)
case _ =>
delegate.updatePet.handle_petstore_auth(auth, req, responses)
}
}
val responses: updatePetResponses[F] = new updatePetResponses[F] {
def resp200(value: Pet): F[Response[F]] = Ok(value)
def resp200(): F[Response[F]] = Ok()
def resp400(): F[Response[F]] = BadRequest()
def resp404(): F[Response[F]] = NotFound()
def resp405(allow: Allow): F[Response[F]] = MethodNotAllowed(allow)
}
}
object updatePetWithForm {
import PetApiDelegate.updatePetWithFormResponses
val routepetstore_auth = AuthedRoutes.of[petstore_auth, F] {
case (req @ POST -> Root / "pet" / LongVarr(petId)) as auth =>
delegate.updatePetWithForm.handle_petstore_auth(auth, req, petId, responses)
}
val responses: updatePetWithFormResponses[F] = new updatePetWithFormResponses[F] {
def resp405(allow: Allow): F[Response[F]] = MethodNotAllowed(allow)
}
}
object uploadFile {
import PetApiDelegate.uploadFileResponses
val routepetstore_auth = AuthedRoutes.of[petstore_auth, F] {
case (req @ POST -> Root / "pet" / LongVarr(petId) / "uploadImage") as auth =>
delegate.uploadFile.handle_petstore_auth(auth, req, petId, responses)
}
val responses: uploadFileResponses[F] = new uploadFileResponses[F] {
def resp200(value: ApiResponse): F[Response[F]] = Ok(value)
}
}
val routespetstore_auth =
addPet.routepetstore_auth <+>
deletePet.routepetstore_auth <+>
findPetsByStatus.routepetstore_auth <+>
findPetsByTags.routepetstore_auth <+>
updatePet.routepetstore_auth <+>
updatePetWithForm.routepetstore_auth <+>
uploadFile.routepetstore_auth
val routesapi_key =
getPetById.routeapi_key
}
object PetApiDelegate {
trait addPetResponses[F[_]] {
def resp200(value: Pet): F[Response[F]]
def resp200(): F[Response[F]]
def resp405(allow: Allow): F[Response[F]]
}
trait deletePetResponses[F[_]] {
def resp400(): F[Response[F]]
}
trait findPetsByStatusResponses[F[_]] {
def resp200(value: List[Pet]): F[Response[F]]
def resp200(): F[Response[F]]
def resp400(): F[Response[F]]
}
trait findPetsByTagsResponses[F[_]] {
def resp200(value: List[Pet]): F[Response[F]]
def resp200(): F[Response[F]]
def resp400(): F[Response[F]]
}
trait getPetByIdResponses[F[_]] {
def resp200(value: Pet): F[Response[F]]
def resp200(): F[Response[F]]
def resp400(): F[Response[F]]
def resp404(): F[Response[F]]
}
trait updatePetResponses[F[_]] {
def resp200(value: Pet): F[Response[F]]
def resp200(): F[Response[F]]
def resp400(): F[Response[F]]
def resp404(): F[Response[F]]
def resp405(allow: Allow): F[Response[F]]
}
trait updatePetWithFormResponses[F[_]] {
def resp405(allow: Allow): F[Response[F]]
}
trait uploadFileResponses[F[_]] {
def resp200(value: ApiResponse): F[Response[F]]
}
}
trait PetApiDelegate[F[_], petstore_auth, api_key] {
trait addPet {
import PetApiDelegate.addPetResponses
def handle_petstore_auth(
auth: petstore_auth,
req: Request[F],
addPet: F[Pet],
responses: addPetResponses[F]
): F[Response[F]]
def handle_petstore_auth(
auth: petstore_auth,
req: Request[F],
responses: addPetResponses[F]
): F[Response[F]]
}
def addPet: addPet
trait deletePet {
import PetApiDelegate.deletePetResponses
def handle_petstore_auth(
auth: petstore_auth,
req: Request[F],
petId: Long,
responses: deletePetResponses[F]
): F[Response[F]]
}
def deletePet: deletePet
trait findPetsByStatus {
import PetApiDelegate.findPetsByStatusResponses
def handle_petstore_auth(
auth: petstore_auth,
req: Request[F],
status: List[String],
responses: findPetsByStatusResponses[F]
): F[Response[F]]
}
def findPetsByStatus: findPetsByStatus
trait findPetsByTags {
import PetApiDelegate.findPetsByTagsResponses
def handle_petstore_auth(
auth: petstore_auth,
req: Request[F],
tags: List[String],
responses: findPetsByTagsResponses[F]
): F[Response[F]]
}
def findPetsByTags: findPetsByTags
trait getPetById {
import PetApiDelegate.getPetByIdResponses
def handle_api_key(
auth: api_key,
req: Request[F],
petId: Long,
responses: getPetByIdResponses[F]
): F[Response[F]]
}
def getPetById: getPetById
trait updatePet {
import PetApiDelegate.updatePetResponses
def handle_petstore_auth(
auth: petstore_auth,
req: Request[F],
updatePet: F[Pet],
responses: updatePetResponses[F]
): F[Response[F]]
def handle_petstore_auth(
auth: petstore_auth,
req: Request[F],
responses: updatePetResponses[F]
): F[Response[F]]
}
def updatePet: updatePet
trait updatePetWithForm {
import PetApiDelegate.updatePetWithFormResponses
def handle_petstore_auth(
auth: petstore_auth,
req: Request[F],
petId: Long,
responses: updatePetWithFormResponses[F]
): F[Response[F]]
}
def updatePetWithForm: updatePetWithForm
trait uploadFile {
import PetApiDelegate.uploadFileResponses
def handle_petstore_auth(
auth: petstore_auth,
req: Request[F],
petId: Long,
responses: uploadFileResponses[F]
): F[Response[F]]
}
def uploadFile: uploadFile
}

View File

@ -0,0 +1,181 @@
package org.openapitools.apis
import org.openapitools.apis.path._
import org.openapitools.apis.query._
import org.openapitools.models.Order
import eu.timepit.refined.api.Refined
import eu.timepit.refined.boolean.And
import eu.timepit.refined.numeric.GreaterEqual
import eu.timepit.refined.numeric.LessEqual
import cats.Monad
import cats.syntax.all._
import org.http4s._
import org.http4s.circe._
import org.http4s.server._
import org.http4s.headers._
import org.http4s.dsl.Http4sDsl
import org.http4s.circe.CirceEntityEncoder._
final case class StoreApiRoutes[
F[_]: JsonDecoder: Monad, api_key
](delegate: StoreApiDelegate[F, api_key]) extends Http4sDsl[F] {
object deleteOrder {
import StoreApiDelegate.deleteOrderResponses
val route = HttpRoutes.of[F] {
case req @ DELETE -> Root / "store" / "order" / orderId =>
delegate.deleteOrder.handle(req, orderId, responses)
}
val responses: deleteOrderResponses[F] = new deleteOrderResponses[F] {
def resp400(): F[Response[F]] = BadRequest()
def resp404(): F[Response[F]] = NotFound()
}
}
object getInventory {
import StoreApiDelegate.getInventoryResponses
val routeapi_key = AuthedRoutes.of[api_key, F] {
case (req @ GET -> Root / "store" / "inventory") as auth =>
delegate.getInventory.handle_api_key(auth, req, responses)
}
val responses: getInventoryResponses[F] = new getInventoryResponses[F] {
def resp200(value: Map[String, Int]): F[Response[F]] = Ok(value)
}
}
object getOrderById {
import StoreApiDelegate.getOrderByIdResponses
object orderIdVarr extends RefinedVarr[Long, GreaterEqual[1] And LessEqual[5]]
val route = HttpRoutes.of[F] {
case req @ GET -> Root / "store" / "order" / orderIdVarr(orderId) =>
delegate.getOrderById.handle(req, orderId, responses)
}
val responses: getOrderByIdResponses[F] = new getOrderByIdResponses[F] {
def resp200(value: Order): F[Response[F]] = Ok(value)
def resp200(): F[Response[F]] = Ok()
def resp400(): F[Response[F]] = BadRequest()
def resp404(): F[Response[F]] = NotFound()
}
}
object placeOrder {
import StoreApiDelegate.placeOrderResponses
val route = HttpRoutes.of[F] {
case req @ POST -> Root / "store" / "order" =>
delegate.placeOrder.handle(req, req.asJsonDecode[Order] , responses)
}
val responses: placeOrderResponses[F] = new placeOrderResponses[F] {
def resp200(value: Order): F[Response[F]] = Ok(value)
def resp200(): F[Response[F]] = Ok()
def resp400(): F[Response[F]] = BadRequest()
}
}
val routes =
deleteOrder.route <+>
getOrderById.route <+>
placeOrder.route
val routesapi_key =
getInventory.routeapi_key
}
object StoreApiDelegate {
trait deleteOrderResponses[F[_]] {
def resp400(): F[Response[F]]
def resp404(): F[Response[F]]
}
trait getInventoryResponses[F[_]] {
def resp200(value: Map[String, Int]): F[Response[F]]
}
trait getOrderByIdResponses[F[_]] {
def resp200(value: Order): F[Response[F]]
def resp200(): F[Response[F]]
def resp400(): F[Response[F]]
def resp404(): F[Response[F]]
}
trait placeOrderResponses[F[_]] {
def resp200(value: Order): F[Response[F]]
def resp200(): F[Response[F]]
def resp400(): F[Response[F]]
}
}
trait StoreApiDelegate[F[_], api_key] {
trait deleteOrder {
import StoreApiDelegate.deleteOrderResponses
def handle(
req: Request[F],
orderId: String,
responses: deleteOrderResponses[F]
): F[Response[F]]
}
def deleteOrder: deleteOrder
trait getInventory {
import StoreApiDelegate.getInventoryResponses
def handle_api_key(
auth: api_key,
req: Request[F],
responses: getInventoryResponses[F]
): F[Response[F]]
}
def getInventory: getInventory
trait getOrderById {
import StoreApiDelegate.getOrderByIdResponses
def handle(
req: Request[F],
orderId: Refined[Long, GreaterEqual[1] And LessEqual[5]],
responses: getOrderByIdResponses[F]
): F[Response[F]]
}
def getOrderById: getOrderById
trait placeOrder {
import StoreApiDelegate.placeOrderResponses
def handle(
req: Request[F],
placeOrder: F[Order],
responses: placeOrderResponses[F]
): F[Response[F]]
}
def placeOrder: placeOrder
}

View File

@ -0,0 +1,326 @@
package org.openapitools.apis
import org.openapitools.apis.path._
import org.openapitools.apis.query._
import org.openapitools.models.User
import java.time.ZonedDateTime
import eu.timepit.refined.api.Refined
import eu.timepit.refined.boolean.And
import eu.timepit.refined.string.MatchesRegex
import cats.Monad
import cats.syntax.all._
import org.http4s._
import org.http4s.circe._
import org.http4s.server._
import org.http4s.headers._
import org.http4s.dsl.Http4sDsl
import org.http4s.circe.CirceEntityEncoder._
final case class UserApiRoutes[
F[_]: JsonDecoder: Monad, api_key
](delegate: UserApiDelegate[F, api_key]) extends Http4sDsl[F] {
object createUser {
import UserApiDelegate.createUserResponses
val routeapi_key = AuthedRoutes.of[api_key, F] {
case (req @ POST -> Root / "user") as auth =>
delegate.createUser.handle_api_key(auth, req, req.asJsonDecode[User] , responses)
}
val responses: createUserResponses[F] = new createUserResponses[F] {
def resp200(): F[Response[F]] = Ok()
}
}
object createUsersWithArrayInput {
import UserApiDelegate.createUsersWithArrayInputResponses
val routeapi_key = AuthedRoutes.of[api_key, F] {
case (req @ POST -> Root / "user" / "createWithArray") as auth =>
delegate.createUsersWithArrayInput.handle_api_key(auth, req, req.asJsonDecode[List[User]] , responses)
}
val responses: createUsersWithArrayInputResponses[F] = new createUsersWithArrayInputResponses[F] {
def resp200(): F[Response[F]] = Ok()
}
}
object createUsersWithListInput {
import UserApiDelegate.createUsersWithListInputResponses
val routeapi_key = AuthedRoutes.of[api_key, F] {
case (req @ POST -> Root / "user" / "createWithList") as auth =>
delegate.createUsersWithListInput.handle_api_key(auth, req, req.asJsonDecode[List[User]] , responses)
}
val responses: createUsersWithListInputResponses[F] = new createUsersWithListInputResponses[F] {
def resp200(): F[Response[F]] = Ok()
}
}
object deleteUser {
import UserApiDelegate.deleteUserResponses
val routeapi_key = AuthedRoutes.of[api_key, F] {
case (req @ DELETE -> Root / "user" / username) as auth =>
delegate.deleteUser.handle_api_key(auth, req, username, responses)
}
val responses: deleteUserResponses[F] = new deleteUserResponses[F] {
def resp400(): F[Response[F]] = BadRequest()
def resp404(): F[Response[F]] = NotFound()
}
}
object getUserByName {
import UserApiDelegate.getUserByNameResponses
val route = HttpRoutes.of[F] {
case req @ GET -> Root / "user" / username =>
delegate.getUserByName.handle(req, username, responses)
}
val responses: getUserByNameResponses[F] = new getUserByNameResponses[F] {
def resp200(value: User): F[Response[F]] = Ok(value)
def resp200(): F[Response[F]] = Ok()
def resp400(): F[Response[F]] = BadRequest()
def resp404(): F[Response[F]] = NotFound()
}
}
object loginUser {
import UserApiDelegate.loginUserResponses
object usernameQueryParam extends QueryParamDecoderMatcher[Refined[String, MatchesRegex["^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$"]]]("username")
object passwordQueryParam extends QueryParamDecoderMatcher[String]("password")
val route = HttpRoutes.of[F] {
case req @ GET -> Root / "user" / "login" :? usernameQueryParam(username) +& passwordQueryParam(password) =>
delegate.loginUser.handle(req, username, password, responses)
}
val responses: loginUserResponses[F] = new loginUserResponses[F] {
def resp200(value: String): F[Response[F]] = Ok(value)
def resp200(): F[Response[F]] = Ok()
def resp400(): F[Response[F]] = BadRequest()
}
}
object logoutUser {
import UserApiDelegate.logoutUserResponses
val routeapi_key = AuthedRoutes.of[api_key, F] {
case (req @ GET -> Root / "user" / "logout") as auth =>
delegate.logoutUser.handle_api_key(auth, req, responses)
}
val responses: logoutUserResponses[F] = new logoutUserResponses[F] {
def resp200(): F[Response[F]] = Ok()
}
}
object updateUser {
import UserApiDelegate.updateUserResponses
val routeapi_key = AuthedRoutes.of[api_key, F] {
case (req @ PUT -> Root / "user" / username) as auth =>
delegate.updateUser.handle_api_key(auth, req, req.asJsonDecode[User] , username, responses)
}
val responses: updateUserResponses[F] = new updateUserResponses[F] {
def resp400(): F[Response[F]] = BadRequest()
def resp404(): F[Response[F]] = NotFound()
}
}
val routes =
getUserByName.route <+>
loginUser.route
val routesapi_key =
createUser.routeapi_key <+>
createUsersWithArrayInput.routeapi_key <+>
createUsersWithListInput.routeapi_key <+>
deleteUser.routeapi_key <+>
logoutUser.routeapi_key <+>
updateUser.routeapi_key
}
object UserApiDelegate {
trait createUserResponses[F[_]] {
def resp200(): F[Response[F]]
}
trait createUsersWithArrayInputResponses[F[_]] {
def resp200(): F[Response[F]]
}
trait createUsersWithListInputResponses[F[_]] {
def resp200(): F[Response[F]]
}
trait deleteUserResponses[F[_]] {
def resp400(): F[Response[F]]
def resp404(): F[Response[F]]
}
trait getUserByNameResponses[F[_]] {
def resp200(value: User): F[Response[F]]
def resp200(): F[Response[F]]
def resp400(): F[Response[F]]
def resp404(): F[Response[F]]
}
trait loginUserResponses[F[_]] {
def resp200(value: String): F[Response[F]]
def resp200(): F[Response[F]]
def resp400(): F[Response[F]]
}
trait logoutUserResponses[F[_]] {
def resp200(): F[Response[F]]
}
trait updateUserResponses[F[_]] {
def resp400(): F[Response[F]]
def resp404(): F[Response[F]]
}
}
trait UserApiDelegate[F[_], api_key] {
trait createUser {
import UserApiDelegate.createUserResponses
def handle_api_key(
auth: api_key,
req: Request[F],
createUser: F[User],
responses: createUserResponses[F]
): F[Response[F]]
}
def createUser: createUser
trait createUsersWithArrayInput {
import UserApiDelegate.createUsersWithArrayInputResponses
def handle_api_key(
auth: api_key,
req: Request[F],
createUsersWithArrayInput: F[List[User]],
responses: createUsersWithArrayInputResponses[F]
): F[Response[F]]
}
def createUsersWithArrayInput: createUsersWithArrayInput
trait createUsersWithListInput {
import UserApiDelegate.createUsersWithListInputResponses
def handle_api_key(
auth: api_key,
req: Request[F],
createUsersWithListInput: F[List[User]],
responses: createUsersWithListInputResponses[F]
): F[Response[F]]
}
def createUsersWithListInput: createUsersWithListInput
trait deleteUser {
import UserApiDelegate.deleteUserResponses
def handle_api_key(
auth: api_key,
req: Request[F],
username: String,
responses: deleteUserResponses[F]
): F[Response[F]]
}
def deleteUser: deleteUser
trait getUserByName {
import UserApiDelegate.getUserByNameResponses
def handle(
req: Request[F],
username: String,
responses: getUserByNameResponses[F]
): F[Response[F]]
}
def getUserByName: getUserByName
trait loginUser {
import UserApiDelegate.loginUserResponses
def handle(
req: Request[F],
username: Refined[String, MatchesRegex["^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$"]],
password: String,
responses: loginUserResponses[F]
): F[Response[F]]
}
def loginUser: loginUser
trait logoutUser {
import UserApiDelegate.logoutUserResponses
def handle_api_key(
auth: api_key,
req: Request[F],
responses: logoutUserResponses[F]
): F[Response[F]]
}
def logoutUser: logoutUser
trait updateUser {
import UserApiDelegate.updateUserResponses
def handle_api_key(
auth: api_key,
req: Request[F],
updateUser: F[User],
username: String,
responses: updateUserResponses[F]
): F[Response[F]]
}
def updateUser: updateUser
}

View File

@ -0,0 +1,62 @@
package org.openapitools.apis
import cats.syntax.all._
import cats.data.ValidatedNel
import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.api.Validate
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZonedDateTime
import java.util.UUID
import scala.util.Try
object path {
trait Varrr[T] {
def unapply(str: String): Option[T]
}
implicit val LocalDateVarr: Varrr[LocalDate] = new Varrr[LocalDate] {
def unapply(str: String): Option[LocalDate] = Try(LocalDate.parse(str)).toOption
}
implicit val LocalDateTimeVarr: Varrr[LocalDateTime] = new Varrr[LocalDateTime] {
def unapply(str: String): Option[LocalDateTime] = Try(LocalDateTime.parse(str)).toOption
}
implicit val ZonedDateTimeVarr: Varrr[ZonedDateTime] = new Varrr[ZonedDateTime] {
def unapply(str: String): Option[ZonedDateTime] = Try(ZonedDateTime.parse(str)).toOption
}
implicit val UUIDVarr: Varrr[UUID] = new Varrr[UUID] {
def unapply(str: String): Option[UUID] = Try(java.util.UUID.fromString(str)).toOption
}
implicit val IntVarr: Varrr[Int] = new Varrr[Int] {
def unapply(str: String): Option[Int] = Try(str.toInt).toOption
}
implicit val LongVarr: Varrr[Long] = new Varrr[Long] {
def unapply(str: String): Option[Long] = Try(str.toLong).toOption
}
implicit val DoubleVarr: Varrr[Double] = new Varrr[Double] {
def unapply(str: String): Option[Double] = Try(str.toDouble).toOption
}
implicit val BigDecimalVarr: Varrr[BigDecimal] = new Varrr[BigDecimal] {
def unapply(str: String): Option[BigDecimal] = Try(BigDecimal(str)).toOption
}
implicit val StringVarr: Varrr[String] = new Varrr[String] {
def unapply(str: String): Option[String] = str.some
}
abstract class RefinedVarr[T, P](implicit varrr: Varrr[T], validate: Validate[T, P]) extends Varrr[Refined[T, P]] {
def unapply(str: String): Option[Refined[T, P]] =
varrr.unapply(str).flatMap(x => refineV(x).toOption)
}
}

View File

@ -0,0 +1,59 @@
package org.openapitools.apis
import cats.data.NonEmptyList
import cats.data.ValidatedNel
import cats.syntax.all._
import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.api.Validate
import org.http4s.ParseFailure
import org.http4s.QueryParamDecoder
import org.http4s.QueryParameterValue
import java.time._
import java.util.UUID
object query {
implicit def rrrefinedQueryParamDecoder[T, P](
implicit tDecoder: QueryParamDecoder[T], validate: Validate[T, P]
): QueryParamDecoder[Refined[T, P]] = new QueryParamDecoder[Refined[T, P]] {
def decode(value: QueryParameterValue): ValidatedNel[ParseFailure,Refined[T, P]] =
tDecoder.decode(value).withEither(t => t.flatMap(x =>
refineV(x).leftMap(err => NonEmptyList.one(ParseFailure(err, err)))
))
}
abstract class QuerySeqParamDecoderMatcher[T: QueryParamDecoder](name: String) {
def unapply(params: Map[String, Seq[String]]): Option[List[T]] =
params
.get(name)
.flatMap(values =>
values.toList.traverse(s => QueryParamDecoder[T].decode(QueryParameterValue(s)).toOption))
}
abstract class OptionalQuerySeqParamDecoderMatcher[T: QueryParamDecoder](name: String) {
def unapply(params: Map[String, List[String]]): Option[Option[List[T]]] =
params
.get(name)
.flatMap(values =>
values.toList.traverse(s => QueryParamDecoder[T].decode(QueryParameterValue(s)).toOption))
.fold(List.empty[T].some.some)(_.some.some)
}
implicit lazy val BigDecimalQueryParamDecoder: QueryParamDecoder[BigDecimal] =
QueryParamDecoder.fromUnsafeCast[BigDecimal](x => BigDecimal(x.value))("BigDecimal")
implicit lazy val LocalDateTimeQueryParamDecoder: QueryParamDecoder[LocalDateTime] =
QueryParamDecoder.fromUnsafeCast[LocalDateTime](x => LocalDateTime.parse(x.value))("LocalDateTime")
implicit lazy val LocalDateQueryParamDecoder: QueryParamDecoder[LocalDate] =
QueryParamDecoder.fromUnsafeCast[LocalDate](x => LocalDate.parse(x.value))("LocalDateTime")
implicit lazy val ZonedDateTimeQueryParamDecoder: QueryParamDecoder[ZonedDateTime] =
QueryParamDecoder.fromUnsafeCast[ZonedDateTime](x => ZonedDateTime.parse(x.value))("ZonedDateTime")
implicit lazy val UUIDQueryParamDecoder: QueryParamDecoder[UUID] =
QueryParamDecoder.fromUnsafeCast[UUID](x => UUID.fromString(x.value))("UUID")
}

View File

@ -0,0 +1,134 @@
package org.openapitools.models
import java.time._
import io.circe.refined._
import io.circe.syntax._
import io.circe.{ Decoder, Encoder }
import io.circe.generic.semiauto.{ deriveDecoder, deriveEncoder }
import eu.timepit.refined.api.Refined
import eu.timepit.refined.boolean.And
import eu.timepit.refined.string.MatchesRegex
import java.time.ZonedDateTime
/**
* Describes the result of uploading an image resource
* @param code
* @param _type
* @param message
*/
case class ApiResponse(
code: Option[Int],
_type: Option[String],
message: Option[String]
)
object ApiResponse {
implicit val encoderApiResponse: Encoder[ApiResponse] = deriveEncoder[ApiResponse].mapJson(_.dropNullValues)
implicit val decoderApiResponse: Decoder[ApiResponse] = deriveDecoder[ApiResponse]
}
/**
* A category for a pet
* @param id
* @param name
*/
case class Category(
id: Option[Long],
name: Option[Refined[String, MatchesRegex["^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$"]]]
)
object Category {
implicit val encoderCategory: Encoder[Category] = deriveEncoder[Category].mapJson(_.dropNullValues)
implicit val decoderCategory: Decoder[Category] = deriveDecoder[Category]
}
/**
* An order for a pets from the pet store
* @param id
* @param petId
* @param quantity
* @param shipDate
* @param status Order Status
* @param complete
*/
case class Order(
id: Option[Long],
petId: Option[Long],
quantity: Option[Int],
shipDate: Option[ZonedDateTime],
status: Option[String],
complete: Option[Boolean]
)
object Order {
implicit val encoderOrder: Encoder[Order] = deriveEncoder[Order].mapJson(_.dropNullValues)
implicit val decoderOrder: Decoder[Order] = deriveDecoder[Order]
}
/**
* A pet for sale in the pet store
* @param id
* @param category
* @param name
* @param photoUrls
* @param tags
* @param status pet status in the store
*/
case class Pet(
id: Option[Long],
category: Option[Category],
name: String,
photoUrls: List[String],
tags: Option[List[Tag]],
status: Option[String]
)
object Pet {
implicit val encoderPet: Encoder[Pet] = deriveEncoder[Pet].mapJson(_.dropNullValues)
implicit val decoderPet: Decoder[Pet] = deriveDecoder[Pet]
}
/**
* A tag for a pet
* @param id
* @param name
*/
case class Tag(
id: Option[Long],
name: Option[String]
)
object Tag {
implicit val encoderTag: Encoder[Tag] = deriveEncoder[Tag].mapJson(_.dropNullValues)
implicit val decoderTag: Decoder[Tag] = deriveDecoder[Tag]
}
/**
* A User who is purchasing from the pet store
* @param id
* @param username
* @param firstName
* @param lastName
* @param email
* @param password
* @param phone
* @param userStatus User Status
*/
case class User(
id: Option[Long],
username: Option[String],
firstName: Option[String],
lastName: Option[String],
email: Option[String],
password: Option[String],
phone: Option[String],
userStatus: Option[Int]
)
object User {
implicit val encoderUser: Encoder[User] = deriveEncoder[User].mapJson(_.dropNullValues)
implicit val decoderUser: Decoder[User] = deriveDecoder[User]
}