forked from loafle/openapi-generator-original
[Scala] scala-akka-http-server support pekko-http using useApachePekko flag (#16255)
* introduce useApachePekko flag into scala-akka-http-server * useApachePekko flag in scala-akka-http-server templates * introduce scala-pekko-http-server samples
This commit is contained in:
parent
82516c75d2
commit
c080660cc1
1
.github/workflows/samples-scala.yaml
vendored
1
.github/workflows/samples-scala.yaml
vendored
@ -27,6 +27,7 @@ jobs:
|
||||
- samples/server/petstore/scala-lagom-server
|
||||
- samples/server/petstore/scala-play-server
|
||||
- samples/server/petstore/scala-akka-http-server
|
||||
- samples/server/petstore/scala-pekko-http-server
|
||||
- samples/server/petstore/scalatra
|
||||
- samples/server/petstore/scala-finch # cannot be tested with jdk11
|
||||
steps:
|
||||
|
7
bin/configs/scala-pekko-http-server.yaml
Normal file
7
bin/configs/scala-pekko-http-server.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
generatorName: scala-akka-http-server
|
||||
outputDir: samples/server/petstore/scala-pekko-http-server
|
||||
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
|
||||
templateDir: modules/openapi-generator/src/main/resources/scala-akka-http-server
|
||||
additionalProperties:
|
||||
artifactId: openapi-scala-pekko-http-server
|
||||
useApachePekko: true
|
@ -37,6 +37,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|
||||
|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|
|
||||
|useApachePekko|Use apache pekko-http instead of akka-http.| |false|
|
||||
|
||||
## IMPORT MAPPING
|
||||
|
||||
|
@ -44,15 +44,27 @@ public class ScalaAkkaHttpServerCodegen extends AbstractScalaCodegen implements
|
||||
|
||||
protected String akkaHttpVersion;
|
||||
protected boolean generateAsManagedSources;
|
||||
protected boolean useApachePekko;
|
||||
|
||||
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";
|
||||
public static final String DEFAULT_PEKKO_HTTP_VERSION = "1.0.0";
|
||||
|
||||
public static final String GENERATE_AS_MANAGED_SOURCES = "asManagedSources";
|
||||
public static final String GENERATE_AS_MANAGED_SOURCES_DESC = "Resulting files cab be used as managed resources. No build files or default controllers will be generated";
|
||||
public static final boolean DEFAULT_GENERATE_AS_MANAGED_SOURCES = false;
|
||||
|
||||
public static final String USE_APACHE_PEKKO = "useApachePekko";
|
||||
public static final String USE_APACHE_PEKKO_DESC = "Use apache pekko-http instead of akka-http.";
|
||||
public static final boolean DEFAULT_USE_APACHE_PEKKO = false;
|
||||
|
||||
// scala-akka-http-server specific properties
|
||||
private static final String IS_10_1_10_PLUS = "akkaHttp10_1_10_plus";
|
||||
private static final String AKKA_IMPORT_GROUP_ID = "akkaImportGroupId";
|
||||
|
||||
private static final Pattern akkaVersionPattern = Pattern.compile("([0-9]+)(\\.([0-9]+))?(\\.([0-9]+))?(.\\+)?");
|
||||
|
||||
final Logger LOGGER = LoggerFactory.getLogger(ScalaAkkaHttpServerCodegen.class);
|
||||
|
||||
public CodegenType getTag() {
|
||||
@ -108,6 +120,7 @@ public class ScalaAkkaHttpServerCodegen extends AbstractScalaCodegen implements
|
||||
invokerPackage = "org.openapitools.server";
|
||||
akkaHttpVersion = DEFAULT_AKKA_HTTP_VERSION;
|
||||
generateAsManagedSources = DEFAULT_GENERATE_AS_MANAGED_SOURCES;
|
||||
useApachePekko = DEFAULT_USE_APACHE_PEKKO;
|
||||
|
||||
setReservedWordsLowerCase(
|
||||
Arrays.asList(
|
||||
@ -124,6 +137,7 @@ public class ScalaAkkaHttpServerCodegen extends AbstractScalaCodegen implements
|
||||
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));
|
||||
cliOptions.add(CliOption.newBoolean(GENERATE_AS_MANAGED_SOURCES, GENERATE_AS_MANAGED_SOURCES_DESC).defaultValue(Boolean.valueOf(DEFAULT_GENERATE_AS_MANAGED_SOURCES).toString()));
|
||||
cliOptions.add(CliOption.newBoolean(USE_APACHE_PEKKO, USE_APACHE_PEKKO_DESC).defaultValue(Boolean.valueOf(DEFAULT_USE_APACHE_PEKKO).toString()));
|
||||
|
||||
importMapping.remove("Seq");
|
||||
importMapping.remove("List");
|
||||
@ -181,14 +195,27 @@ public class ScalaAkkaHttpServerCodegen extends AbstractScalaCodegen implements
|
||||
additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion);
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(USE_APACHE_PEKKO)) {
|
||||
useApachePekko = Boolean.parseBoolean(additionalProperties.get(USE_APACHE_PEKKO).toString());
|
||||
} else {
|
||||
additionalProperties.put(USE_APACHE_PEKKO, useApachePekko);
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(AKKA_HTTP_VERSION)) {
|
||||
akkaHttpVersion = (String) additionalProperties.get(AKKA_HTTP_VERSION);
|
||||
} else {
|
||||
additionalProperties.put(AKKA_HTTP_VERSION, akkaHttpVersion);
|
||||
String version = useApachePekko ? DEFAULT_PEKKO_HTTP_VERSION : DEFAULT_AKKA_HTTP_VERSION;
|
||||
additionalProperties.put(AKKA_HTTP_VERSION, version);
|
||||
}
|
||||
|
||||
parseAkkaHttpVersion();
|
||||
|
||||
if (useApachePekko) {
|
||||
additionalProperties.put(IS_10_1_10_PLUS, true); // Pekko HTTP is a fork of Akka HTTP 10.2.x
|
||||
additionalProperties.put(USE_APACHE_PEKKO, true);
|
||||
additionalProperties.put(AKKA_IMPORT_GROUP_ID, "org.apache.pekko");
|
||||
} else {
|
||||
additionalProperties.put(AKKA_IMPORT_GROUP_ID, "akka");
|
||||
parseAkkaHttpVersion();
|
||||
}
|
||||
|
||||
if (additionalProperties.containsKey(GENERATE_AS_MANAGED_SOURCES)) {
|
||||
generateAsManagedSources = Boolean.parseBoolean(additionalProperties.get(GENERATE_AS_MANAGED_SOURCES).toString());
|
||||
@ -210,12 +237,8 @@ public class ScalaAkkaHttpServerCodegen extends AbstractScalaCodegen implements
|
||||
(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() {
|
||||
boolean is10_1_10AndAbove = false;
|
||||
Matcher matcher = akkaVersionPattern.matcher(akkaHttpVersion);
|
||||
if (matcher.matches()) {
|
||||
String majorS = matcher.group(1);
|
||||
|
@ -1,15 +1,15 @@
|
||||
package {{package}}
|
||||
|
||||
import akka.http.scaladsl.server.Directives._
|
||||
import akka.http.scaladsl.server.Route
|
||||
import akka.http.scaladsl.model.StatusCodes
|
||||
{{^pathMatcherPatterns.isEmpty}}import akka.http.scaladsl.server.{PathMatcher, PathMatcher1}
|
||||
import {{akkaImportGroupId}}.http.scaladsl.server.Directives._
|
||||
import {{akkaImportGroupId}}.http.scaladsl.server.Route
|
||||
import {{akkaImportGroupId}}.http.scaladsl.model.StatusCodes
|
||||
{{^pathMatcherPatterns.isEmpty}}import {{akkaImportGroupId}}.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}}import {{akkaImportGroupId}}.http.scaladsl.marshalling.ToEntityMarshaller
|
||||
import {{akkaImportGroupId}}.http.scaladsl.unmarshalling.FromEntityUnmarshaller
|
||||
import {{akkaImportGroupId}}.http.scaladsl.unmarshalling.FromStringUnmarshaller
|
||||
{{/hasMarshalling}}
|
||||
{{#hasCookieParams}}import akka.http.scaladsl.model.headers.HttpCookiePair
|
||||
{{#hasCookieParams}}import {{akkaImportGroupId}}.http.scaladsl.model.headers.HttpCookiePair
|
||||
{{/hasCookieParams}}
|
||||
import {{invokerPackage}}.AkkaHttpHelper._
|
||||
{{#hasMultipart}}import {{invokerPackage}}.StringDirectives
|
||||
@ -20,8 +20,8 @@ import {{invokerPackage}}.PartsAndFiles
|
||||
{{#imports}}import {{import}}
|
||||
{{/imports}}
|
||||
{{#hasMultipart}}import scala.util.Try
|
||||
import akka.http.scaladsl.server.MalformedRequestContentRejection
|
||||
import akka.http.scaladsl.server.directives.FileInfo
|
||||
import {{akkaImportGroupId}}.http.scaladsl.server.MalformedRequestContentRejection
|
||||
import {{akkaImportGroupId}}.http.scaladsl.server.directives.FileInfo
|
||||
{{/hasMultipart}}
|
||||
|
||||
|
||||
|
@ -3,7 +3,9 @@ name := "{{artifactId}}"
|
||||
organization := "{{groupId}}"
|
||||
scalaVersion := "2.12.8"
|
||||
|
||||
libraryDependencies ++= Seq(
|
||||
libraryDependencies ++= Seq({{#useApachePekko}}
|
||||
"org.apache.pekko" %% "pekko-stream" % "{{akkaHttpVersion}}",
|
||||
"org.apache.pekko" %% "pekko-http" % "{{akkaHttpVersion}}"{{/useApachePekko}}{{^useApachePekko}}
|
||||
"com.typesafe.akka" %% "akka-stream" % "2.5.21",
|
||||
"com.typesafe.akka" %% "akka-http" % "{{akkaHttpVersion}}"
|
||||
"com.typesafe.akka" %% "akka-http" % "{{akkaHttpVersion}}"{{/useApachePekko}}
|
||||
)
|
||||
|
@ -1,12 +1,12 @@
|
||||
package {{invokerPackage}}
|
||||
|
||||
import akka.http.scaladsl.Http
|
||||
import akka.http.scaladsl.server.Route
|
||||
import {{akkaImportGroupId}}.http.scaladsl.Http
|
||||
import {{akkaImportGroupId}}.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
|
||||
import {{akkaImportGroupId}}.http.scaladsl.server.Directives._
|
||||
import {{akkaImportGroupId}}.actor.ActorSystem
|
||||
import {{akkaImportGroupId}}.stream.ActorMaterializer
|
||||
|
||||
class Controller({{#apiInfo}}{{#apis}}{{#operations}}{{classVarName}}: {{classname}}{{^-last}}, {{/-last}}{{/operations}}{{/apis}}{{/apiInfo}})(implicit system: ActorSystem, materializer: ActorMaterializer) {
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package {{invokerPackage}}
|
||||
|
||||
import akka.http.scaladsl.server.Directives._
|
||||
import akka.http.scaladsl.server.{PathMatcher, PathMatcher1}
|
||||
import {{akkaImportGroupId}}.http.scaladsl.server.Directives._
|
||||
import {{akkaImportGroupId}}.http.scaladsl.server.{PathMatcher, PathMatcher1}
|
||||
import scala.util.{Failure, Success, Try}
|
||||
import scala.util.control.NoStackTrace
|
||||
|
||||
|
@ -3,22 +3,22 @@ package {{invokerPackage}}
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
||||
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 {{akkaImportGroupId}}.annotation.ApiMayChange
|
||||
import {{akkaImportGroupId}}.http.scaladsl.model.Multipart.FormData
|
||||
import {{akkaImportGroupId}}.http.scaladsl.model.{ContentType, HttpEntity, Multipart}
|
||||
import {{akkaImportGroupId}}.http.scaladsl.server.Directive1
|
||||
import {{akkaImportGroupId}}.http.scaladsl.server.directives._
|
||||
import {{akkaImportGroupId}}.stream.Materializer
|
||||
import {{akkaImportGroupId}}.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._
|
||||
import {{akkaImportGroupId}}.http.scaladsl.server.directives.BasicDirectives._
|
||||
import {{akkaImportGroupId}}.http.scaladsl.server.directives.FutureDirectives._
|
||||
import {{akkaImportGroupId}}.http.scaladsl.server.directives.MarshallingDirectives._
|
||||
|
||||
@ApiMayChange
|
||||
def formAndFiles(fileFields: FileField*): Directive1[PartsAndFiles] =
|
||||
|
@ -1,9 +1,9 @@
|
||||
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 {{akkaImportGroupId}}.http.scaladsl.common._
|
||||
import {{akkaImportGroupId}}.http.scaladsl.server.{Directive, Directive0, Directive1, InvalidRequiredValueForQueryParamRejection, MalformedFormFieldRejection, MissingFormFieldRejection, MissingQueryParamRejection, UnsupportedRequestContentTypeRejection}
|
||||
import {{akkaImportGroupId}}.http.scaladsl.server.directives.BasicDirectives
|
||||
import {{akkaImportGroupId}}.http.scaladsl.unmarshalling.Unmarshaller.UnsupportedContentTypeException
|
||||
|
||||
import scala.concurrent.Future
|
||||
import scala.util.{Failure, Success}
|
||||
@ -48,10 +48,10 @@ object StringDirectives extends StringDirectives {
|
||||
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._
|
||||
import {{akkaImportGroupId}}.http.scaladsl.server.directives.BasicDirectives._
|
||||
import {{akkaImportGroupId}}.http.scaladsl.server.directives.FutureDirectives._
|
||||
import {{akkaImportGroupId}}.http.scaladsl.server.directives.RouteDirectives._
|
||||
import {{akkaImportGroupId}}.http.scaladsl.unmarshalling._
|
||||
|
||||
type FSU[T] = FromStringUnmarshaller[T]
|
||||
type FSOU[T] = Unmarshaller[Option[String], T]
|
||||
@ -112,8 +112,8 @@ object StringDirectives extends StringDirectives {
|
||||
|
||||
//////////////////// tuple support ////////////////////
|
||||
|
||||
import akka.http.scaladsl.server.util.BinaryPolyFunc
|
||||
import akka.http.scaladsl.server.util.TupleOps._
|
||||
import {{akkaImportGroupId}}.http.scaladsl.server.util.BinaryPolyFunc
|
||||
import {{akkaImportGroupId}}.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, _))
|
||||
|
@ -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,15 @@
|
||||
README.md
|
||||
build.sbt
|
||||
src/main/scala/org/openapitools/server/AkkaHttpHelper.scala
|
||||
src/main/scala/org/openapitools/server/Controller.scala
|
||||
src/main/scala/org/openapitools/server/MultipartDirectives.scala
|
||||
src/main/scala/org/openapitools/server/StringDirectives.scala
|
||||
src/main/scala/org/openapitools/server/api/PetApi.scala
|
||||
src/main/scala/org/openapitools/server/api/StoreApi.scala
|
||||
src/main/scala/org/openapitools/server/api/UserApi.scala
|
||||
src/main/scala/org/openapitools/server/model/ApiResponse.scala
|
||||
src/main/scala/org/openapitools/server/model/Category.scala
|
||||
src/main/scala/org/openapitools/server/model/Order.scala
|
||||
src/main/scala/org/openapitools/server/model/Pet.scala
|
||||
src/main/scala/org/openapitools/server/model/Tag.scala
|
||||
src/main/scala/org/openapitools/server/model/User.scala
|
@ -0,0 +1 @@
|
||||
7.0.0-SNAPSHOT
|
54
samples/server/petstore/scala-pekko-http-server/README.md
Normal file
54
samples/server/petstore/scala-pekko-http-server/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
|
||||
|
@ -0,0 +1,9 @@
|
||||
version := "1.0.0"
|
||||
name := "openapi-scala-pekko-http-server"
|
||||
organization := "org.openapitools"
|
||||
scalaVersion := "2.12.8"
|
||||
|
||||
libraryDependencies ++= Seq(
|
||||
"org.apache.pekko" %% "pekko-stream" % "1.0.0",
|
||||
"org.apache.pekko" %% "pekko-http" % "1.0.0"
|
||||
)
|
@ -0,0 +1,34 @@
|
||||
package org.openapitools.server
|
||||
|
||||
import org.apache.pekko.http.scaladsl.server.Directives._
|
||||
import org.apache.pekko.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 org.apache.pekko.http.scaladsl.Http
|
||||
import org.apache.pekko.http.scaladsl.server.Route
|
||||
import org.openapitools.server.api.PetApi
|
||||
import org.openapitools.server.api.StoreApi
|
||||
import org.openapitools.server.api.UserApi
|
||||
|
||||
import org.apache.pekko.http.scaladsl.server.Directives._
|
||||
import org.apache.pekko.actor.ActorSystem
|
||||
import org.apache.pekko.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,89 @@
|
||||
package org.openapitools.server
|
||||
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
||||
import org.apache.pekko.annotation.ApiMayChange
|
||||
import org.apache.pekko.http.scaladsl.model.Multipart.FormData
|
||||
import org.apache.pekko.http.scaladsl.model.{ContentType, HttpEntity, Multipart}
|
||||
import org.apache.pekko.http.scaladsl.server.Directive1
|
||||
import org.apache.pekko.http.scaladsl.server.directives._
|
||||
import org.apache.pekko.stream.Materializer
|
||||
import org.apache.pekko.stream.scaladsl._
|
||||
|
||||
import scala.collection.immutable
|
||||
import scala.concurrent.{ExecutionContextExecutor, Future}
|
||||
|
||||
trait MultipartDirectives {
|
||||
|
||||
import org.apache.pekko.http.scaladsl.server.directives.BasicDirectives._
|
||||
import org.apache.pekko.http.scaladsl.server.directives.FutureDirectives._
|
||||
import org.apache.pekko.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 => Files.createTempFile(file.fileName, ".tmp").toFile()
|
||||
}
|
||||
}
|
||||
|
||||
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 org.apache.pekko.http.scaladsl.common._
|
||||
import org.apache.pekko.http.scaladsl.server.{Directive, Directive0, Directive1, InvalidRequiredValueForQueryParamRejection, MalformedFormFieldRejection, MissingFormFieldRejection, MissingQueryParamRejection, UnsupportedRequestContentTypeRejection}
|
||||
import org.apache.pekko.http.scaladsl.server.directives.BasicDirectives
|
||||
import org.apache.pekko.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 org.apache.pekko.http.scaladsl.server.directives.BasicDirectives._
|
||||
import org.apache.pekko.http.scaladsl.server.directives.FutureDirectives._
|
||||
import org.apache.pekko.http.scaladsl.server.directives.RouteDirectives._
|
||||
import org.apache.pekko.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 org.apache.pekko.http.scaladsl.server.util.BinaryPolyFunc
|
||||
import org.apache.pekko.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,198 @@
|
||||
package org.openapitools.server.api
|
||||
|
||||
import org.apache.pekko.http.scaladsl.server.Directives._
|
||||
import org.apache.pekko.http.scaladsl.server.Route
|
||||
import org.apache.pekko.http.scaladsl.model.StatusCodes
|
||||
import org.apache.pekko.http.scaladsl.marshalling.ToEntityMarshaller
|
||||
import org.apache.pekko.http.scaladsl.unmarshalling.FromEntityUnmarshaller
|
||||
import org.apache.pekko.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 org.apache.pekko.http.scaladsl.server.MalformedRequestContentRejection
|
||||
import org.apache.pekko.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]){ pet =>
|
||||
petService.addPet(pet = pet)
|
||||
}
|
||||
}
|
||||
} ~
|
||||
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]){ pet =>
|
||||
petService.updatePet(pet = pet)
|
||||
}
|
||||
}
|
||||
} ~
|
||||
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 addPet200(responsePet: Pet)(implicit toEntityMarshallerPet: ToEntityMarshaller[Pet]): Route =
|
||||
complete((200, responsePet))
|
||||
def addPet405: Route =
|
||||
complete((405, "Invalid input"))
|
||||
/**
|
||||
* Code: 200, Message: successful operation, DataType: Pet
|
||||
* Code: 405, Message: Invalid input
|
||||
*/
|
||||
def addPet(pet: Pet)
|
||||
(implicit toEntityMarshallerPet: ToEntityMarshaller[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 updatePet200(responsePet: Pet)(implicit toEntityMarshallerPet: ToEntityMarshaller[Pet]): Route =
|
||||
complete((200, responsePet))
|
||||
def updatePet400: Route =
|
||||
complete((400, "Invalid ID supplied"))
|
||||
def updatePet404: Route =
|
||||
complete((404, "Pet not found"))
|
||||
def updatePet405: Route =
|
||||
complete((405, "Validation exception"))
|
||||
/**
|
||||
* Code: 200, Message: successful operation, DataType: Pet
|
||||
* Code: 400, Message: Invalid ID supplied
|
||||
* Code: 404, Message: Pet not found
|
||||
* Code: 405, Message: Validation exception
|
||||
*/
|
||||
def updatePet(pet: Pet)
|
||||
(implicit toEntityMarshallerPet: ToEntityMarshaller[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,101 @@
|
||||
package org.openapitools.server.api
|
||||
|
||||
import org.apache.pekko.http.scaladsl.server.Directives._
|
||||
import org.apache.pekko.http.scaladsl.server.Route
|
||||
import org.apache.pekko.http.scaladsl.model.StatusCodes
|
||||
import org.apache.pekko.http.scaladsl.marshalling.ToEntityMarshaller
|
||||
import org.apache.pekko.http.scaladsl.unmarshalling.FromEntityUnmarshaller
|
||||
import org.apache.pekko.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]){ order =>
|
||||
storeService.placeOrder(order = order)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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(order: Order)
|
||||
(implicit toEntityMarshallerOrder: ToEntityMarshaller[Order]): Route
|
||||
|
||||
}
|
||||
|
||||
trait StoreApiMarshaller {
|
||||
implicit def fromEntityUnmarshallerOrder: FromEntityUnmarshaller[Order]
|
||||
|
||||
|
||||
|
||||
implicit def toEntityMarshallerOrder: ToEntityMarshaller[Order]
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,162 @@
|
||||
package org.openapitools.server.api
|
||||
|
||||
import org.apache.pekko.http.scaladsl.server.Directives._
|
||||
import org.apache.pekko.http.scaladsl.server.Route
|
||||
import org.apache.pekko.http.scaladsl.model.StatusCodes
|
||||
import org.apache.pekko.http.scaladsl.marshalling.ToEntityMarshaller
|
||||
import org.apache.pekko.http.scaladsl.unmarshalling.FromEntityUnmarshaller
|
||||
import org.apache.pekko.http.scaladsl.unmarshalling.FromStringUnmarshaller
|
||||
import org.openapitools.server.AkkaHttpHelper._
|
||||
import java.time.OffsetDateTime
|
||||
import org.openapitools.server.model.User
|
||||
|
||||
|
||||
class UserApi(
|
||||
userService: UserApiService,
|
||||
userMarshaller: UserApiMarshaller
|
||||
) {
|
||||
|
||||
|
||||
import userMarshaller._
|
||||
|
||||
lazy val route: Route =
|
||||
path("user") {
|
||||
post {
|
||||
entity(as[User]){ user =>
|
||||
userService.createUser(user = user)
|
||||
}
|
||||
}
|
||||
} ~
|
||||
path("user" / "createWithArray") {
|
||||
post {
|
||||
entity(as[Seq[User]]){ user =>
|
||||
userService.createUsersWithArrayInput(user = user)
|
||||
}
|
||||
}
|
||||
} ~
|
||||
path("user" / "createWithList") {
|
||||
post {
|
||||
entity(as[Seq[User]]){ user =>
|
||||
userService.createUsersWithListInput(user = user)
|
||||
}
|
||||
}
|
||||
} ~
|
||||
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]){ user =>
|
||||
userService.updateUser(username = username, user = user)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
trait UserApiService {
|
||||
|
||||
def createUserDefault(statusCode: Int): Route =
|
||||
complete((statusCode, "successful operation"))
|
||||
/**
|
||||
* Code: 0, Message: successful operation
|
||||
*/
|
||||
def createUser(user: User): Route
|
||||
|
||||
def createUsersWithArrayInputDefault(statusCode: Int): Route =
|
||||
complete((statusCode, "successful operation"))
|
||||
/**
|
||||
* Code: 0, Message: successful operation
|
||||
*/
|
||||
def createUsersWithArrayInput(user: Seq[User]): Route
|
||||
|
||||
def createUsersWithListInputDefault(statusCode: Int): Route =
|
||||
complete((statusCode, "successful operation"))
|
||||
/**
|
||||
* Code: 0, Message: successful operation
|
||||
*/
|
||||
def createUsersWithListInput(user: 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, user: 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] = None,
|
||||
`type`: Option[String] = None,
|
||||
message: Option[String] = None
|
||||
)
|
||||
|
@ -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] = None,
|
||||
name: Option[String] = None
|
||||
)
|
||||
|
@ -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] = None,
|
||||
petId: Option[Long] = None,
|
||||
quantity: Option[Int] = None,
|
||||
shipDate: Option[OffsetDateTime] = None,
|
||||
status: Option[String] = None,
|
||||
complete: Option[Boolean] = None
|
||||
)
|
||||
|
@ -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] = None,
|
||||
category: Option[Category] = None,
|
||||
name: String,
|
||||
photoUrls: Seq[String],
|
||||
tags: Option[Seq[Tag]] = None,
|
||||
status: Option[String] = None
|
||||
)
|
||||
|
@ -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] = None,
|
||||
name: Option[String] = None
|
||||
)
|
||||
|
@ -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] = None,
|
||||
username: Option[String] = None,
|
||||
firstName: Option[String] = None,
|
||||
lastName: Option[String] = None,
|
||||
email: Option[String] = None,
|
||||
password: Option[String] = None,
|
||||
phone: Option[String] = None,
|
||||
userStatus: Option[Int] = None
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user