forked from loafle/openapi-generator-original
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:
parent
11caad92df
commit
455add6d80
1
.github/workflows/samples-scala.yaml
vendored
1
.github/workflows/samples-scala.yaml
vendored
@ -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
|
||||
|
6
bin/configs/scala-http4s-server.yaml
Normal file
6
bin/configs/scala-http4s-server.yaml
Normal 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
|
@ -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; }
|
||||
}
|
@ -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
|
||||
|
255
modules/openapi-generator/src/main/resources/scala-http4s-server/api.mustache
vendored
Normal file
255
modules/openapi-generator/src/main/resources/scala-http4s-server/api.mustache
vendored
Normal 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}}
|
||||
}
|
53
modules/openapi-generator/src/main/resources/scala-http4s-server/apis.mustache
vendored
Normal file
53
modules/openapi-generator/src/main/resources/scala-http4s-server/apis.mustache
vendored
Normal 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}}
|
||||
}
|
@ -0,0 +1 @@
|
||||
sbt.version=1.8.2
|
@ -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)
|
21
modules/openapi-generator/src/main/resources/scala-http4s-server/delegateArgs.mustache
vendored
Normal file
21
modules/openapi-generator/src/main/resources/scala-http4s-server/delegateArgs.mustache
vendored
Normal 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}}
|
@ -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}}
|
6
modules/openapi-generator/src/main/resources/scala-http4s-server/delegateCallJson.mustache
vendored
Normal file
6
modules/openapi-generator/src/main/resources/scala-http4s-server/delegateCallJson.mustache
vendored
Normal 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}}
|
62
modules/openapi-generator/src/main/resources/scala-http4s-server/path.mustache
vendored
Normal file
62
modules/openapi-generator/src/main/resources/scala-http4s-server/path.mustache
vendored
Normal 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)
|
||||
}
|
||||
}
|
59
modules/openapi-generator/src/main/resources/scala-http4s-server/query.mustache
vendored
Normal file
59
modules/openapi-generator/src/main/resources/scala-http4s-server/query.mustache
vendored
Normal 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")
|
||||
}
|
98
modules/openapi-generator/src/main/resources/scala-http4s-server/types.mustache
vendored
Normal file
98
modules/openapi-generator/src/main/resources/scala-http4s-server/types.mustache
vendored
Normal 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}}
|
@ -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
|
@ -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
|
@ -0,0 +1 @@
|
||||
unset
|
27
samples/server/petstore/scala-http4s-server/build.sbt
Normal file
27
samples/server/petstore/scala-http4s-server/build.sbt
Normal 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)
|
34
samples/server/petstore/scala-http4s-server/pom.xml
Normal file
34
samples/server/petstore/scala-http4s-server/pom.xml
Normal 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>
|
@ -0,0 +1 @@
|
||||
sbt.version=1.8.2
|
@ -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
|
||||
}
|
@ -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
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
@ -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]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user