diff --git a/bin/scala-akka-http-server-petstore.sh b/bin/scala-akka-http-server-petstore.sh
new file mode 100644
index 000000000000..83ca77a4c384
--- /dev/null
+++ b/bin/scala-akka-http-server-petstore.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+SCRIPT="$0"
+
+while [ -h "$SCRIPT" ] ; do
+ ls=$(ls -ld "$SCRIPT")
+ link=$(expr "$ls" : '.*-> \(.*\)$')
+ if expr "$link" : '/.*' > /dev/null; then
+ SCRIPT="$link"
+ else
+ SCRIPT=$(dirname "$SCRIPT")/"$link"
+ fi
+done
+
+if [ ! -d "${APP_DIR}" ]; then
+ APP_DIR=$(dirname "$SCRIPT")/..
+ APP_DIR=$(cd "${APP_DIR}"; pwd)
+fi
+
+executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar"
+
+if [ ! -f "$executable" ]
+then
+ mvn clean package
+fi
+
+# if you've executed sbt assembly previously it will use that instead.
+export JAVA_OPTS="${JAVA_OPTS} -Xmx1024M -DloggerPath=conf/log4j.properties"
+ags="$@ generate -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g scala-akka-http -o samples/server/petstore/scala-akka-http"
+
+java ${JAVA_OPTS} -jar ${executable} ${ags}
diff --git a/bin/windows/scala-akka-http-server-petstore.bat b/bin/windows/scala-akka-http-server-petstore.bat
new file mode 100644
index 000000000000..f9c73f7d7aec
--- /dev/null
+++ b/bin/windows/scala-akka-http-server-petstore.bat
@@ -0,0 +1,10 @@
+set executable=.\modules\openapi-generator-cli\target\openapi-generator-cli.jar
+
+If Not Exist %executable% (
+ mvn clean package
+)
+
+REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M -DloggerPath=conf/log4j.properties
+set ags=generate --artifact-id "scala-akka-http-petstore-server" -i modules\openapi-generator\src\test\resources\2_0\petstore.yaml -g scala-akka-http -o samples\server\petstore\scala-akka-http
+
+java %JAVA_OPTS% -jar %executable% %ags%
diff --git a/docs/generators.md b/docs/generators.md
index 8e4995658a89..555373e4a690 100644
--- a/docs/generators.md
+++ b/docs/generators.md
@@ -120,6 +120,7 @@ The following generators are available:
* [ruby-on-rails](generators/ruby-on-rails.md)
* [ruby-sinatra](generators/ruby-sinatra.md)
* [rust-server](generators/rust-server.md)
+* [scala-akka-http](generators/scala-akka-http.md)
* [scala-finch](generators/scala-finch.md)
* [scala-lagom-server](generators/scala-lagom-server.md)
* [scala-play-server](generators/scala-play-server.md)
diff --git a/docs/generators/scala-akka-http.md b/docs/generators/scala-akka-http.md
new file mode 100644
index 000000000000..88f96cb531fd
--- /dev/null
+++ b/docs/generators/scala-akka-http.md
@@ -0,0 +1,221 @@
+---
+title: Config Options for scala-akka-http
+sidebar_label: scala-akka-http
+---
+
+| Option | Description | Values | Default |
+| ------ | ----------- | ------ | ------- |
+|akkaHttpVersion|The version of akka-http| |10.1.10|
+|allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
+|apiPackage|package for generated api classes| |null|
+|artifactId|artifactId| |openapi-scala-akka-http-server|
+|artifactVersion|artifact version in generated pom.xml. This also becomes part of the generated library's filename| |1.0.0|
+|dateLibrary|Option. Date library to use|
**joda** Joda (for legacy app) **java8** Java 8 native JSR310 (prefered for JDK 1.8+) |java8|
+|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
+|groupId|groupId in generated pom.xml| |org.openapitools|
+|invokerPackage|root package for generated code| |org.openapitools.server|
+|modelPackage|package for generated models| |null|
+|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name| |camelCase|
+|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false|
+|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
+|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
+|sourceFolder|source folder for generated code| |null|
+
+## IMPORT MAPPING
+
+| Type/Alias | Imports |
+| ---------- | ------- |
+|Array|java.util.List|
+|ArrayList|java.util.ArrayList|
+|Date|java.util.Date|
+|DateTime|org.joda.time.*|
+|File|java.io.File|
+|HashMap|java.util.HashMap|
+|ListBuffer|scala.collection.mutable.ListBuffer|
+|ListSet|scala.collection.immutable.ListSet|
+|LocalDate|org.joda.time.*|
+|LocalDateTime|org.joda.time.*|
+|LocalTime|org.joda.time.*|
+|Timestamp|java.sql.Timestamp|
+|URI|java.net.URI|
+|UUID|java.util.UUID|
+
+
+## INSTANTIATION TYPES
+
+| Type/Alias | Instantiated By |
+| ---------- | --------------- |
+|array|ListBuffer|
+|map|Map|
+|set|Set|
+
+
+## LANGUAGE PRIMITIVES
+
+
+Any
+Array
+Boolean
+Double
+Float
+Int
+List
+Long
+Map
+Object
+Seq
+String
+boolean
+
+
+## RESERVED WORDS
+
+
+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
+true
+try
+type
+val
+var
+while
+with
+yield
+
+
+## FEATURE SET
+
+
+### Client Modification Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|BasePath|✗|ToolingExtension
+|Authorizations|✗|ToolingExtension
+|UserAgent|✗|ToolingExtension
+
+### Data Type Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Custom|✗|OAS2,OAS3
+|Int32|✓|OAS2,OAS3
+|Int64|✓|OAS2,OAS3
+|Float|✓|OAS2,OAS3
+|Double|✓|OAS2,OAS3
+|Decimal|✓|ToolingExtension
+|String|✓|OAS2,OAS3
+|Byte|✓|OAS2,OAS3
+|Binary|✓|OAS2,OAS3
+|Boolean|✓|OAS2,OAS3
+|Date|✓|OAS2,OAS3
+|DateTime|✓|OAS2,OAS3
+|Password|✓|OAS2,OAS3
+|File|✓|OAS2
+|Array|✓|OAS2,OAS3
+|Maps|✓|ToolingExtension
+|CollectionFormat|✓|OAS2
+|CollectionFormatMulti|✓|OAS2
+|Enum|✓|OAS2,OAS3
+|ArrayOfEnum|✓|ToolingExtension
+|ArrayOfModel|✓|ToolingExtension
+|ArrayOfCollectionOfPrimitives|✓|ToolingExtension
+|ArrayOfCollectionOfModel|✓|ToolingExtension
+|ArrayOfCollectionOfEnum|✓|ToolingExtension
+|MapOfEnum|✓|ToolingExtension
+|MapOfModel|✓|ToolingExtension
+|MapOfCollectionOfPrimitives|✓|ToolingExtension
+|MapOfCollectionOfModel|✓|ToolingExtension
+|MapOfCollectionOfEnum|✓|ToolingExtension
+
+### Documentation Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Readme|✓|ToolingExtension
+|Model|✓|ToolingExtension
+|Api|✓|ToolingExtension
+
+### Global Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Host|✓|OAS2,OAS3
+|BasePath|✓|OAS2,OAS3
+|Info|✓|OAS2,OAS3
+|Schemes|✗|OAS2,OAS3
+|PartialSchemes|✓|OAS2,OAS3
+|Consumes|✓|OAS2
+|Produces|✓|OAS2
+|ExternalDocumentation|✓|OAS2,OAS3
+|Examples|✓|OAS2,OAS3
+|XMLStructureDefinitions|✗|OAS2,OAS3
+|MultiServer|✗|OAS3
+|ParameterizedServer|✗|OAS3
+|ParameterStyling|✗|OAS3
+|Callbacks|✗|OAS3
+|LinkObjects|✗|OAS3
+
+### Parameter Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Path|✓|OAS2,OAS3
+|Query|✓|OAS2,OAS3
+|Header|✓|OAS2,OAS3
+|Body|✓|OAS2
+|FormUnencoded|✓|OAS2
+|FormMultipart|✓|OAS2
+|Cookie|✗|OAS3
+
+### Schema Support Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Simple|✓|OAS2,OAS3
+|Composite|✓|OAS2,OAS3
+|Polymorphism|✗|OAS2,OAS3
+|Union|✗|OAS3
+
+### Security Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|BasicAuth|✓|OAS2,OAS3
+|ApiKey|✓|OAS2,OAS3
+|OpenIDConnect|✗|OAS3
+|BearerToken|✓|OAS3
+|OAuth2_Implicit|✗|OAS2,OAS3
+|OAuth2_Password|✗|OAS2,OAS3
+|OAuth2_ClientCredentials|✗|OAS2,OAS3
+|OAuth2_AuthorizationCode|✗|OAS2,OAS3
+
+### Wire Format Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|JSON|✓|OAS2,OAS3
+|XML|✓|OAS2,OAS3
+|PROTOBUF|✗|ToolingExtension
+|Custom|✓|OAS2,OAS3
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaAkkaHttpServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaAkkaHttpServerCodegen.java
new file mode 100644
index 000000000000..5e1ae3dfc7dd
--- /dev/null
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaAkkaHttpServerCodegen.java
@@ -0,0 +1,486 @@
+package org.openapitools.codegen.languages;
+
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.parameters.Parameter;
+import io.swagger.v3.oas.models.servers.Server;
+import org.openapitools.codegen.*;
+
+import java.io.File;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.openapitools.codegen.meta.features.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ScalaAkkaHttpServerCodegen extends AbstractScalaCodegen implements CodegenConfig {
+ protected String groupId;
+ protected String artifactId;
+ protected String artifactVersion;
+ protected String invokerPackage;
+
+ protected String akkaHttpVersion;
+
+ public static final String AKKA_HTTP_VERSION = "akkaHttpVersion";
+ public static final String AKKA_HTTP_VERSION_DESC = "The version of akka-http";
+ public static final String DEFAULT_AKKA_HTTP_VERSION = "10.1.10";
+
+ static Logger LOGGER = LoggerFactory.getLogger(ScalaAkkaHttpServerCodegen.class);
+
+ public CodegenType getTag() {
+ return CodegenType.SERVER;
+ }
+
+ public String getName() {
+ return "scala-akka-http";
+ }
+
+ public String getHelp() {
+ return "Generates a scala-akka-http server.";
+ }
+
+ public ScalaAkkaHttpServerCodegen() {
+ super();
+
+ modifyFeatureSet(features -> features
+ .includeDocumentationFeatures(DocumentationFeature.Readme)
+ .wireFormatFeatures(EnumSet.of(WireFormatFeature.JSON, WireFormatFeature.XML, WireFormatFeature.Custom))
+ .securityFeatures(EnumSet.of(
+ SecurityFeature.BasicAuth,
+ SecurityFeature.ApiKey,
+ SecurityFeature.BearerToken
+ ))
+ .excludeGlobalFeatures(
+ GlobalFeature.XMLStructureDefinitions,
+ GlobalFeature.Callbacks,
+ GlobalFeature.LinkObjects,
+ GlobalFeature.ParameterStyling
+ )
+ .excludeSchemaSupportFeatures(
+ SchemaSupportFeature.Polymorphism
+ )
+ .excludeParameterFeatures(
+ ParameterFeature.Cookie
+ )
+ );
+
+ outputFolder = "generated-code" + File.separator + "scala-akka-http";
+ modelTemplateFiles.put("model.mustache", ".scala");
+ apiTemplateFiles.put("api.mustache", ".scala");
+ embeddedTemplateDir = templateDir = "scala-akka-http-server";
+
+ groupId = "org.openapitools";
+ artifactId = "openapi-scala-akka-http-server";
+ artifactVersion = "1.0.0";
+ apiPackage = "org.openapitools.server.api";
+ modelPackage = "org.openapitools.server.model";
+ invokerPackage = "org.openapitools.server";
+ akkaHttpVersion = DEFAULT_AKKA_HTTP_VERSION;
+
+ setReservedWordsLowerCase(
+ Arrays.asList(
+ "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")
+ );
+
+ cliOptions.add(CliOption.newString(CodegenConstants.INVOKER_PACKAGE, CodegenConstants.INVOKER_PACKAGE_DESC).defaultValue(invokerPackage));
+ cliOptions.add(CliOption.newString(CodegenConstants.GROUP_ID, CodegenConstants.GROUP_ID_DESC).defaultValue(groupId));
+ cliOptions.add(CliOption.newString(CodegenConstants.ARTIFACT_ID, CodegenConstants.ARTIFACT_ID).defaultValue(artifactId));
+ cliOptions.add(CliOption.newString(CodegenConstants.ARTIFACT_VERSION, CodegenConstants.ARTIFACT_VERSION_DESC).defaultValue(artifactVersion));
+ cliOptions.add(CliOption.newString(AKKA_HTTP_VERSION, AKKA_HTTP_VERSION_DESC).defaultValue(akkaHttpVersion));
+
+ importMapping.remove("Seq");
+ importMapping.remove("List");
+ importMapping.remove("Set");
+ importMapping.remove("Map");
+
+ typeMapping = new HashMap<>();
+ typeMapping.put("array", "Seq");
+ typeMapping.put("set", "Set");
+ typeMapping.put("boolean", "Boolean");
+ typeMapping.put("string", "String");
+ typeMapping.put("int", "Int");
+ typeMapping.put("integer", "Int");
+ typeMapping.put("long", "Long");
+ typeMapping.put("float", "Float");
+ typeMapping.put("byte", "Byte");
+ typeMapping.put("short", "Short");
+ typeMapping.put("char", "Char");
+ typeMapping.put("double", "Double");
+ typeMapping.put("object", "Any");
+ typeMapping.put("file", "File");
+ typeMapping.put("binary", "File");
+ typeMapping.put("number", "Double");
+
+ instantiationTypes.put("array", "ListBuffer");
+ instantiationTypes.put("map", "Map");
+
+ supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
+ }
+
+ @Override
+ public void processOpts() {
+ super.processOpts();
+
+ if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) {
+ invokerPackage = (String) additionalProperties.get(CodegenConstants.INVOKER_PACKAGE);
+ } else {
+ additionalProperties.put(CodegenConstants.INVOKER_PACKAGE, invokerPackage);
+ }
+
+ if (additionalProperties.containsKey(CodegenConstants.GROUP_ID)) {
+ groupId = (String) additionalProperties.get(CodegenConstants.GROUP_ID);
+ } else {
+ additionalProperties.put(CodegenConstants.GROUP_ID, groupId);
+ }
+
+ if (additionalProperties.containsKey(CodegenConstants.ARTIFACT_ID)) {
+ artifactId = (String) additionalProperties.get(CodegenConstants.ARTIFACT_ID);
+ } else {
+ additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId);
+ }
+
+ if (additionalProperties.containsKey(CodegenConstants.ARTIFACT_VERSION)) {
+ artifactVersion = (String) additionalProperties.get(CodegenConstants.ARTIFACT_VERSION);
+ } else {
+ additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion);
+ }
+
+ if (additionalProperties.containsKey(AKKA_HTTP_VERSION)) {
+ akkaHttpVersion = (String) additionalProperties.get(AKKA_HTTP_VERSION);
+ } else {
+ additionalProperties.put(AKKA_HTTP_VERSION, akkaHttpVersion);
+ }
+
+ parseAkkaHttpVersion();
+
+ supportingFiles.add(new SupportingFile("build.sbt.mustache", "", "build.sbt"));
+ supportingFiles.add(new SupportingFile("controller.mustache",
+ (sourceFolder + File.separator + invokerPackage).replace(".", java.io.File.separator), "Controller.scala"));
+ supportingFiles.add(new SupportingFile("helper.mustache",
+ (sourceFolder + File.separator + invokerPackage).replace(".", java.io.File.separator), "AkkaHttpHelper.scala"));
+ supportingFiles.add(new SupportingFile("stringDirectives.mustache",
+ (sourceFolder + File.separator + invokerPackage).replace(".", java.io.File.separator), "StringDirectives.scala"));
+ supportingFiles.add(new SupportingFile("multipartDirectives.mustache",
+ (sourceFolder + File.separator + invokerPackage).replace(".", java.io.File.separator), "MultipartDirectives.scala"));
+ }
+
+ private static final String IS_10_1_10_PLUS = "akkaHttp10_1_10_plus";
+ private boolean is10_1_10AndAbove = false;
+
+ private static final Pattern akkaVersionPattern = Pattern.compile("([0-9]+)(\\.([0-9]+))?(\\.([0-9]+))?(.\\+)?");
+ private void parseAkkaHttpVersion() {
+ Matcher matcher = akkaVersionPattern.matcher(akkaHttpVersion);
+ if (matcher.matches()) {
+ String majorS = matcher.group(1);
+ String minorS = matcher.group(3);
+ String patchS = matcher.group(5);
+ boolean andAbove = matcher.group(6) != null;
+ int major = -1, minor = -1, patch = -1;
+ try {
+ if (majorS != null) {
+ major = Integer.parseInt(majorS);
+ if (minorS != null) {
+ minor = Integer.parseInt(minorS);
+ if (patchS != null) {
+ patch = Integer.parseInt(patchS);
+ }
+ }
+ }
+
+
+ if (major > 10 || major == -1 && andAbove) {
+ is10_1_10AndAbove = true;
+ } else if (major == 10) {
+ if (minor > 1 || minor == -1 && andAbove) {
+ is10_1_10AndAbove = true;
+ } else if (minor == 1) {
+ if (patch >= 10 || patch == -1 && andAbove) {
+ is10_1_10AndAbove = true;
+ }
+ }
+ }
+
+ } catch (NumberFormatException e) {
+ LOGGER.warn("Unable to parse " + AKKA_HTTP_VERSION + ": " + akkaHttpVersion + ", fallback to " + DEFAULT_AKKA_HTTP_VERSION);
+ akkaHttpVersion = DEFAULT_AKKA_HTTP_VERSION;
+ is10_1_10AndAbove = true;
+ }
+ }
+
+ additionalProperties.put(IS_10_1_10_PLUS, is10_1_10AndAbove);
+ }
+
+ @Override
+ public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, List servers) {
+ CodegenOperation codegenOperation = super.fromOperation(path, httpMethod, operation, servers);
+ addPathMatcher(codegenOperation);
+ return codegenOperation;
+ }
+
+ @Override
+ public CodegenParameter fromParameter(Parameter parameter, Set imports) {
+ CodegenParameter param = super.fromParameter(parameter, imports);
+ // Removing unhandled types
+ if(!primitiveParamTypes.contains(param.dataType)) {
+ param.dataType = "String";
+ }
+ if (!param.required) {
+ param.vendorExtensions.put("hasDefaultValue", param.defaultValue != null);
+ // Escaping default string values
+ if (param.defaultValue != null && param.dataType.equals("String")) {
+ param.defaultValue = String.format(Locale.ROOT, "\"%s\"", param.defaultValue);
+ }
+ }
+ return param;
+ }
+
+
+
+ @Override
+ public Map postProcessOperationsWithModels(Map objs, List allModels) {
+ Map baseObjs = super.postProcessOperationsWithModels(objs, allModels);
+ pathMatcherPatternsPostProcessor(baseObjs);
+ marshallingPostProcessor(baseObjs);
+ return baseObjs;
+ }
+
+ private static Set primitiveParamTypes = new HashSet(){{
+ addAll(Arrays.asList(
+ "Int",
+ "Long",
+ "Float",
+ "Double",
+ "Boolean",
+ "String"
+ ));
+ }};
+
+ private static Map pathTypeToMatcher = new HashMap(){{
+ put("Int", "IntNumber");
+ put("Long", "LongNumber");
+ put("Float","FloatNumber");
+ put("Double","DoubleNumber");
+ put("Boolean","Boolean");
+ put("String", "Segment");
+ }};
+
+ protected static void addPathMatcher(CodegenOperation codegenOperation) {
+ LinkedList allPaths = new LinkedList<>(Arrays.asList(codegenOperation.path.split("/")));
+ allPaths.removeIf(""::equals);
+
+ LinkedList pathMatchers = new LinkedList<>();
+ for(String path: allPaths){
+ TextOrMatcher textOrMatcher = new TextOrMatcher("", true, true);
+ if(path.startsWith("{") && path.endsWith("}")) {
+ String parameterName = path.substring(1, path.length()-1);
+ for(CodegenParameter pathParam: codegenOperation.pathParams){
+ if(pathParam.baseName.equals(parameterName)) {
+ String matcher = pathTypeToMatcher.get(pathParam.dataType);
+ if(matcher == null) {
+ LOGGER.warn("The path parameter " + pathParam.baseName +
+ " with the datatype " + pathParam.dataType +
+ " could not be translated to a corresponding path matcher of akka http" +
+ " and therefore has been translated to string.");
+ matcher = pathTypeToMatcher.get("String");
+ }
+ if (pathParam.pattern != null && !pathParam.pattern.isEmpty()) {
+ matcher = pathMatcherPatternName(pathParam);
+ }
+ textOrMatcher.value = matcher;
+ textOrMatcher.isText = false;
+ pathMatchers.add(textOrMatcher);
+ }
+ }
+ } else {
+ textOrMatcher.value = path;
+ textOrMatcher.isText = true;
+ pathMatchers.add(textOrMatcher);
+ }
+ }
+ pathMatchers.getLast().hasMore = false;
+
+ codegenOperation.vendorExtensions.put("paths", pathMatchers);
+ }
+
+ public static String PATH_MATCHER_PATTERNS_KEY = "pathMatcherPatterns";
+
+ @SuppressWarnings("unchecked")
+ private static void pathMatcherPatternsPostProcessor(Map objs) {
+ if (objs != null) {
+ HashMap patternMap = new HashMap<>();
+ Map operations = (Map) objs.get("operations");
+ if (operations != null) {
+ List ops = (List) operations.get("operation");
+ for (CodegenOperation operation: ops) {
+ for (CodegenParameter parameter: operation.pathParams) {
+ if (parameter.pattern != null && !parameter.pattern.isEmpty()) {
+ String name = pathMatcherPatternName(parameter);
+ if (!patternMap.containsKey(name)) {
+ patternMap.put(name, new PathMatcherPattern(name, parameter.pattern.substring(1, parameter.pattern.length() - 1)));
+ }
+ }
+ }
+ }
+ }
+ objs.put(PATH_MATCHER_PATTERNS_KEY, new ArrayList<>(patternMap.values()));
+ }
+ }
+
+ private static String pathMatcherPatternName(CodegenParameter parameter) {
+ return parameter.paramName + "Pattern";
+ }
+
+ // Responsible for setting up Marshallers/Unmarshallers
+ @SuppressWarnings("unchecked")
+ public static void marshallingPostProcessor(Map objs) {
+
+ if (objs == null) {
+ return;
+ }
+
+ Set entityUnmarshallerTypes = new HashSet<>();
+ Set entityMarshallerTypes = new HashSet<>();
+ Set stringUnmarshallerTypes = new HashSet<>();
+ boolean hasCookieParams = false;
+ boolean hasMultipart = false;
+
+ Map operations = (Map) objs.get("operations");
+ if (operations != null) {
+ List operationList = (List) operations.get("operation");
+
+ for (CodegenOperation op : operationList) {
+ boolean isMultipart = op.isMultipart;
+ hasMultipart |= isMultipart;
+ hasCookieParams |= op.getHasCookieParams();
+ ArrayList fileParams = new ArrayList<>();
+ ArrayList nonFileParams = new ArrayList<>();
+ for (CodegenParameter parameter : op.allParams) {
+ if (parameter.isBodyParam || parameter.isFormParam) {
+ if (parameter.isFile) {
+ fileParams.add(parameter.copy());
+ } else {
+ nonFileParams.add(parameter.copy());
+ }
+ if (!parameter.isPrimitiveType) {
+ if (isMultipart) {
+ stringUnmarshallerTypes.add(new Marshaller(parameter));
+ } else {
+ entityUnmarshallerTypes.add(new Marshaller(parameter));
+ }
+ }
+ }
+ }
+ for (int i = 0, size = fileParams.size(); i < size; ++i) {
+ fileParams.get(i).hasMore = i < size - 1;
+ }
+ for (int i = 0, size = nonFileParams.size(); i < size; ++i) {
+ nonFileParams.get(i).hasMore = i < size - 1;
+ }
+
+ HashSet operationSpecificMarshallers = new HashSet<>();
+ for (CodegenResponse response : op.responses) {
+ if (!response.primitiveType) {
+ Marshaller marshaller = new Marshaller(response);
+ entityMarshallerTypes.add(marshaller);
+ operationSpecificMarshallers.add(marshaller);
+ }
+ response.vendorExtensions.put("isDefault", response.code.equals("0"));
+ }
+ op.vendorExtensions.put("specificMarshallers", operationSpecificMarshallers);
+ op.vendorExtensions.put("fileParams", fileParams);
+ op.vendorExtensions.put("nonFileParams", nonFileParams);
+ }
+ }
+
+ objs.put("hasCookieParams", hasCookieParams);
+ objs.put("entityMarshallers", entityMarshallerTypes);
+ objs.put("entityUnmarshallers", entityUnmarshallerTypes);
+ objs.put("stringUnmarshallers", stringUnmarshallerTypes);
+ objs.put("hasMarshalling", !entityMarshallerTypes.isEmpty() || !entityUnmarshallerTypes.isEmpty() || !stringUnmarshallerTypes.isEmpty());
+ objs.put("hasMultipart", hasMultipart);
+ }
+
+}
+
+class Marshaller {
+ String varName;
+ String dataType;
+
+ public Marshaller(CodegenResponse response) {
+ if (response.containerType != null) {
+ this.varName = response.baseType + response.containerType;
+ } else {
+ this.varName = response.baseType;
+ }
+ this.dataType = response.dataType;
+ }
+
+ public Marshaller(CodegenParameter parameter) {
+ if (parameter.isListContainer) {
+ this.varName = parameter.baseType + "List";
+ } else if (parameter.isMapContainer) {
+ this.varName = parameter.baseType + "Map";
+ } else if (parameter.isContainer) {
+ this.varName = parameter.baseType + "Container";
+ } else {
+ this.varName = parameter.baseType;
+ }
+ this.dataType = parameter.dataType;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Marshaller that = (Marshaller) o;
+ return varName.equals(that.varName) &&
+ dataType.equals(that.dataType);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(varName, dataType);
+ }
+}
+
+class PathMatcherPattern {
+ String pathMatcherVarName;
+ String pattern;
+
+ public PathMatcherPattern(String pathMatcherVarName, String pattern) {
+ this.pathMatcherVarName = pathMatcherVarName;
+ this.pattern = pattern;
+ }
+}
+
+class TextOrMatcher {
+ String value;
+ boolean isText;
+ boolean hasMore;
+
+ public TextOrMatcher(String value, boolean isText, boolean hasMore) {
+ this.value = value;
+ this.isText = isText;
+ this.hasMore = hasMore;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ TextOrMatcher that = (TextOrMatcher) o;
+ return isText == that.isText &&
+ hasMore == that.hasMore &&
+ value.equals(that.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value, isText, hasMore);
+ }
+}
diff --git a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig
index cac33205e17f..8e2db93db510 100644
--- a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig
+++ b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig
@@ -129,3 +129,5 @@ org.openapitools.codegen.languages.FsharpFunctionsServerCodegen
org.openapitools.codegen.languages.MarkdownDocumentationCodegen
org.openapitools.codegen.languages.ScalaSttpClientCodegen
+
+org.openapitools.codegen.languages.ScalaAkkaHttpServerCodegen
diff --git a/modules/openapi-generator/src/main/resources/scala-akka-http-server/README.mustache b/modules/openapi-generator/src/main/resources/scala-akka-http-server/README.mustache
new file mode 100644
index 000000000000..64c7b16ced18
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/scala-akka-http-server/README.mustache
@@ -0,0 +1,32 @@
+# {{&appName}}
+
+{{&appDescription}}
+
+{{^hideGenerationTimestamp}}
+ This Scala akka-http framework project was generated by the OpenAPI generator tool at {{generatedDate}}.
+{{/hideGenerationTimestamp}}
+
+{{#generateApis}}
+ ## API
+
+ {{#apiInfo}}
+ {{#apis}}
+ ### {{baseName}}
+
+ |Name|Role|
+ |----|----|
+ |`{{importPath}}Controller`|akka-http API controller|
+ |`{{importPath}}Api`|Representing trait|
+ {{^skipStubs}}
+ |`{{importPath}}ApiImpl`|Default implementation|
+ {{/skipStubs}}
+
+ {{#operations}}
+ {{#operation}}
+ * `{{httpMethod}} {{contextPath}}{{path}}{{#queryParams.0}}?{{/queryParams.0}}{{#queryParams}}{{paramName}}=[value]{{#hasMore}}&{{/hasMore}}{{/queryParams}}` - {{summary}}
+ {{/operation}}
+ {{/operations}}
+
+ {{/apis}}
+ {{/apiInfo}}
+{{/generateApis}}
diff --git a/modules/openapi-generator/src/main/resources/scala-akka-http-server/api.mustache b/modules/openapi-generator/src/main/resources/scala-akka-http-server/api.mustache
new file mode 100644
index 000000000000..0fcceddbbfc4
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/scala-akka-http-server/api.mustache
@@ -0,0 +1,95 @@
+package {{package}}
+
+import akka.http.scaladsl.server.Directives._
+import akka.http.scaladsl.server.Route
+{{^pathMatcherPatterns.isEmpty}}import akka.http.scaladsl.server.{PathMatcher, PathMatcher1}
+{{/pathMatcherPatterns.isEmpty}}
+{{#hasMarshalling}}import akka.http.scaladsl.marshalling.ToEntityMarshaller
+import akka.http.scaladsl.unmarshalling.FromEntityUnmarshaller
+import akka.http.scaladsl.unmarshalling.FromStringUnmarshaller
+{{/hasMarshalling}}
+{{#hasCookieParams}}import akka.http.scaladsl.model.headers.HttpCookiePair
+{{/hasCookieParams}}
+import {{invokerPackage}}.AkkaHttpHelper._
+{{#hasMultipart}}import {{invokerPackage}}.StringDirectives
+import {{invokerPackage}}.MultipartDirectives
+import {{invokerPackage}}.FileField
+import {{invokerPackage}}.PartsAndFiles
+{{/hasMultipart}}
+{{#imports}}import {{import}}
+{{/imports}}
+{{#hasMultipart}}import scala.util.Try
+import akka.http.scaladsl.server.MalformedRequestContentRejection
+import akka.http.scaladsl.server.directives.FileInfo
+{{/hasMultipart}}
+
+
+{{#operations}}
+class {{classname}}(
+ {{classVarName}}Service: {{classname}}Service{{#hasMarshalling}},
+ {{classVarName}}Marshaller: {{classname}}Marshaller{{/hasMarshalling}}
+) {{#hasMultipart}} extends MultipartDirectives with StringDirectives {{/hasMultipart}}{
+
+ {{#pathMatcherPatterns}}import {{classname}}Patterns.{{pathMatcherVarName}}
+ {{/pathMatcherPatterns}}
+
+ {{#hasMarshalling}}import {{classVarName}}Marshaller._
+ {{/hasMarshalling}}
+
+ lazy val route: Route =
+ {{#operation}}
+ path({{#vendorExtensions.paths}}{{#isText}}"{{/isText}}{{value}}{{#isText}}"{{/isText}}{{#hasMore}} / {{/hasMore}}{{/vendorExtensions.paths}}) { {{^pathParams.isEmpty}}({{#pathParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/pathParams}}) => {{/pathParams.isEmpty}}
+ {{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}} { {{^queryParams.isEmpty}}
+ parameters({{#queryParams}}"{{baseName}}".as[{{dataType}}]{{^required}}.?{{#vendorExtensions.hasDefaultValue}}({{{defaultValue}}}){{/vendorExtensions.hasDefaultValue}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/queryParams}}) { ({{#queryParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/queryParams}}) =>{{/queryParams.isEmpty}} {{^headerParams.isEmpty}}
+ {{#headerParams}}{{#required}}headerValueByName{{/required}}{{^required}}optionalHeaderValueByName{{/required}}("{{baseName}}") { {{paramName}} => {{/headerParams}}{{/headerParams.isEmpty}}{{^cookieParams.isEmpty}}
+ {{#cookieParams}}{{#required}}cookie({{/required}}{{^required}}optionalCookie({{/required}}"{{baseName}}"){ {{paramName}} => {{/cookieParams}}{{/cookieParams.isEmpty}}{{#isMultipart}}
+{{> multipart}}{{/isMultipart}}{{^isMultipart}}{{> noMultipart}}{{/isMultipart}}{{^cookieParams.isEmpty}}
+ }{{/cookieParams.isEmpty}}{{^headerParams.isEmpty}}
+ }{{/headerParams.isEmpty}}{{^queryParams.isEmpty}}
+ }{{/queryParams.isEmpty}}
+ }
+ }{{^-last}} ~{{/-last}}
+ {{/operation}}
+}
+
+{{^pathMatcherPatterns.isEmpty}}
+object {{classname}}Patterns {
+
+ {{#pathMatcherPatterns}}val {{pathMatcherVarName}}: PathMatcher1[String] = PathMatcher("{{pattern}}".r)
+ {{/pathMatcherPatterns}}
+}
+{{/pathMatcherPatterns.isEmpty}}
+
+trait {{classname}}Service {
+
+{{#operation}}
+{{#responses}} def {{operationId}}{{#vendorExtensions.isDefault}}Default{{/vendorExtensions.isDefault}}{{^vendorExtensions.isDefault}}{{code}}{{/vendorExtensions.isDefault}}{{#baseType}}({{#vendorExtensions.isDefault}}statusCode: Int, {{/vendorExtensions.isDefault}}response{{baseType}}{{containerType}}: {{dataType}}){{^isPrimitiveType}}(implicit toEntityMarshaller{{baseType}}{{containerType}}: ToEntityMarshaller[{{dataType}}]){{/isPrimitiveType}}{{/baseType}}{{^baseType}}{{#vendorExtensions.isDefault}}(statusCode: Int){{/vendorExtensions.isDefault}}{{/baseType}}: Route =
+ complete(({{#vendorExtensions.isDefault}}statusCode{{/vendorExtensions.isDefault}}{{^vendorExtensions.isDefault}}{{code}}{{/vendorExtensions.isDefault}}, {{#baseType}}response{{baseType}}{{containerType}}{{/baseType}}{{^baseType}}"{{message}}"{{/baseType}}))
+{{/responses}}
+ /**
+{{#responses}} * {{#code}}Code: {{.}}{{/code}}{{#message}}, Message: {{.}}{{/message}}{{#dataType}}, DataType: {{.}}{{/dataType}}
+ {{/responses}}
+ */
+ def {{operationId}}({{> operationParam}}){{^vendorExtensions.specificMarshallers.isEmpty}}
+ (implicit {{#vendorExtensions.specificMarshallers}}toEntityMarshaller{{varName}}: ToEntityMarshaller[{{dataType}}]{{^-last}}, {{/-last}}{{/vendorExtensions.specificMarshallers}}){{/vendorExtensions.specificMarshallers.isEmpty}}: Route
+
+{{/operation}}
+}
+
+{{#hasMarshalling}}
+trait {{classname}}Marshaller {
+{{#entityUnmarshallers}} implicit def fromEntityUnmarshaller{{varName}}: FromEntityUnmarshaller[{{dataType}}]
+
+{{/entityUnmarshallers}}
+
+{{#stringUnmarshallers}} implicit def fromStringUnmarshaller{{varName}}: FromStringUnmarshaller[{{dataType}}]
+
+{{/stringUnmarshallers}}
+
+{{#entityMarshallers}} implicit def toEntityMarshaller{{varName}}: ToEntityMarshaller[{{dataType}}]
+
+{{/entityMarshallers}}
+}
+{{/hasMarshalling}}
+
+{{/operations}}
diff --git a/modules/openapi-generator/src/main/resources/scala-akka-http-server/build.sbt.mustache b/modules/openapi-generator/src/main/resources/scala-akka-http-server/build.sbt.mustache
new file mode 100644
index 000000000000..d4b57b676140
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/scala-akka-http-server/build.sbt.mustache
@@ -0,0 +1,9 @@
+version := "{{artifactVersion}}"
+name := "{{artifactId}}"
+organization := "{{groupId}}"
+scalaVersion := "2.12.8"
+
+libraryDependencies ++= Seq(
+ "com.typesafe.akka" %% "akka-stream" % "2.5.21",
+ "com.typesafe.akka" %% "akka-http" % "{{akkaHttpVersion}}"
+)
diff --git a/modules/openapi-generator/src/main/resources/scala-akka-http-server/controller.mustache b/modules/openapi-generator/src/main/resources/scala-akka-http-server/controller.mustache
new file mode 100644
index 000000000000..ec9fe871c753
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/scala-akka-http-server/controller.mustache
@@ -0,0 +1,16 @@
+package {{invokerPackage}}
+
+import akka.http.scaladsl.Http
+import akka.http.scaladsl.server.Route
+{{#apiInfo}}{{#apis}}{{#operations}}import {{package}}.{{classname}}
+{{/operations}}{{/apis}}{{/apiInfo}}
+import akka.http.scaladsl.server.Directives._
+import akka.actor.ActorSystem
+import akka.stream.ActorMaterializer
+
+class Controller({{#apiInfo}}{{#apis}}{{#operations}}{{classVarName}}: {{classname}}{{#hasMore}}, {{/hasMore}}{{/operations}}{{/apis}}{{/apiInfo}})(implicit system: ActorSystem, materializer: ActorMaterializer) {
+
+ lazy val routes: Route = {{#apiInfo}}{{#apis}}{{#operations}}{{classVarName}}.route {{#hasMore}}~ {{/hasMore}}{{/operations}}{{/apis}}{{/apiInfo}}
+
+ Http().bindAndHandle(routes, "0.0.0.0", 9000)
+}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/scala-akka-http-server/helper.mustache b/modules/openapi-generator/src/main/resources/scala-akka-http-server/helper.mustache
new file mode 100644
index 000000000000..8aa3c0f8c269
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/scala-akka-http-server/helper.mustache
@@ -0,0 +1,34 @@
+package {{invokerPackage}}
+
+import akka.http.scaladsl.server.Directives._
+import akka.http.scaladsl.server.{PathMatcher, PathMatcher1}
+import scala.util.{Failure, Success, Try}
+import scala.util.control.NoStackTrace
+
+object AkkaHttpHelper {
+ def optToTry[T](opt: Option[T], err: => String): Try[T] =
+ opt.map[Try[T]](Success(_)) getOrElse Failure(new RuntimeException(err) with NoStackTrace)
+
+ /**
+ * A PathMatcher that matches and extracts a Float value. The matched string representation is the pure decimal,
+ * optionally signed form of a float value, i.e. without exponent.
+ *
+ * @group pathmatcher
+ */
+ val FloatNumber: PathMatcher1[Float] =
+ PathMatcher("""[+-]?\d*\.?\d*""".r) flatMap { string =>
+ try Some(java.lang.Float.parseFloat(string))
+ catch { case _: NumberFormatException => None }
+ }
+
+ /**
+ * A PathMatcher that matches and extracts a Boolean value.
+ *
+ * @group pathmatcher
+ */
+ val Boolean: PathMatcher1[Boolean] =
+ Segment.flatMap { string =>
+ try Some(string.toBoolean)
+ catch { case _: IllegalArgumentException => None }
+ }
+}
diff --git a/modules/openapi-generator/src/main/resources/scala-akka-http-server/model.mustache b/modules/openapi-generator/src/main/resources/scala-akka-http-server/model.mustache
new file mode 100644
index 000000000000..85ab81e74bfa
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/scala-akka-http-server/model.mustache
@@ -0,0 +1,27 @@
+package {{package}}
+
+{{#imports}}
+import {{import}}
+{{/imports}}
+
+{{#models}}
+{{#model}}
+/**
+{{#title}} * = {{{title}}} =
+ *
+{{/title}}
+{{#description}} * {{{description}}}
+ *
+{{/description}}
+{{#vars}}
+ * @param {{{name}}} {{#description}}{{{description}}}{{/description}}{{#example}} for example: ''{{{example}}}''{{/example}}
+{{/vars}}
+*/
+final case class {{classname}} (
+ {{#vars}}
+ {{{name}}}: {{^required}}Option[{{/required}}{{datatype}}{{^required}}]{{/required}}{{#hasMore}},{{/hasMore}}
+ {{/vars}}
+)
+
+{{/model}}
+{{/models}}
diff --git a/modules/openapi-generator/src/main/resources/scala-akka-http-server/multipart.mustache b/modules/openapi-generator/src/main/resources/scala-akka-http-server/multipart.mustache
new file mode 100644
index 000000000000..6f8d2355b2ed
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/scala-akka-http-server/multipart.mustache
@@ -0,0 +1,12 @@
+ formAndFiles({{#vendorExtensions.fileParams}}FileField("{{baseName}}")){{/vendorExtensions.fileParams}}{{#hasMore}}, {{/hasMore}} { partsAndFiles => {{^vendorExtensions.fileParams.isEmpty}}
+ val _____ : Try[Route] = for {
+ {{#vendorExtensions.fileParams}}{{baseName}} <- optToTry(partsAndFiles.files.get("{{baseName}}"), s"File {{baseName}} missing")
+ {{/vendorExtensions.fileParams}}
+ } yield { {{/vendorExtensions.fileParams.isEmpty}}
+ implicit val vp: StringValueProvider = partsAndFiles.form{{^vendorExtensions.nonFileParams.isEmpty}}
+ stringFields({{#vendorExtensions.nonFileParams}}"{{baseName}}".as[{{dataType}}]{{^required}}.?{{#vendorExtensions.hasDefaultValue}}({{defaultValue}}){{/vendorExtensions.hasDefaultValue}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/vendorExtensions.nonFileParams}}) { ({{#vendorExtensions.nonFileParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/vendorExtensions.nonFileParams}}) =>{{/vendorExtensions.nonFileParams.isEmpty}}
+ {{classVarName}}Service.{{operationId}}({{#allParams}}{{paramName}} = {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{^vendorExtensions.nonFileFormParams.isEmpty}}
+ }{{/vendorExtensions.nonFileFormParams.isEmpty}}{{^vendorExtensions.fileParams.isEmpty}}
+ }
+ _____.fold[Route](t => reject(MalformedRequestContentRejection("Missing file.", t)), identity){{/vendorExtensions.fileParams.isEmpty}}
+ }
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/scala-akka-http-server/multipartDirectives.mustache b/modules/openapi-generator/src/main/resources/scala-akka-http-server/multipartDirectives.mustache
new file mode 100644
index 000000000000..98a2186fd2e7
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/scala-akka-http-server/multipartDirectives.mustache
@@ -0,0 +1,88 @@
+package {{invokerPackage}}
+
+import java.io.File
+
+import akka.annotation.ApiMayChange
+import akka.http.scaladsl.model.Multipart.FormData
+import akka.http.scaladsl.model.{ContentType, HttpEntity, Multipart}
+import akka.http.scaladsl.server.Directive1
+import akka.http.scaladsl.server.directives._
+import akka.stream.Materializer
+import akka.stream.scaladsl._
+
+import scala.collection.immutable
+import scala.concurrent.{ExecutionContextExecutor, Future}
+
+trait MultipartDirectives {
+
+ import akka.http.scaladsl.server.directives.BasicDirectives._
+ import akka.http.scaladsl.server.directives.FutureDirectives._
+ import akka.http.scaladsl.server.directives.MarshallingDirectives._
+
+ @ApiMayChange
+ def formAndFiles(fileFields: FileField*): Directive1[PartsAndFiles] =
+ entity(as[Multipart.FormData]).flatMap {
+ formData =>
+ extractRequestContext.flatMap { ctx =>
+ implicit val mat: Materializer = ctx.materializer
+ implicit val ec: ExecutionContextExecutor = ctx.executionContext
+
+ val uploadingSink: Sink[FormData.BodyPart, Future[PartsAndFiles]] =
+ Sink.foldAsync[PartsAndFiles, Multipart.FormData.BodyPart](PartsAndFiles.Empty) {
+ (acc, part) =>
+ def discard(p: Multipart.FormData.BodyPart): Future[PartsAndFiles] = {
+ p.entity.discardBytes()
+ Future.successful(acc)
+ }
+
+ part.filename.map {
+ fileName =>
+ fileFields.find(_.fieldName == part.name)
+ .map {
+ case FileField(_, destFn) =>
+ val fileInfo = FileInfo(part.name, fileName, part.entity.contentType)
+ val dest = destFn(fileInfo)
+
+ part.entity.dataBytes.runWith(FileIO.toPath(dest.toPath)).map { _ =>
+ acc.addFile(fileInfo, dest)
+ }
+ }.getOrElse(discard(part))
+ } getOrElse {
+ part.entity match {
+ case HttpEntity.Strict(ct: ContentType.NonBinary, data) =>
+ val charsetName = ct.charset.nioCharset.name
+ val partContent = data.decodeString(charsetName)
+
+ Future.successful(acc.addForm(part.name, partContent))
+ case _ =>
+ discard(part)
+ }
+ }
+ }
+
+ val uploadedF = formData.parts.runWith(uploadingSink)
+
+ onSuccess(uploadedF)
+ }
+ }
+}
+
+object MultipartDirectives extends MultipartDirectives with FileUploadDirectives {
+ val tempFileFromFileInfo: FileInfo => File = {
+ file: FileInfo => File.createTempFile(file.fileName, ".tmp")
+ }
+}
+
+final case class FileField(fieldName: String, fileNameF: FileInfo => File = MultipartDirectives.tempFileFromFileInfo)
+
+final case class PartsAndFiles(form: immutable.Map[String, String], files: Map[String, (FileInfo, File)]) {
+ def addForm(fieldName: String, content: String): PartsAndFiles = this.copy(form.updated(fieldName, content))
+
+ def addFile(info: FileInfo, file: File): PartsAndFiles = this.copy(
+ files = files.updated(info.fieldName, (info, file))
+ )
+}
+
+object PartsAndFiles {
+ val Empty: PartsAndFiles = PartsAndFiles(immutable.Map.empty, immutable.Map.empty)
+}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/scala-akka-http-server/noMultipart.mustache b/modules/openapi-generator/src/main/resources/scala-akka-http-server/noMultipart.mustache
new file mode 100644
index 000000000000..a4c25bb27d2d
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/scala-akka-http-server/noMultipart.mustache
@@ -0,0 +1,7 @@
+{{^formParams.isEmpty}}
+
+ formFields({{#formParams}}"{{baseName}}".as[{{#isPrimitiveType}}{{dataType}}{{/isPrimitiveType}}{{^isPrimitiveType}}String{{/isPrimitiveType}}]{{^required}}.?{{#vendorExtensions.hasDefaultValue}}({{defaultValue}}){{/vendorExtensions.hasDefaultValue}}{{/required}}{{#hasMore}}, {{/hasMore}}{{/formParams}}) { ({{#formParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/formParams}}) =>{{/formParams.isEmpty}}
+ {{#bodyParam}}{{^isPrimitiveType}}entity(as[{{dataType}}]){ {{paramName}} =>
+ {{/isPrimitiveType}}{{/bodyParam}}{{classVarName}}Service.{{operationId}}({{#allParams}}{{paramName}} = {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}){{#bodyParam}}{{^isPrimitiveType}}
+ }{{/isPrimitiveType}}{{/bodyParam}}{{^formParams.isEmpty}}
+ }{{/formParams.isEmpty}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/scala-akka-http-server/operationParam.mustache b/modules/openapi-generator/src/main/resources/scala-akka-http-server/operationParam.mustache
new file mode 100644
index 000000000000..7e5846f3b882
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/scala-akka-http-server/operationParam.mustache
@@ -0,0 +1 @@
+{{#allParams}}{{paramName}}: {{#isFile}}(FileInfo, File){{/isFile}}{{^isFile}}{{^required}}{{^vendorExtensions.hasDefaultValue}}Option[{{/vendorExtensions.hasDefaultValue}}{{/required}}{{dataType}}{{^required}}{{^vendorExtensions.hasDefaultValue}}]{{/vendorExtensions.hasDefaultValue}}{{/required}}{{/isFile}}{{#hasMore}}, {{/hasMore}}{{/allParams}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/scala-akka-http-server/stringDirectives.mustache b/modules/openapi-generator/src/main/resources/scala-akka-http-server/stringDirectives.mustache
new file mode 100644
index 000000000000..5640a9d45490
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/scala-akka-http-server/stringDirectives.mustache
@@ -0,0 +1,127 @@
+package {{invokerPackage}}
+
+import akka.http.scaladsl.common._
+import akka.http.scaladsl.server.{Directive, Directive0, Directive1, InvalidRequiredValueForQueryParamRejection, MalformedFormFieldRejection, MissingFormFieldRejection, MissingQueryParamRejection, UnsupportedRequestContentTypeRejection}
+import akka.http.scaladsl.server.directives.BasicDirectives
+import akka.http.scaladsl.unmarshalling.Unmarshaller.UnsupportedContentTypeException
+
+import scala.concurrent.Future
+import scala.util.{Failure, Success}
+
+trait StringDirectives {
+ implicit def _symbol2NR(symbol: Symbol): NameReceptacle[String] = new NameReceptacle[String](symbol.name)
+ implicit def _string2NR(string: String): NameReceptacle[String] = new NameReceptacle[String](string)
+
+ import StringDirectives._
+ type StringValueProvider = Map[String, String]
+
+ def stringField(pdm: StringMagnet): pdm.Out = pdm()
+
+ def stringFields(pdm: StringMagnet): pdm.Out = pdm()
+
+}
+
+object StringDirectives extends StringDirectives {
+
+ sealed trait StringMagnet {
+ type Out
+ def apply(): Out
+ }
+ object StringMagnet {
+ implicit def apply[T](value: T)(implicit sdef: StringDef[T]): StringMagnet { type Out = sdef.Out } =
+ new StringMagnet {
+ type Out = sdef.Out
+ def apply(): sdef.Out = sdef(value)
+ }
+ }
+
+ type StringDefAux[A, B] = StringDef[A] { type Out = B }
+ sealed trait StringDef[T] {
+ type Out
+ def apply(value: T): Out
+ }
+ object StringDef {
+ protected def stringDef[A, B](f: A => B): StringDefAux[A, B] =
+ new StringDef[A] {
+ type Out = B
+
+ def apply(value: A): B = f(value)
+ }
+
+ import akka.http.scaladsl.server.directives.BasicDirectives._
+ import akka.http.scaladsl.server.directives.FutureDirectives._
+ import akka.http.scaladsl.server.directives.RouteDirectives._
+ import akka.http.scaladsl.unmarshalling._
+
+ type FSU[T] = FromStringUnmarshaller[T]
+ type FSOU[T] = Unmarshaller[Option[String], T]
+ type SFVP = StringValueProvider
+
+ protected def extractField[A, B](f: A => Directive1[B]): StringDefAux[A, Directive1[B]] = stringDef(f)
+
+ protected def handleFieldResult[T](fieldName: String, result: Future[T]): Directive1[T] = onComplete(result).flatMap {
+ case Success(x) => provide(x)
+ case Failure(Unmarshaller.NoContentException) => reject(MissingFormFieldRejection(fieldName)){{#akkaHttp10_1_10_plus}}
+ case Failure(x: UnsupportedContentTypeException) => reject(UnsupportedRequestContentTypeRejection(x.supported, x.actualContentType)){{/akkaHttp10_1_10_plus}}{{^akkaHttp10_1_10_plus}}
+ case Failure(x: UnsupportedContentTypeException) => reject(UnsupportedRequestContentTypeRejection(x.supported)){{/akkaHttp10_1_10_plus}}
+ case Failure(x) => reject(MalformedFormFieldRejection(fieldName, if (x.getMessage == null) "" else x.getMessage, Option(x.getCause)))
+ }
+
+ private def filter[T](paramName: String, fsou: FSOU[T])(implicit vp: SFVP): Directive1[T] = {
+ extract { ctx =>
+ import ctx.{executionContext, materializer}
+ handleFieldResult(paramName, fsou(vp.get(paramName)))
+ }.flatMap(identity)
+ }
+
+ implicit def forString(implicit fsu: FSU[String], vp: SFVP): StringDefAux[String, Directive1[String]] =
+ extractField[String, String] { string => filter(string, fsu) }
+ implicit def forSymbol(implicit fsu: FSU[String], vp: SFVP): StringDefAux[Symbol, Directive1[String]] =
+ extractField[Symbol, String] { symbol => filter(symbol.name, fsu) }
+ implicit def forNR[T](implicit fsu: FSU[T], vp: SFVP): StringDefAux[NameReceptacle[T], Directive1[T]] =
+ extractField[NameReceptacle[T], T] { nr => filter(nr.name, fsu) }
+ implicit def forNUR[T](implicit vp: SFVP): StringDefAux[NameUnmarshallerReceptacle[T], Directive1[T]] =
+ extractField[NameUnmarshallerReceptacle[T], T] { nr => filter(nr.name, nr.um) }
+ implicit def forNOR[T](implicit fsou: FSOU[T], vp: SFVP): StringDefAux[NameOptionReceptacle[T], Directive1[Option[T]]] =
+ extractField[NameOptionReceptacle[T], Option[T]] { nr => filter[Option[T]](nr.name, fsou) }
+ implicit def forNDR[T](implicit fsou: FSOU[T], vp: SFVP): StringDefAux[NameDefaultReceptacle[T], Directive1[T]] =
+ extractField[NameDefaultReceptacle[T], T] { nr => filter[T](nr.name, fsou withDefaultValue nr.default) }
+ implicit def forNOUR[T](implicit vp: SFVP): StringDefAux[NameOptionUnmarshallerReceptacle[T], Directive1[Option[T]]] =
+ extractField[NameOptionUnmarshallerReceptacle[T], Option[T]] { nr => filter(nr.name, nr.um: FSOU[T]) }
+ implicit def forNDUR[T](implicit vp: SFVP): StringDefAux[NameDefaultUnmarshallerReceptacle[T], Directive1[T]] =
+ extractField[NameDefaultUnmarshallerReceptacle[T], T] { nr => filter[T](nr.name, (nr.um: FSOU[T]) withDefaultValue nr.default) }
+
+ //////////////////// required parameter support ////////////////////
+
+ private def requiredFilter[T](paramName: String, fsou: FSOU[T], requiredValue: Any)(implicit vp: SFVP): Directive0 = {
+ extract { ctx =>
+ import ctx.{executionContext, materializer}
+ onComplete(fsou(vp.get(paramName))) flatMap {
+ case Success(value) if value == requiredValue => pass
+ case Success(value) => reject(InvalidRequiredValueForQueryParamRejection(paramName, requiredValue.toString, value.toString)).toDirective[Unit]
+ case _ => reject(MissingQueryParamRejection(paramName)).toDirective[Unit]
+ }
+ }.flatMap(identity)
+ }
+
+ implicit def forRVR[T](implicit fsu: FSU[T], vp: SFVP): StringDefAux[RequiredValueReceptacle[T], Directive0] =
+ stringDef[RequiredValueReceptacle[T], Directive0] { rvr => requiredFilter(rvr.name, fsu, rvr.requiredValue) }
+
+ implicit def forRVDR[T](implicit vp: SFVP): StringDefAux[RequiredValueUnmarshallerReceptacle[T], Directive0] =
+ stringDef[RequiredValueUnmarshallerReceptacle[T], Directive0] { rvr => requiredFilter(rvr.name, rvr.um, rvr.requiredValue) }
+
+ //////////////////// tuple support ////////////////////
+
+ import akka.http.scaladsl.server.util.BinaryPolyFunc
+ import akka.http.scaladsl.server.util.TupleOps._
+
+ implicit def forTuple[T](implicit fold: FoldLeft[Directive0, T, ConvertStringDefAndConcatenate.type]): StringDefAux[T, fold.Out] =
+ stringDef[T, fold.Out](fold(BasicDirectives.pass, _))
+
+ object ConvertStringDefAndConcatenate extends BinaryPolyFunc {
+ implicit def from[P, TA, TB](implicit sdef: StringDef[P] {type Out = Directive[TB]}, ev: Join[TA, TB]): BinaryPolyFunc.Case[Directive[TA], P, ConvertStringDefAndConcatenate.type] {type Out = Directive[ev.Out]} =
+ at[Directive[TA], P] { (a, t) => a & sdef(t) }
+ }
+
+ }
+}
diff --git a/samples/server/petstore/scala-akka-http/.openapi-generator-ignore b/samples/server/petstore/scala-akka-http/.openapi-generator-ignore
new file mode 100644
index 000000000000..7484ee590a38
--- /dev/null
+++ b/samples/server/petstore/scala-akka-http/.openapi-generator-ignore
@@ -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
diff --git a/samples/server/petstore/scala-akka-http/.openapi-generator/VERSION b/samples/server/petstore/scala-akka-http/.openapi-generator/VERSION
new file mode 100644
index 000000000000..b5d898602c2c
--- /dev/null
+++ b/samples/server/petstore/scala-akka-http/.openapi-generator/VERSION
@@ -0,0 +1 @@
+4.3.1-SNAPSHOT
\ No newline at end of file
diff --git a/samples/server/petstore/scala-akka-http/README.md b/samples/server/petstore/scala-akka-http/README.md
new file mode 100644
index 000000000000..a1c3b50c09b2
--- /dev/null
+++ b/samples/server/petstore/scala-akka-http/README.md
@@ -0,0 +1,54 @@
+# OpenAPI Petstore
+
+This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
+
+
+ ## API
+
+ ### Pet
+
+ |Name|Role|
+ |----|----|
+ |`org.openapitools.server.api.PetController`|akka-http API controller|
+ |`org.openapitools.server.api.PetApi`|Representing trait|
+ |`org.openapitools.server.api.PetApiImpl`|Default implementation|
+
+ * `POST /v2/pet` - Add a new pet to the store
+ * `DELETE /v2/pet/{petId}` - Deletes a pet
+ * `GET /v2/pet/findByStatus?status=[value]` - Finds Pets by status
+ * `GET /v2/pet/findByTags?tags=[value]` - Finds Pets by tags
+ * `GET /v2/pet/{petId}` - Find pet by ID
+ * `PUT /v2/pet` - Update an existing pet
+ * `POST /v2/pet/{petId}` - Updates a pet in the store with form data
+ * `POST /v2/pet/{petId}/uploadImage` - uploads an image
+
+ ### Store
+
+ |Name|Role|
+ |----|----|
+ |`org.openapitools.server.api.StoreController`|akka-http API controller|
+ |`org.openapitools.server.api.StoreApi`|Representing trait|
+ |`org.openapitools.server.api.StoreApiImpl`|Default implementation|
+
+ * `DELETE /v2/store/order/{orderId}` - Delete purchase order by ID
+ * `GET /v2/store/inventory` - Returns pet inventories by status
+ * `GET /v2/store/order/{orderId}` - Find purchase order by ID
+ * `POST /v2/store/order` - Place an order for a pet
+
+ ### User
+
+ |Name|Role|
+ |----|----|
+ |`org.openapitools.server.api.UserController`|akka-http API controller|
+ |`org.openapitools.server.api.UserApi`|Representing trait|
+ |`org.openapitools.server.api.UserApiImpl`|Default implementation|
+
+ * `POST /v2/user` - Create user
+ * `POST /v2/user/createWithArray` - Creates list of users with given input array
+ * `POST /v2/user/createWithList` - Creates list of users with given input array
+ * `DELETE /v2/user/{username}` - Delete user
+ * `GET /v2/user/{username}` - Get user by user name
+ * `GET /v2/user/login?username=[value]&password=[value]` - Logs user into the system
+ * `GET /v2/user/logout` - Logs out current logged in user session
+ * `PUT /v2/user/{username}` - Updated user
+
diff --git a/samples/server/petstore/scala-akka-http/build.sbt b/samples/server/petstore/scala-akka-http/build.sbt
new file mode 100644
index 000000000000..1099fe43c457
--- /dev/null
+++ b/samples/server/petstore/scala-akka-http/build.sbt
@@ -0,0 +1,9 @@
+version := "1.0.0"
+name := "scala-akka-http-petstore-server"
+organization := "org.openapitools"
+scalaVersion := "2.12.8"
+
+libraryDependencies ++= Seq(
+ "com.typesafe.akka" %% "akka-stream" % "2.5.21",
+ "com.typesafe.akka" %% "akka-http" % "10.1.10"
+)
diff --git a/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/AkkaHttpHelper.scala b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/AkkaHttpHelper.scala
new file mode 100644
index 000000000000..b35110e91f5c
--- /dev/null
+++ b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/AkkaHttpHelper.scala
@@ -0,0 +1,34 @@
+package org.openapitools.server
+
+import akka.http.scaladsl.server.Directives._
+import akka.http.scaladsl.server.{PathMatcher, PathMatcher1}
+import scala.util.{Failure, Success, Try}
+import scala.util.control.NoStackTrace
+
+object AkkaHttpHelper {
+ def optToTry[T](opt: Option[T], err: => String): Try[T] =
+ opt.map[Try[T]](Success(_)) getOrElse Failure(new RuntimeException(err) with NoStackTrace)
+
+ /**
+ * A PathMatcher that matches and extracts a Float value. The matched string representation is the pure decimal,
+ * optionally signed form of a float value, i.e. without exponent.
+ *
+ * @group pathmatcher
+ */
+ val FloatNumber: PathMatcher1[Float] =
+ PathMatcher("""[+-]?\d*\.?\d*""".r) flatMap { string =>
+ try Some(java.lang.Float.parseFloat(string))
+ catch { case _: NumberFormatException => None }
+ }
+
+ /**
+ * A PathMatcher that matches and extracts a Boolean value.
+ *
+ * @group pathmatcher
+ */
+ val Boolean: PathMatcher1[Boolean] =
+ Segment.flatMap { string =>
+ try Some(string.toBoolean)
+ catch { case _: IllegalArgumentException => None }
+ }
+}
diff --git a/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/Controller.scala b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/Controller.scala
new file mode 100644
index 000000000000..8cfc986a0aa4
--- /dev/null
+++ b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/Controller.scala
@@ -0,0 +1,18 @@
+package org.openapitools.server
+
+import akka.http.scaladsl.Http
+import akka.http.scaladsl.server.Route
+import org.openapitools.server.api.PetApi
+import org.openapitools.server.api.StoreApi
+import org.openapitools.server.api.UserApi
+
+import akka.http.scaladsl.server.Directives._
+import akka.actor.ActorSystem
+import akka.stream.ActorMaterializer
+
+class Controller(pet: PetApi, store: StoreApi, user: UserApi)(implicit system: ActorSystem, materializer: ActorMaterializer) {
+
+ lazy val routes: Route = pet.route ~ store.route ~ user.route
+
+ Http().bindAndHandle(routes, "0.0.0.0", 9000)
+}
\ No newline at end of file
diff --git a/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/MultipartDirectives.scala b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/MultipartDirectives.scala
new file mode 100644
index 000000000000..79891d7095a8
--- /dev/null
+++ b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/MultipartDirectives.scala
@@ -0,0 +1,88 @@
+package org.openapitools.server
+
+import java.io.File
+
+import akka.annotation.ApiMayChange
+import akka.http.scaladsl.model.Multipart.FormData
+import akka.http.scaladsl.model.{ContentType, HttpEntity, Multipart}
+import akka.http.scaladsl.server.Directive1
+import akka.http.scaladsl.server.directives._
+import akka.stream.Materializer
+import akka.stream.scaladsl._
+
+import scala.collection.immutable
+import scala.concurrent.{ExecutionContextExecutor, Future}
+
+trait MultipartDirectives {
+
+ import akka.http.scaladsl.server.directives.BasicDirectives._
+ import akka.http.scaladsl.server.directives.FutureDirectives._
+ import akka.http.scaladsl.server.directives.MarshallingDirectives._
+
+ @ApiMayChange
+ def formAndFiles(fileFields: FileField*): Directive1[PartsAndFiles] =
+ entity(as[Multipart.FormData]).flatMap {
+ formData =>
+ extractRequestContext.flatMap { ctx =>
+ implicit val mat: Materializer = ctx.materializer
+ implicit val ec: ExecutionContextExecutor = ctx.executionContext
+
+ val uploadingSink: Sink[FormData.BodyPart, Future[PartsAndFiles]] =
+ Sink.foldAsync[PartsAndFiles, Multipart.FormData.BodyPart](PartsAndFiles.Empty) {
+ (acc, part) =>
+ def discard(p: Multipart.FormData.BodyPart): Future[PartsAndFiles] = {
+ p.entity.discardBytes()
+ Future.successful(acc)
+ }
+
+ part.filename.map {
+ fileName =>
+ fileFields.find(_.fieldName == part.name)
+ .map {
+ case FileField(_, destFn) =>
+ val fileInfo = FileInfo(part.name, fileName, part.entity.contentType)
+ val dest = destFn(fileInfo)
+
+ part.entity.dataBytes.runWith(FileIO.toPath(dest.toPath)).map { _ =>
+ acc.addFile(fileInfo, dest)
+ }
+ }.getOrElse(discard(part))
+ } getOrElse {
+ part.entity match {
+ case HttpEntity.Strict(ct: ContentType.NonBinary, data) =>
+ val charsetName = ct.charset.nioCharset.name
+ val partContent = data.decodeString(charsetName)
+
+ Future.successful(acc.addForm(part.name, partContent))
+ case _ =>
+ discard(part)
+ }
+ }
+ }
+
+ val uploadedF = formData.parts.runWith(uploadingSink)
+
+ onSuccess(uploadedF)
+ }
+ }
+}
+
+object MultipartDirectives extends MultipartDirectives with FileUploadDirectives {
+ val tempFileFromFileInfo: FileInfo => File = {
+ file: FileInfo => File.createTempFile(file.fileName, ".tmp")
+ }
+}
+
+final case class FileField(fieldName: String, fileNameF: FileInfo => File = MultipartDirectives.tempFileFromFileInfo)
+
+final case class PartsAndFiles(form: immutable.Map[String, String], files: Map[String, (FileInfo, File)]) {
+ def addForm(fieldName: String, content: String): PartsAndFiles = this.copy(form.updated(fieldName, content))
+
+ def addFile(info: FileInfo, file: File): PartsAndFiles = this.copy(
+ files = files.updated(info.fieldName, (info, file))
+ )
+}
+
+object PartsAndFiles {
+ val Empty: PartsAndFiles = PartsAndFiles(immutable.Map.empty, immutable.Map.empty)
+}
\ No newline at end of file
diff --git a/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/StringDirectives.scala b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/StringDirectives.scala
new file mode 100644
index 000000000000..2d115849056e
--- /dev/null
+++ b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/StringDirectives.scala
@@ -0,0 +1,126 @@
+package org.openapitools.server
+
+import akka.http.scaladsl.common._
+import akka.http.scaladsl.server.{Directive, Directive0, Directive1, InvalidRequiredValueForQueryParamRejection, MalformedFormFieldRejection, MissingFormFieldRejection, MissingQueryParamRejection, UnsupportedRequestContentTypeRejection}
+import akka.http.scaladsl.server.directives.BasicDirectives
+import akka.http.scaladsl.unmarshalling.Unmarshaller.UnsupportedContentTypeException
+
+import scala.concurrent.Future
+import scala.util.{Failure, Success}
+
+trait StringDirectives {
+ implicit def _symbol2NR(symbol: Symbol): NameReceptacle[String] = new NameReceptacle[String](symbol.name)
+ implicit def _string2NR(string: String): NameReceptacle[String] = new NameReceptacle[String](string)
+
+ import StringDirectives._
+ type StringValueProvider = Map[String, String]
+
+ def stringField(pdm: StringMagnet): pdm.Out = pdm()
+
+ def stringFields(pdm: StringMagnet): pdm.Out = pdm()
+
+}
+
+object StringDirectives extends StringDirectives {
+
+ sealed trait StringMagnet {
+ type Out
+ def apply(): Out
+ }
+ object StringMagnet {
+ implicit def apply[T](value: T)(implicit sdef: StringDef[T]): StringMagnet { type Out = sdef.Out } =
+ new StringMagnet {
+ type Out = sdef.Out
+ def apply(): sdef.Out = sdef(value)
+ }
+ }
+
+ type StringDefAux[A, B] = StringDef[A] { type Out = B }
+ sealed trait StringDef[T] {
+ type Out
+ def apply(value: T): Out
+ }
+ object StringDef {
+ protected def stringDef[A, B](f: A => B): StringDefAux[A, B] =
+ new StringDef[A] {
+ type Out = B
+
+ def apply(value: A): B = f(value)
+ }
+
+ import akka.http.scaladsl.server.directives.BasicDirectives._
+ import akka.http.scaladsl.server.directives.FutureDirectives._
+ import akka.http.scaladsl.server.directives.RouteDirectives._
+ import akka.http.scaladsl.unmarshalling._
+
+ type FSU[T] = FromStringUnmarshaller[T]
+ type FSOU[T] = Unmarshaller[Option[String], T]
+ type SFVP = StringValueProvider
+
+ protected def extractField[A, B](f: A => Directive1[B]): StringDefAux[A, Directive1[B]] = stringDef(f)
+
+ protected def handleFieldResult[T](fieldName: String, result: Future[T]): Directive1[T] = onComplete(result).flatMap {
+ case Success(x) => provide(x)
+ case Failure(Unmarshaller.NoContentException) => reject(MissingFormFieldRejection(fieldName))
+ case Failure(x: UnsupportedContentTypeException) => reject(UnsupportedRequestContentTypeRejection(x.supported, x.actualContentType))
+ case Failure(x) => reject(MalformedFormFieldRejection(fieldName, if (x.getMessage == null) "" else x.getMessage, Option(x.getCause)))
+ }
+
+ private def filter[T](paramName: String, fsou: FSOU[T])(implicit vp: SFVP): Directive1[T] = {
+ extract { ctx =>
+ import ctx.{executionContext, materializer}
+ handleFieldResult(paramName, fsou(vp.get(paramName)))
+ }.flatMap(identity)
+ }
+
+ implicit def forString(implicit fsu: FSU[String], vp: SFVP): StringDefAux[String, Directive1[String]] =
+ extractField[String, String] { string => filter(string, fsu) }
+ implicit def forSymbol(implicit fsu: FSU[String], vp: SFVP): StringDefAux[Symbol, Directive1[String]] =
+ extractField[Symbol, String] { symbol => filter(symbol.name, fsu) }
+ implicit def forNR[T](implicit fsu: FSU[T], vp: SFVP): StringDefAux[NameReceptacle[T], Directive1[T]] =
+ extractField[NameReceptacle[T], T] { nr => filter(nr.name, fsu) }
+ implicit def forNUR[T](implicit vp: SFVP): StringDefAux[NameUnmarshallerReceptacle[T], Directive1[T]] =
+ extractField[NameUnmarshallerReceptacle[T], T] { nr => filter(nr.name, nr.um) }
+ implicit def forNOR[T](implicit fsou: FSOU[T], vp: SFVP): StringDefAux[NameOptionReceptacle[T], Directive1[Option[T]]] =
+ extractField[NameOptionReceptacle[T], Option[T]] { nr => filter[Option[T]](nr.name, fsou) }
+ implicit def forNDR[T](implicit fsou: FSOU[T], vp: SFVP): StringDefAux[NameDefaultReceptacle[T], Directive1[T]] =
+ extractField[NameDefaultReceptacle[T], T] { nr => filter[T](nr.name, fsou withDefaultValue nr.default) }
+ implicit def forNOUR[T](implicit vp: SFVP): StringDefAux[NameOptionUnmarshallerReceptacle[T], Directive1[Option[T]]] =
+ extractField[NameOptionUnmarshallerReceptacle[T], Option[T]] { nr => filter(nr.name, nr.um: FSOU[T]) }
+ implicit def forNDUR[T](implicit vp: SFVP): StringDefAux[NameDefaultUnmarshallerReceptacle[T], Directive1[T]] =
+ extractField[NameDefaultUnmarshallerReceptacle[T], T] { nr => filter[T](nr.name, (nr.um: FSOU[T]) withDefaultValue nr.default) }
+
+ //////////////////// required parameter support ////////////////////
+
+ private def requiredFilter[T](paramName: String, fsou: FSOU[T], requiredValue: Any)(implicit vp: SFVP): Directive0 = {
+ extract { ctx =>
+ import ctx.{executionContext, materializer}
+ onComplete(fsou(vp.get(paramName))) flatMap {
+ case Success(value) if value == requiredValue => pass
+ case Success(value) => reject(InvalidRequiredValueForQueryParamRejection(paramName, requiredValue.toString, value.toString)).toDirective[Unit]
+ case _ => reject(MissingQueryParamRejection(paramName)).toDirective[Unit]
+ }
+ }.flatMap(identity)
+ }
+
+ implicit def forRVR[T](implicit fsu: FSU[T], vp: SFVP): StringDefAux[RequiredValueReceptacle[T], Directive0] =
+ stringDef[RequiredValueReceptacle[T], Directive0] { rvr => requiredFilter(rvr.name, fsu, rvr.requiredValue) }
+
+ implicit def forRVDR[T](implicit vp: SFVP): StringDefAux[RequiredValueUnmarshallerReceptacle[T], Directive0] =
+ stringDef[RequiredValueUnmarshallerReceptacle[T], Directive0] { rvr => requiredFilter(rvr.name, rvr.um, rvr.requiredValue) }
+
+ //////////////////// tuple support ////////////////////
+
+ import akka.http.scaladsl.server.util.BinaryPolyFunc
+ import akka.http.scaladsl.server.util.TupleOps._
+
+ implicit def forTuple[T](implicit fold: FoldLeft[Directive0, T, ConvertStringDefAndConcatenate.type]): StringDefAux[T, fold.Out] =
+ stringDef[T, fold.Out](fold(BasicDirectives.pass, _))
+
+ object ConvertStringDefAndConcatenate extends BinaryPolyFunc {
+ implicit def from[P, TA, TB](implicit sdef: StringDef[P] {type Out = Directive[TB]}, ev: Join[TA, TB]): BinaryPolyFunc.Case[Directive[TA], P, ConvertStringDefAndConcatenate.type] {type Out = Directive[ev.Out]} =
+ at[Directive[TA], P] { (a, t) => a & sdef(t) }
+ }
+
+ }
+}
diff --git a/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/api/PetApi.scala b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/api/PetApi.scala
new file mode 100644
index 000000000000..a78bb32e9a58
--- /dev/null
+++ b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/api/PetApi.scala
@@ -0,0 +1,189 @@
+package org.openapitools.server.api
+
+import akka.http.scaladsl.server.Directives._
+import akka.http.scaladsl.server.Route
+import akka.http.scaladsl.marshalling.ToEntityMarshaller
+import akka.http.scaladsl.unmarshalling.FromEntityUnmarshaller
+import akka.http.scaladsl.unmarshalling.FromStringUnmarshaller
+import org.openapitools.server.AkkaHttpHelper._
+import org.openapitools.server.StringDirectives
+import org.openapitools.server.MultipartDirectives
+import org.openapitools.server.FileField
+import org.openapitools.server.PartsAndFiles
+import org.openapitools.server.model.ApiResponse
+import java.io.File
+import org.openapitools.server.model.Pet
+import scala.util.Try
+import akka.http.scaladsl.server.MalformedRequestContentRejection
+import akka.http.scaladsl.server.directives.FileInfo
+
+
+class PetApi(
+ petService: PetApiService,
+ petMarshaller: PetApiMarshaller
+) extends MultipartDirectives with StringDirectives {
+
+
+ import petMarshaller._
+
+ lazy val route: Route =
+ path("pet") {
+ post {
+ entity(as[Pet]){ body =>
+ petService.addPet(body = body)
+ }
+ }
+ } ~
+ path("pet" / LongNumber) { (petId) =>
+ delete {
+ optionalHeaderValueByName("api_key") { apiKey =>
+ petService.deletePet(petId = petId, apiKey = apiKey)
+ }
+ }
+ } ~
+ path("pet" / "findByStatus") {
+ get {
+ parameters("status".as[String]) { (status) =>
+ petService.findPetsByStatus(status = status)
+ }
+ }
+ } ~
+ path("pet" / "findByTags") {
+ get {
+ parameters("tags".as[String]) { (tags) =>
+ petService.findPetsByTags(tags = tags)
+ }
+ }
+ } ~
+ path("pet" / LongNumber) { (petId) =>
+ get {
+ petService.getPetById(petId = petId)
+ }
+ } ~
+ path("pet") {
+ put {
+ entity(as[Pet]){ body =>
+ petService.updatePet(body = body)
+ }
+ }
+ } ~
+ path("pet" / LongNumber) { (petId) =>
+ post {
+ formFields("name".as[String].?, "status".as[String].?) { (name, status) =>
+ petService.updatePetWithForm(petId = petId, name = name, status = status)
+ }
+ }
+ } ~
+ path("pet" / LongNumber / "uploadImage") { (petId) =>
+ post {
+ formAndFiles(FileField("file")) { partsAndFiles =>
+ val _____ : Try[Route] = for {
+ file <- optToTry(partsAndFiles.files.get("file"), s"File file missing")
+ } yield {
+ implicit val vp: StringValueProvider = partsAndFiles.form
+ stringFields("additionalMetadata".as[String].?) { (additionalMetadata) =>
+ petService.uploadFile(petId = petId, additionalMetadata = additionalMetadata, file = file)
+ }
+ }
+ _____.fold[Route](t => reject(MalformedRequestContentRejection("Missing file.", t)), identity)
+ }
+ }
+ }
+}
+
+
+trait PetApiService {
+
+ def addPet405: Route =
+ complete((405, "Invalid input"))
+ /**
+ * Code: 405, Message: Invalid input
+ */
+ def addPet(body: Pet): Route
+
+ def deletePet400: Route =
+ complete((400, "Invalid pet value"))
+ /**
+ * Code: 400, Message: Invalid pet value
+ */
+ def deletePet(petId: Long, apiKey: Option[String]): Route
+
+ def findPetsByStatus200(responsePetarray: Seq[Pet])(implicit toEntityMarshallerPetarray: ToEntityMarshaller[Seq[Pet]]): Route =
+ complete((200, responsePetarray))
+ def findPetsByStatus400: Route =
+ complete((400, "Invalid status value"))
+ /**
+ * Code: 200, Message: successful operation, DataType: Seq[Pet]
+ * Code: 400, Message: Invalid status value
+ */
+ def findPetsByStatus(status: String)
+ (implicit toEntityMarshallerPetarray: ToEntityMarshaller[Seq[Pet]]): Route
+
+ def findPetsByTags200(responsePetarray: Seq[Pet])(implicit toEntityMarshallerPetarray: ToEntityMarshaller[Seq[Pet]]): Route =
+ complete((200, responsePetarray))
+ def findPetsByTags400: Route =
+ complete((400, "Invalid tag value"))
+ /**
+ * Code: 200, Message: successful operation, DataType: Seq[Pet]
+ * Code: 400, Message: Invalid tag value
+ */
+ def findPetsByTags(tags: String)
+ (implicit toEntityMarshallerPetarray: ToEntityMarshaller[Seq[Pet]]): Route
+
+ def getPetById200(responsePet: Pet)(implicit toEntityMarshallerPet: ToEntityMarshaller[Pet]): Route =
+ complete((200, responsePet))
+ def getPetById400: Route =
+ complete((400, "Invalid ID supplied"))
+ def getPetById404: Route =
+ complete((404, "Pet not found"))
+ /**
+ * Code: 200, Message: successful operation, DataType: Pet
+ * Code: 400, Message: Invalid ID supplied
+ * Code: 404, Message: Pet not found
+ */
+ def getPetById(petId: Long)
+ (implicit toEntityMarshallerPet: ToEntityMarshaller[Pet]): Route
+
+ def updatePet400: Route =
+ complete((400, "Invalid ID supplied"))
+ def updatePet404: Route =
+ complete((404, "Pet not found"))
+ def updatePet405: Route =
+ complete((405, "Validation exception"))
+ /**
+ * Code: 400, Message: Invalid ID supplied
+ * Code: 404, Message: Pet not found
+ * Code: 405, Message: Validation exception
+ */
+ def updatePet(body: Pet): Route
+
+ def updatePetWithForm405: Route =
+ complete((405, "Invalid input"))
+ /**
+ * Code: 405, Message: Invalid input
+ */
+ def updatePetWithForm(petId: Long, name: Option[String], status: Option[String]): Route
+
+ def uploadFile200(responseApiResponse: ApiResponse)(implicit toEntityMarshallerApiResponse: ToEntityMarshaller[ApiResponse]): Route =
+ complete((200, responseApiResponse))
+ /**
+ * Code: 200, Message: successful operation, DataType: ApiResponse
+ */
+ def uploadFile(petId: Long, additionalMetadata: Option[String], file: (FileInfo, File))
+ (implicit toEntityMarshallerApiResponse: ToEntityMarshaller[ApiResponse]): Route
+
+}
+
+trait PetApiMarshaller {
+ implicit def fromEntityUnmarshallerPet: FromEntityUnmarshaller[Pet]
+
+
+
+ implicit def toEntityMarshallerPetarray: ToEntityMarshaller[Seq[Pet]]
+
+ implicit def toEntityMarshallerPet: ToEntityMarshaller[Pet]
+
+ implicit def toEntityMarshallerApiResponse: ToEntityMarshaller[ApiResponse]
+
+}
+
diff --git a/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/api/StoreApi.scala b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/api/StoreApi.scala
new file mode 100644
index 000000000000..a7bfdc650129
--- /dev/null
+++ b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/api/StoreApi.scala
@@ -0,0 +1,100 @@
+package org.openapitools.server.api
+
+import akka.http.scaladsl.server.Directives._
+import akka.http.scaladsl.server.Route
+import akka.http.scaladsl.marshalling.ToEntityMarshaller
+import akka.http.scaladsl.unmarshalling.FromEntityUnmarshaller
+import akka.http.scaladsl.unmarshalling.FromStringUnmarshaller
+import org.openapitools.server.AkkaHttpHelper._
+import org.openapitools.server.model.Order
+
+
+class StoreApi(
+ storeService: StoreApiService,
+ storeMarshaller: StoreApiMarshaller
+) {
+
+
+ import storeMarshaller._
+
+ lazy val route: Route =
+ path("store" / "order" / Segment) { (orderId) =>
+ delete {
+ storeService.deleteOrder(orderId = orderId)
+ }
+ } ~
+ path("store" / "inventory") {
+ get {
+ storeService.getInventory()
+ }
+ } ~
+ path("store" / "order" / LongNumber) { (orderId) =>
+ get {
+ storeService.getOrderById(orderId = orderId)
+ }
+ } ~
+ path("store" / "order") {
+ post {
+ entity(as[Order]){ body =>
+ storeService.placeOrder(body = body)
+ }
+ }
+ }
+}
+
+
+trait StoreApiService {
+
+ def deleteOrder400: Route =
+ complete((400, "Invalid ID supplied"))
+ def deleteOrder404: Route =
+ complete((404, "Order not found"))
+ /**
+ * Code: 400, Message: Invalid ID supplied
+ * Code: 404, Message: Order not found
+ */
+ def deleteOrder(orderId: String): Route
+
+ def getInventory200(responseMapmap: Map[String, Int])(implicit toEntityMarshallerMapmap: ToEntityMarshaller[Map[String, Int]]): Route =
+ complete((200, responseMapmap))
+ /**
+ * Code: 200, Message: successful operation, DataType: Map[String, Int]
+ */
+ def getInventory(): Route
+
+ def getOrderById200(responseOrder: Order)(implicit toEntityMarshallerOrder: ToEntityMarshaller[Order]): Route =
+ complete((200, responseOrder))
+ def getOrderById400: Route =
+ complete((400, "Invalid ID supplied"))
+ def getOrderById404: Route =
+ complete((404, "Order not found"))
+ /**
+ * Code: 200, Message: successful operation, DataType: Order
+ * Code: 400, Message: Invalid ID supplied
+ * Code: 404, Message: Order not found
+ */
+ def getOrderById(orderId: Long)
+ (implicit toEntityMarshallerOrder: ToEntityMarshaller[Order]): Route
+
+ def placeOrder200(responseOrder: Order)(implicit toEntityMarshallerOrder: ToEntityMarshaller[Order]): Route =
+ complete((200, responseOrder))
+ def placeOrder400: Route =
+ complete((400, "Invalid Order"))
+ /**
+ * Code: 200, Message: successful operation, DataType: Order
+ * Code: 400, Message: Invalid Order
+ */
+ def placeOrder(body: Order)
+ (implicit toEntityMarshallerOrder: ToEntityMarshaller[Order]): Route
+
+}
+
+trait StoreApiMarshaller {
+ implicit def fromEntityUnmarshallerOrder: FromEntityUnmarshaller[Order]
+
+
+
+ implicit def toEntityMarshallerOrder: ToEntityMarshaller[Order]
+
+}
+
diff --git a/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/api/UserApi.scala b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/api/UserApi.scala
new file mode 100644
index 000000000000..0d8cdff76944
--- /dev/null
+++ b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/api/UserApi.scala
@@ -0,0 +1,160 @@
+package org.openapitools.server.api
+
+import akka.http.scaladsl.server.Directives._
+import akka.http.scaladsl.server.Route
+import akka.http.scaladsl.marshalling.ToEntityMarshaller
+import akka.http.scaladsl.unmarshalling.FromEntityUnmarshaller
+import akka.http.scaladsl.unmarshalling.FromStringUnmarshaller
+import org.openapitools.server.AkkaHttpHelper._
+import org.openapitools.server.model.User
+
+
+class UserApi(
+ userService: UserApiService,
+ userMarshaller: UserApiMarshaller
+) {
+
+
+ import userMarshaller._
+
+ lazy val route: Route =
+ path("user") {
+ post {
+ entity(as[User]){ body =>
+ userService.createUser(body = body)
+ }
+ }
+ } ~
+ path("user" / "createWithArray") {
+ post {
+ entity(as[Seq[User]]){ body =>
+ userService.createUsersWithArrayInput(body = body)
+ }
+ }
+ } ~
+ path("user" / "createWithList") {
+ post {
+ entity(as[Seq[User]]){ body =>
+ userService.createUsersWithListInput(body = body)
+ }
+ }
+ } ~
+ path("user" / Segment) { (username) =>
+ delete {
+ userService.deleteUser(username = username)
+ }
+ } ~
+ path("user" / Segment) { (username) =>
+ get {
+ userService.getUserByName(username = username)
+ }
+ } ~
+ path("user" / "login") {
+ get {
+ parameters("username".as[String], "password".as[String]) { (username, password) =>
+ userService.loginUser(username = username, password = password)
+ }
+ }
+ } ~
+ path("user" / "logout") {
+ get {
+ userService.logoutUser()
+ }
+ } ~
+ path("user" / Segment) { (username) =>
+ put {
+ entity(as[User]){ body =>
+ userService.updateUser(username = username, body = body)
+ }
+ }
+ }
+}
+
+
+trait UserApiService {
+
+ def createUserDefault(statusCode: Int): Route =
+ complete((statusCode, "successful operation"))
+ /**
+ * Code: 0, Message: successful operation
+ */
+ def createUser(body: User): Route
+
+ def createUsersWithArrayInputDefault(statusCode: Int): Route =
+ complete((statusCode, "successful operation"))
+ /**
+ * Code: 0, Message: successful operation
+ */
+ def createUsersWithArrayInput(body: Seq[User]): Route
+
+ def createUsersWithListInputDefault(statusCode: Int): Route =
+ complete((statusCode, "successful operation"))
+ /**
+ * Code: 0, Message: successful operation
+ */
+ def createUsersWithListInput(body: Seq[User]): Route
+
+ def deleteUser400: Route =
+ complete((400, "Invalid username supplied"))
+ def deleteUser404: Route =
+ complete((404, "User not found"))
+ /**
+ * Code: 400, Message: Invalid username supplied
+ * Code: 404, Message: User not found
+ */
+ def deleteUser(username: String): Route
+
+ def getUserByName200(responseUser: User)(implicit toEntityMarshallerUser: ToEntityMarshaller[User]): Route =
+ complete((200, responseUser))
+ def getUserByName400: Route =
+ complete((400, "Invalid username supplied"))
+ def getUserByName404: Route =
+ complete((404, "User not found"))
+ /**
+ * Code: 200, Message: successful operation, DataType: User
+ * Code: 400, Message: Invalid username supplied
+ * Code: 404, Message: User not found
+ */
+ def getUserByName(username: String)
+ (implicit toEntityMarshallerUser: ToEntityMarshaller[User]): Route
+
+ def loginUser200(responseString: String)(implicit toEntityMarshallerString: ToEntityMarshaller[String]): Route =
+ complete((200, responseString))
+ def loginUser400: Route =
+ complete((400, "Invalid username/password supplied"))
+ /**
+ * Code: 200, Message: successful operation, DataType: String
+ * Code: 400, Message: Invalid username/password supplied
+ */
+ def loginUser(username: String, password: String): Route
+
+ def logoutUserDefault(statusCode: Int): Route =
+ complete((statusCode, "successful operation"))
+ /**
+ * Code: 0, Message: successful operation
+ */
+ def logoutUser(): Route
+
+ def updateUser400: Route =
+ complete((400, "Invalid user supplied"))
+ def updateUser404: Route =
+ complete((404, "User not found"))
+ /**
+ * Code: 400, Message: Invalid user supplied
+ * Code: 404, Message: User not found
+ */
+ def updateUser(username: String, body: User): Route
+
+}
+
+trait UserApiMarshaller {
+ implicit def fromEntityUnmarshallerUser: FromEntityUnmarshaller[User]
+
+ implicit def fromEntityUnmarshallerUserList: FromEntityUnmarshaller[Seq[User]]
+
+
+
+ implicit def toEntityMarshallerUser: ToEntityMarshaller[User]
+
+}
+
diff --git a/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/model/ApiResponse.scala b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/model/ApiResponse.scala
new file mode 100644
index 000000000000..9091fd61fbc4
--- /dev/null
+++ b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/model/ApiResponse.scala
@@ -0,0 +1,18 @@
+package org.openapitools.server.model
+
+
+/**
+ * = An uploaded response =
+ *
+ * Describes the result of uploading an image resource
+ *
+ * @param code for example: ''null''
+ * @param `type` for example: ''null''
+ * @param message for example: ''null''
+*/
+final case class ApiResponse (
+ code: Option[Int],
+ `type`: Option[String],
+ message: Option[String]
+)
+
diff --git a/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/model/Category.scala b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/model/Category.scala
new file mode 100644
index 000000000000..cbde3f530168
--- /dev/null
+++ b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/model/Category.scala
@@ -0,0 +1,16 @@
+package org.openapitools.server.model
+
+
+/**
+ * = Pet category =
+ *
+ * A category for a pet
+ *
+ * @param id for example: ''null''
+ * @param name for example: ''null''
+*/
+final case class Category (
+ id: Option[Long],
+ name: Option[String]
+)
+
diff --git a/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/model/Order.scala b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/model/Order.scala
new file mode 100644
index 000000000000..669df7946c43
--- /dev/null
+++ b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/model/Order.scala
@@ -0,0 +1,25 @@
+package org.openapitools.server.model
+
+import java.time.OffsetDateTime
+
+/**
+ * = Pet Order =
+ *
+ * An order for a pets from the pet store
+ *
+ * @param id for example: ''null''
+ * @param petId for example: ''null''
+ * @param quantity for example: ''null''
+ * @param shipDate for example: ''null''
+ * @param status Order Status for example: ''null''
+ * @param complete for example: ''null''
+*/
+final case class Order (
+ id: Option[Long],
+ petId: Option[Long],
+ quantity: Option[Int],
+ shipDate: Option[OffsetDateTime],
+ status: Option[String],
+ complete: Option[Boolean]
+)
+
diff --git a/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/model/Pet.scala b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/model/Pet.scala
new file mode 100644
index 000000000000..4e929dbccb88
--- /dev/null
+++ b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/model/Pet.scala
@@ -0,0 +1,24 @@
+package org.openapitools.server.model
+
+
+/**
+ * = a Pet =
+ *
+ * A pet for sale in the pet store
+ *
+ * @param id for example: ''null''
+ * @param category for example: ''null''
+ * @param name for example: ''doggie''
+ * @param photoUrls for example: ''null''
+ * @param tags for example: ''null''
+ * @param status pet status in the store for example: ''null''
+*/
+final case class Pet (
+ id: Option[Long],
+ category: Option[Category],
+ name: String,
+ photoUrls: Seq[String],
+ tags: Option[Seq[Tag]],
+ status: Option[String]
+)
+
diff --git a/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/model/Tag.scala b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/model/Tag.scala
new file mode 100644
index 000000000000..3e62ea28016e
--- /dev/null
+++ b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/model/Tag.scala
@@ -0,0 +1,16 @@
+package org.openapitools.server.model
+
+
+/**
+ * = Pet Tag =
+ *
+ * A tag for a pet
+ *
+ * @param id for example: ''null''
+ * @param name for example: ''null''
+*/
+final case class Tag (
+ id: Option[Long],
+ name: Option[String]
+)
+
diff --git a/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/model/User.scala b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/model/User.scala
new file mode 100644
index 000000000000..315a86efd65a
--- /dev/null
+++ b/samples/server/petstore/scala-akka-http/src/main/scala/org/openapitools/server/model/User.scala
@@ -0,0 +1,28 @@
+package org.openapitools.server.model
+
+
+/**
+ * = a User =
+ *
+ * A User who is purchasing from the pet store
+ *
+ * @param id for example: ''null''
+ * @param username for example: ''null''
+ * @param firstName for example: ''null''
+ * @param lastName for example: ''null''
+ * @param email for example: ''null''
+ * @param password for example: ''null''
+ * @param phone for example: ''null''
+ * @param userStatus User Status for example: ''null''
+*/
+final 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]
+)
+