Scala akka-http server (#5758)

* Scala akka-http server base implementation

* [scala-akka-http-server] petStore samples

* Improved the formatting of generated files

* Updated scala-akka-http server samples

* [scala-akka-http-server] the groupId, artifactId and artifactVersion default value are used as intended.

* Fixed the default operation not being correctly generated on parameterless operations

* Added build.sbt.mustache supporting file

* Updated scala-akka-http server samples

* ScalaAkkaHttpServer: Fixed a String.format call to use Locale.ROOT for locale

* [scala-akka-http-server] Fixed defaultValue being escaped during generation

* Added scala-akka-http.md

* Replaced all "⇒" character with "=>" to retain compatibility with scala 2.13

* [scala-akka-http] Added a config option akkaHttpVersion
It's set in the generated build.sbt.

* Updated scala-akka-http server samples

* [scala-akka-http] More accurate akkaHttpVersion parsing

* Updated scala-akka-http.md

* [scala-akka-http] Changed the akka-http version check to fix the generation of StringDirectives

* Updated scala-akka-http samples

* updated scala-akka-http.md

Co-authored-by: Olivier Leonard <oleonard@ankama.com>
This commit is contained in:
Bouillie 2020-04-04 17:16:14 +02:00 committed by GitHub
parent 00ec8fd15b
commit af85fab52b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 2128 additions and 0 deletions

View File

@ -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}

View File

@ -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%

View File

@ -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)

View File

@ -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|<dl><dt>**joda**</dt><dd>Joda (for legacy app)</dd><dt>**java8**</dt><dd>Java 8 native JSR310 (prefered for JDK 1.8+)</dd></dl>|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
<ul class="column-ul">
<li>Any</li>
<li>Array</li>
<li>Boolean</li>
<li>Double</li>
<li>Float</li>
<li>Int</li>
<li>List</li>
<li>Long</li>
<li>Map</li>
<li>Object</li>
<li>Seq</li>
<li>String</li>
<li>boolean</li>
</ul>
## RESERVED WORDS
<ul class="column-ul">
<li>abstract</li>
<li>case</li>
<li>catch</li>
<li>class</li>
<li>def</li>
<li>do</li>
<li>else</li>
<li>extends</li>
<li>false</li>
<li>final</li>
<li>finally</li>
<li>for</li>
<li>forsome</li>
<li>if</li>
<li>implicit</li>
<li>import</li>
<li>lazy</li>
<li>match</li>
<li>new</li>
<li>null</li>
<li>object</li>
<li>override</li>
<li>package</li>
<li>private</li>
<li>protected</li>
<li>return</li>
<li>sealed</li>
<li>super</li>
<li>this</li>
<li>throw</li>
<li>trait</li>
<li>true</li>
<li>try</li>
<li>type</li>
<li>val</li>
<li>var</li>
<li>while</li>
<li>with</li>
<li>yield</li>
</ul>
## 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

View File

@ -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<Server> servers) {
CodegenOperation codegenOperation = super.fromOperation(path, httpMethod, operation, servers);
addPathMatcher(codegenOperation);
return codegenOperation;
}
@Override
public CodegenParameter fromParameter(Parameter parameter, Set<String> 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<String, Object> postProcessOperationsWithModels(Map<String, Object> objs, List<Object> allModels) {
Map<String, Object> baseObjs = super.postProcessOperationsWithModels(objs, allModels);
pathMatcherPatternsPostProcessor(baseObjs);
marshallingPostProcessor(baseObjs);
return baseObjs;
}
private static Set<String> primitiveParamTypes = new HashSet<String>(){{
addAll(Arrays.asList(
"Int",
"Long",
"Float",
"Double",
"Boolean",
"String"
));
}};
private static Map<String, String> pathTypeToMatcher = new HashMap<String, String>(){{
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<String> allPaths = new LinkedList<>(Arrays.asList(codegenOperation.path.split("/")));
allPaths.removeIf(""::equals);
LinkedList<TextOrMatcher> 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<String, Object> objs) {
if (objs != null) {
HashMap<String, PathMatcherPattern> patternMap = new HashMap<>();
Map<String, Object> operations = (Map<String, Object>) objs.get("operations");
if (operations != null) {
List<CodegenOperation> ops = (List<CodegenOperation>) 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<String, Object> objs) {
if (objs == null) {
return;
}
Set<Marshaller> entityUnmarshallerTypes = new HashSet<>();
Set<Marshaller> entityMarshallerTypes = new HashSet<>();
Set<Marshaller> stringUnmarshallerTypes = new HashSet<>();
boolean hasCookieParams = false;
boolean hasMultipart = false;
Map<String, Object> operations = (Map<String, Object>) objs.get("operations");
if (operations != null) {
List<CodegenOperation> operationList = (List<CodegenOperation>) operations.get("operation");
for (CodegenOperation op : operationList) {
boolean isMultipart = op.isMultipart;
hasMultipart |= isMultipart;
hasCookieParams |= op.getHasCookieParams();
ArrayList<CodegenParameter> fileParams = new ArrayList<>();
ArrayList<CodegenParameter> 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<Marshaller> 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);
}
}

View File

@ -129,3 +129,5 @@ org.openapitools.codegen.languages.FsharpFunctionsServerCodegen
org.openapitools.codegen.languages.MarkdownDocumentationCodegen
org.openapitools.codegen.languages.ScalaSttpClientCodegen
org.openapitools.codegen.languages.ScalaAkkaHttpServerCodegen

View File

@ -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}}

View File

@ -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}}

View File

@ -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}}"
)

View File

@ -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)
}

View File

@ -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 }
}
}

View File

@ -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}}

View File

@ -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}}
}

View File

@ -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)
}

View File

@ -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}}

View File

@ -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}}

View File

@ -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) }
}
}
}

View File

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

View File

@ -0,0 +1 @@
4.3.1-SNAPSHOT

View File

@ -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

View File

@ -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"
)

View File

@ -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 }
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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) }
}
}
}

View File

@ -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]
}

View File

@ -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]
}

View File

@ -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]
}

View File

@ -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]
)

View File

@ -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]
)

View File

@ -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]
)

View File

@ -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]
)

View File

@ -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]
)

View File

@ -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]
)