mirror of
https://github.com/valitydev/openapi-generator.git
synced 2024-11-06 02:25:20 +00:00
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:
parent
00ec8fd15b
commit
af85fab52b
31
bin/scala-akka-http-server-petstore.sh
Normal file
31
bin/scala-akka-http-server-petstore.sh
Normal 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}
|
10
bin/windows/scala-akka-http-server-petstore.bat
Normal file
10
bin/windows/scala-akka-http-server-petstore.bat
Normal 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%
|
@ -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)
|
||||
|
221
docs/generators/scala-akka-http.md
Normal file
221
docs/generators/scala-akka-http.md
Normal 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
|
@ -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);
|
||||
}
|
||||
}
|
@ -129,3 +129,5 @@ org.openapitools.codegen.languages.FsharpFunctionsServerCodegen
|
||||
|
||||
org.openapitools.codegen.languages.MarkdownDocumentationCodegen
|
||||
org.openapitools.codegen.languages.ScalaSttpClientCodegen
|
||||
|
||||
org.openapitools.codegen.languages.ScalaAkkaHttpServerCodegen
|
||||
|
32
modules/openapi-generator/src/main/resources/scala-akka-http-server/README.mustache
vendored
Normal file
32
modules/openapi-generator/src/main/resources/scala-akka-http-server/README.mustache
vendored
Normal 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}}
|
95
modules/openapi-generator/src/main/resources/scala-akka-http-server/api.mustache
vendored
Normal file
95
modules/openapi-generator/src/main/resources/scala-akka-http-server/api.mustache
vendored
Normal 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}}
|
9
modules/openapi-generator/src/main/resources/scala-akka-http-server/build.sbt.mustache
vendored
Normal file
9
modules/openapi-generator/src/main/resources/scala-akka-http-server/build.sbt.mustache
vendored
Normal 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}}"
|
||||
)
|
16
modules/openapi-generator/src/main/resources/scala-akka-http-server/controller.mustache
vendored
Normal file
16
modules/openapi-generator/src/main/resources/scala-akka-http-server/controller.mustache
vendored
Normal 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)
|
||||
}
|
34
modules/openapi-generator/src/main/resources/scala-akka-http-server/helper.mustache
vendored
Normal file
34
modules/openapi-generator/src/main/resources/scala-akka-http-server/helper.mustache
vendored
Normal 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 }
|
||||
}
|
||||
}
|
27
modules/openapi-generator/src/main/resources/scala-akka-http-server/model.mustache
vendored
Normal file
27
modules/openapi-generator/src/main/resources/scala-akka-http-server/model.mustache
vendored
Normal 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}}
|
12
modules/openapi-generator/src/main/resources/scala-akka-http-server/multipart.mustache
vendored
Normal file
12
modules/openapi-generator/src/main/resources/scala-akka-http-server/multipart.mustache
vendored
Normal 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}}
|
||||
}
|
88
modules/openapi-generator/src/main/resources/scala-akka-http-server/multipartDirectives.mustache
vendored
Normal file
88
modules/openapi-generator/src/main/resources/scala-akka-http-server/multipartDirectives.mustache
vendored
Normal 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)
|
||||
}
|
7
modules/openapi-generator/src/main/resources/scala-akka-http-server/noMultipart.mustache
vendored
Normal file
7
modules/openapi-generator/src/main/resources/scala-akka-http-server/noMultipart.mustache
vendored
Normal 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}}
|
1
modules/openapi-generator/src/main/resources/scala-akka-http-server/operationParam.mustache
vendored
Normal file
1
modules/openapi-generator/src/main/resources/scala-akka-http-server/operationParam.mustache
vendored
Normal 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}}
|
127
modules/openapi-generator/src/main/resources/scala-akka-http-server/stringDirectives.mustache
vendored
Normal file
127
modules/openapi-generator/src/main/resources/scala-akka-http-server/stringDirectives.mustache
vendored
Normal 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) }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
# OpenAPI Generator Ignore
|
||||
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
|
||||
|
||||
# Use this file to prevent files from being overwritten by the generator.
|
||||
# The patterns follow closely to .gitignore or .dockerignore.
|
||||
|
||||
# As an example, the C# client generator defines ApiClient.cs.
|
||||
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
|
||||
#ApiClient.cs
|
||||
|
||||
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
|
||||
#foo/*/qux
|
||||
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
|
||||
|
||||
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
|
||||
#foo/**/qux
|
||||
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
|
||||
|
||||
# You can also negate patterns with an exclamation (!).
|
||||
# For example, you can ignore all files in a docs folder with the file extension .md:
|
||||
#docs/*.md
|
||||
# Then explicitly reverse the ignore rule for a single file:
|
||||
#!docs/README.md
|
@ -0,0 +1 @@
|
||||
4.3.1-SNAPSHOT
|
54
samples/server/petstore/scala-akka-http/README.md
Normal file
54
samples/server/petstore/scala-akka-http/README.md
Normal 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
|
||||
|
9
samples/server/petstore/scala-akka-http/build.sbt
Normal file
9
samples/server/petstore/scala-akka-http/build.sbt
Normal 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"
|
||||
)
|
@ -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 }
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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) }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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]
|
||||
|
||||
}
|
||||
|
@ -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]
|
||||
|
||||
}
|
||||
|
@ -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]
|
||||
|
||||
}
|
||||
|
@ -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]
|
||||
)
|
||||
|
@ -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]
|
||||
)
|
||||
|
@ -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]
|
||||
)
|
||||
|
@ -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]
|
||||
)
|
||||
|
@ -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]
|
||||
)
|
||||
|
@ -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]
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user