New generator - Scala Play Framework (#2421)

* Added new generator for Scala + Play Framework (WIP)

* scala-play-framework: default values reintroduced (mostly); datatype -> dataType

* reintroduced missing EOF newline

* Support single/collection params for header/query params

* Rename apiFutures > supportAsync, implStubs > skipStubs (opt-out instead of opt-in)

* Deleted license and small fixes

* Generate extraction of form parameters from request body

* Added missing call to executeApi for unit methods when supportAsync=false

* Polished some stuff and added routes, application.conf, logback.xml, better default responses

* Disabled generation of Json.format for models with files

* Added README

* Multiple additions and improvements.

- Fix Indentation using mustache lambdas
- Option to set routes file name (default: routes) - allows uninterrupted manual maintenance of main routes file, which may include a subroute to the generated routes file
- Move supporting file classes to a package and update application.conf generation accordingly
- Option to generate custom exceptions (default: true) which are used in the controller to differentiate between API call exceptions and validation exceptions
- Generate error handler with basic exception mapping
- Option to generate API docs under /api route
- Reorder routes file so parameter-less paths are given priority over parameterized paths. Prevents case like /v2/user/:username activating before /v2/user/login (thus shadowing the login route completely) as observed using v3 petstore.yaml
- Option to set base package name (default: org.openapitools) to allow placing supporting files under a different package

* Revert supportAsync default to false

* Added binaries and default api/model packages

* Added scala-play-framework sample

* Add missing contextPath to README and controller comment
This commit is contained in:
Adi Gerber 2019-03-26 10:04:48 +02:00 committed by William Cheng
parent 9e391efd1d
commit 28ae33cb13
59 changed files with 3094 additions and 0 deletions

View File

@ -0,0 +1,32 @@
#!/bin/sh
SCRIPT="$0"
echo "# START SCRIPT: $SCRIPT"
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 -B clean package
fi
# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="generate -t modules/openapi-generator/src/main/resources/scala-play-framework -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g scala-play-framework -o samples/server/petstore/scala-play-framework $@"
java $JAVA_OPTS -jar $executable $ags

View File

@ -0,0 +1,10 @@
set executable=.\modules\openapi-generator-cli\target\openapi-generator-cli.jar
If Not Exist %executable% (
mvn clean package
)
REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M
set ags=generate -i modules\openapi-generator\src\test\resources\2_0\petstore.yaml -g scala-play-framework -o samples\server\petstore\scala-play-framework
java %JAVA_OPTS% -jar %executable% %ags%

View File

@ -0,0 +1,401 @@
package org.openapitools.codegen.languages;
import com.google.common.collect.ImmutableMap;
import com.samskivert.mustache.Mustache;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Schema;
import org.openapitools.codegen.*;
import org.openapitools.codegen.mustache.*;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.rightPad;
import static org.openapitools.codegen.utils.StringUtils.camelize;
public class ScalaPlayFrameworkServerCodegen extends AbstractScalaCodegen implements CodegenConfig {
public static final String TITLE = "title";
public static final String SKIP_STUBS = "skipStubs";
public static final String SUPPORT_ASYNC = "supportAsync";
public static final String GENERATE_CUSTOM_EXCEPTIONS = "generateCustomExceptions";
public static final String USE_SWAGGER_UI = "useSwaggerUI";
public static final String ROUTES_FILE_NAME = "routesFileName";
public static final String BASE_PACKAGE = "basePackage";
static Logger LOGGER = LoggerFactory.getLogger(ScalaPlayFrameworkServerCodegen.class);
protected boolean skipStubs = false;
protected boolean supportAsync = false;
protected boolean generateCustomExceptions = true;
protected boolean useSwaggerUI = true;
protected String routesFileName = "routes";
protected String basePackage = "org.openapitools";
public ScalaPlayFrameworkServerCodegen() {
super();
outputFolder = "generated-code" + File.separator + "scala-play-framework";
modelTemplateFiles.put("model.mustache", ".scala");
apiTemplateFiles.put("api.mustache", ".scala");
embeddedTemplateDir = templateDir = "scala-play-framework";
hideGenerationTimestamp = false;
sourceFolder = "app";
apiPackage = "api";
modelPackage = "model";
instantiationTypes.put("map", "Map");
instantiationTypes.put("array", "List");
typeMapping.put("DateTime", "OffsetDateTime");
typeMapping.put("Date", "LocalDate");
typeMapping.put("Integer", "Int");
typeMapping.put("binary", "Array[Byte]");
typeMapping.put("ByteArray", "Array[Byte]");
typeMapping.put("object", "JsObject");
typeMapping.put("file", "TemporaryFile");
importMapping.put("OffsetDateTime", "java.time.OffsetDateTime");
importMapping.put("LocalDate", "java.time.LocalDate");
importMapping.remove("BigDecimal");
importMapping.put("TemporaryFile", "play.api.libs.Files.TemporaryFile");
cliOptions.add(new CliOption(ROUTES_FILE_NAME, "Name of the routes file to generate.").defaultValue(routesFileName));
cliOptions.add(new CliOption(ROUTES_FILE_NAME, "Base package in which supporting classes are generated.").defaultValue(basePackage));
addCliOptionWithDefault(SKIP_STUBS, "If set, skips generation of stub classes.", skipStubs);
addCliOptionWithDefault(SUPPORT_ASYNC, "If set, wraps API return types with Futures and generates async actions.", supportAsync);
addCliOptionWithDefault(GENERATE_CUSTOM_EXCEPTIONS, "If set, generates custom exception types.", generateCustomExceptions);
addCliOptionWithDefault(USE_SWAGGER_UI, "Add a route to /api which show your documentation in swagger-ui. Will also import needed dependencies", useSwaggerUI);
}
public CodegenType getTag() {
return CodegenType.SERVER;
}
public String getName() {
return "scala-play-framework";
}
public String getHelp() {
return "Generates a Scala server application with Play Framework.";
}
public void setSupportAsync(boolean supportAsync) {
this.supportAsync = supportAsync;
}
public void setSkipStubs(boolean skipStubs) {
this.skipStubs = skipStubs;
}
public void setGenerateCustomExceptions(boolean generateCustomExceptions) {
this.generateCustomExceptions = generateCustomExceptions;
}
public void setRoutesFileName(String routesFileName) {
this.routesFileName = routesFileName;
}
public void setBasePackage(String basePackage) {
this.basePackage = basePackage;
}
public void setUseSwaggerUI(boolean useSwaggerUI) {
this.useSwaggerUI = useSwaggerUI;
}
@Override
public void processOpts() {
super.processOpts();
if (additionalProperties.containsKey(SKIP_STUBS)) {
this.setSkipStubs(convertPropertyToBoolean(SKIP_STUBS));
}
writePropertyBack(SKIP_STUBS, skipStubs);
if (additionalProperties.containsKey(SUPPORT_ASYNC)) {
this.setSupportAsync(convertPropertyToBoolean(SUPPORT_ASYNC));
}
writePropertyBack(SUPPORT_ASYNC, supportAsync);
if (additionalProperties.containsKey(GENERATE_CUSTOM_EXCEPTIONS)) {
this.setGenerateCustomExceptions(convertPropertyToBoolean(GENERATE_CUSTOM_EXCEPTIONS));
}
writePropertyBack(GENERATE_CUSTOM_EXCEPTIONS, generateCustomExceptions);
if (additionalProperties.containsKey(USE_SWAGGER_UI)) {
this.setUseSwaggerUI(convertPropertyToBoolean(USE_SWAGGER_UI));
}
writePropertyBack(USE_SWAGGER_UI, useSwaggerUI);
if (additionalProperties.containsKey(ROUTES_FILE_NAME)) {
this.setRoutesFileName((String)additionalProperties.get(ROUTES_FILE_NAME));
} else {
additionalProperties.put(ROUTES_FILE_NAME, routesFileName);
}
if (additionalProperties.containsKey(BASE_PACKAGE)) {
this.setBasePackage((String)additionalProperties.get(BASE_PACKAGE));
} else {
additionalProperties.put(BASE_PACKAGE, basePackage);
}
apiTemplateFiles.remove("api.mustache");
if (!skipStubs) {
apiTemplateFiles.put("app/apiImplStubs.scala.mustache", "Impl.scala");
}
apiTemplateFiles.put("app/apiTrait.scala.mustache", ".scala");
apiTemplateFiles.put("app/apiController.scala.mustache", "Controller.scala");
supportingFiles.add(new SupportingFile("README.md.mustache", "", "README.md"));
supportingFiles.add(new SupportingFile("build.sbt.mustache", "", "build.sbt"));
supportingFiles.add(new SupportingFile("conf/application.conf.mustache", "conf", "application.conf"));
supportingFiles.add(new SupportingFile("conf/logback.xml.mustache", "conf", "logback.xml"));
supportingFiles.add(new SupportingFile("project/build.properties.mustache", "project", "build.properties"));
supportingFiles.add(new SupportingFile("project/plugins.sbt.mustache", "project", "plugins.sbt"));
supportingFiles.add(new SupportingFile("conf/routes.mustache", "conf", routesFileName));
supportingFiles.add(new SupportingFile("app/module.scala.mustache", getBasePackagePath(), "Module.scala"));
supportingFiles.add(new SupportingFile("app/errorHandler.scala.mustache", getBasePackagePath(), "ErrorHandler.scala"));
if (generateCustomExceptions) {
supportingFiles.add(new SupportingFile("app/exceptions.scala.mustache", getBasePackagePath(), "OpenApiExceptions.scala"));
}
if (this.useSwaggerUI) {
//App/Controllers
supportingFiles.add(new SupportingFile("public/openapi.json.mustache", "public", "openapi.json"));
supportingFiles.add(new SupportingFile("app/apiDocController.scala.mustache", String.format(Locale.ROOT, "app/%s", apiPackage.replace(".", File.separator)), "ApiDocController.scala"));
}
addMustacheLambdas(additionalProperties);
}
private void addMustacheLambdas(Map<String, Object> objs) {
Map<String, Mustache.Lambda> lambdas = new ImmutableMap.Builder<String, Mustache.Lambda>()
.put("indented_4", new IndentedLambda(4, " "))
.put("indented_8", new IndentedLambda(8, " "))
.build();
objs.put("lambda", lambdas);
}
@SuppressWarnings("unchecked")
@Override
public Map<String, Object> postProcessOperationsWithModels(Map<String, Object> objs, List<Object> allModels) {
Map<String, CodegenModel> models = new HashMap<>();
for (Object _mo : allModels) {
CodegenModel model = (CodegenModel)((Map<String, Object>)_mo).get("model");
models.put(model.classname, model);
}
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) {
Pattern pathVariableMatcher = Pattern.compile("\\{([^}]+)}");
Matcher match = pathVariableMatcher.matcher(operation.path);
while (match.find()) {
String completeMatch = match.group();
String replacement = ":" + camelize(match.group(1), true);
operation.path = operation.path.replace(completeMatch, replacement);
}
if ("null".equals(operation.defaultResponse) && models.containsKey(operation.returnType)) {
operation.defaultResponse = models.get(operation.returnType).defaultValue;
}
}
}
return objs;
}
@SuppressWarnings("unchecked")
@Override
public Map<String, Object> postProcessAllModels(Map<String, Object> objs) {
objs = super.postProcessAllModels(objs);
Map<String, CodegenModel> modelsByClassName = new HashMap<>();
for (Object _outer : objs.values()) {
Map<String, Object> outer = (Map<String, Object>)_outer;
List<Map<String, Object>> models = (List<Map<String, Object>>)outer.get("models");
for (Map<String, Object> mo : models) {
CodegenModel cm = (CodegenModel)mo.get("model");
postProcessModelsEnum(outer);
cm.classVarName = camelize(cm.classVarName, true);
modelsByClassName.put(cm.classname, cm);
boolean hasFiles = cm.vars.stream().anyMatch(var -> var.isFile);
cm.vendorExtensions.put("hasFiles", hasFiles);
}
}
for (CodegenModel model : modelsByClassName.values()) {
model.defaultValue = generateModelDefaultValue(model, modelsByClassName);
}
return objs;
}
@SuppressWarnings("unchecked")
@Override
public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
objs = super.postProcessSupportingFileData(objs);
generateJSONSpecFile(objs);
// Prettify routes file
Map<String, Object> apiInfo = (Map<String, Object>)objs.get("apiInfo");
List<Map<String, Object>> apis = (List<Map<String, Object>>)apiInfo.get("apis");
List<CodegenOperation> ops = apis.stream()
.map(api -> (Map<String, Object>)api.get("operations"))
.flatMap(operations -> ((List<CodegenOperation>)operations.get("operation")).stream())
.collect(Collectors.toList());
int maxPathLength = ops.stream()
.mapToInt(op -> op.httpMethod.length() + op.path.length())
.reduce(0, Integer::max);
ops.forEach(op -> op.vendorExtensions.put("paddedPath", rightPad(op.path, maxPathLength - op.httpMethod.length())));
ops.forEach(op -> op.vendorExtensions.put("hasPathParams", op.getHasPathParams()));
return objs;
}
@Override
public String getSchemaType(Schema p) {
String openAPIType = super.getSchemaType(p);
openAPIType = getAlias(openAPIType);
// don't apply renaming on types from the typeMapping
if (typeMapping.containsKey(openAPIType)) {
return typeMapping.get(openAPIType);
}
if (null == openAPIType) {
LOGGER.error("No Type defined for Schema " + p);
}
return toModelName(openAPIType);
}
@Override
public String toDefaultValue(Schema p) {
if (p.getRequired() != null && p.getRequired().contains(p.getName())) {
return "None";
}
if (p.getDefault() != null) {
return p.getDefault().toString();
}
if (ModelUtils.isBooleanSchema(p)) {
return "false";
}
if (ModelUtils.isDateSchema(p)) {
return "LocalDate.now";
}
if (ModelUtils.isDateTimeSchema(p)) {
return "OffsetDateTime.now";
}
if (ModelUtils.isDoubleSchema(p)) {
return "0.0";
}
if (ModelUtils.isFloatSchema(p)) {
return "0.0F";
}
if (ModelUtils.isIntegerSchema(p)) {
return "0";
}
if (ModelUtils.isLongSchema(p)) {
return "0L";
}
if (ModelUtils.isStringSchema(p)) {
return "\"\"";
}
if (ModelUtils.isMapSchema(p)) {
Schema ap = ModelUtils.getAdditionalProperties(p);
String inner = getSchemaType(ap);
return "Map.empty[String, " + inner + "]";
}
if (ModelUtils.isArraySchema(p)) {
Schema items = ((ArraySchema)p).getItems();
String inner = getSchemaType(items);
return "List.empty[" + inner + "]";
}
return "null";
}
@Override
public String toEnumName(CodegenProperty property) {
return camelize(property.name);
}
@Override
public String toEnumVarName(String value, String datatype) {
if (value.length() == 0) {
return "EMPTY";
}
String var = camelize(value.replaceAll("\\W+", "_"));
if (var.matches("\\d.*")) {
return "_" + var;
} else {
return var;
}
}
private void addCliOptionWithDefault(String name, String description, boolean defaultValue) {
cliOptions.add(CliOption.newBoolean(name, description).defaultValue(Boolean.toString(defaultValue)));
}
private String getBasePackagePath() {
return String.format(Locale.ROOT, "%s/%s", sourceFolder, basePackage.replace(".", File.separator));
}
private String generateModelDefaultValue(CodegenModel cm, Map<String, CodegenModel> models) {
StringBuilder defaultValue = new StringBuilder();
defaultValue.append(cm.classname).append('(');
for (CodegenProperty var : cm.vars) {
if (!var.required) {
defaultValue.append("None");
} else if (models.containsKey(var.dataType)) {
defaultValue.append(generateModelDefaultValue(models.get(var.dataType), models));
} else if (var.defaultValue != null) {
defaultValue.append(var.defaultValue);
} else if (var.isEnum) {
defaultValue.append(cm.classname).append('.').append(var.enumName).append(".values.head");
} else {
LOGGER.warn("Unknown default value for var {0} in class {1}", var.name, cm.classname);
defaultValue.append("null");
}
if (var.hasMore) {
defaultValue.append(", ");
}
}
if (cm.isMapModel) {
defaultValue.append(", Map.empty");
}
defaultValue.append(')');
return defaultValue.toString();
}
}

View File

@ -88,6 +88,7 @@ org.openapitools.codegen.languages.ScalaAkkaClientCodegen
org.openapitools.codegen.languages.ScalaHttpClientCodegen
org.openapitools.codegen.languages.ScalaGatlingCodegen
org.openapitools.codegen.languages.ScalaLagomServerCodegen
org.openapitools.codegen.languages.ScalaPlayFrameworkServerCodegen
org.openapitools.codegen.languages.ScalazClientCodegen
org.openapitools.codegen.languages.SpringCodegen
org.openapitools.codegen.languages.StaticDocCodegen

View File

@ -0,0 +1,32 @@
# {{&appName}}
{{&appDescription}}
{{^hideGenerationTimestamp}}
This Scala Play Framework project was generated by the OpenAPI generator tool at {{generatedDate}}.
{{/hideGenerationTimestamp}}
{{#generateApis}}
## API
{{#apiInfo}}
{{#apis}}
### {{baseName}}
|Name|Role|
|----|----|
|`{{importPath}}Controller`|Play Framework API controller|
|`{{importPath}}Api`|Representing trait|
{{^skipStubs}}
|`{{importPath}}ApiImpl`|Default implementation|
{{/skipStubs}}
{{#operations}}
{{#operation}}
* `{{httpMethod}} {{contextPath}}{{path}}{{#queryParams.0}}?{{/queryParams.0}}{{#queryParams}}{{paramName}}=[value]{{#hasMore}}&{{/hasMore}}{{/queryParams}}` - {{summary}}
{{/operation}}
{{/operations}}
{{/apis}}
{{/apiInfo}}
{{/generateApis}}

View File

@ -0,0 +1,108 @@
package {{package}}
{{#generateCustomExceptions}}
import {{basePackage}}.OpenApiExceptions
{{/generateCustomExceptions}}
import javax.inject.{Inject, Singleton}
import play.api.libs.json._
import play.api.mvc._
{{#supportAsync}}
import scala.concurrent.{ExecutionContext, Future}
{{/supportAsync}}
{{#imports}}
import {{import}}
{{/imports}}
{{#operations}}
{{>generatedAnnotation}}
@Singleton
class {{classname}}Controller @Inject()(cc: ControllerComponents, api: {{classname}}){{#supportAsync}}(implicit executionContext: ExecutionContext){{/supportAsync}} extends AbstractController(cc) {
{{#operation}}
/**
* {{httpMethod}} {{contextPath}}{{path}}{{#queryParams.0}}?{{/queryParams.0}}{{#queryParams}}{{paramName}}=[value]{{#hasMore}}&{{/hasMore}}{{/queryParams}}
{{#pathParams}}
{{#description}}
* @param {{paramName}} {{description}}
{{/description}}
{{/pathParams}}
*/
def {{operationId}}({{#pathParams}}{{paramName}}: {{dataType}}{{#hasMore}}, {{/hasMore}}{{/pathParams}}): Action[AnyContent] = Action{{#supportAsync}}.async{{/supportAsync}} { request =>
{{! Keep the execution result in its own block to prevent redeclaration of parameter names (however unlikely that might be). }}
def executeApi(): {{>returnTypeOrUnit}} = {
{{#bodyParams}}
val {{paramName}} = request.body.asJson.map(_.as[{{dataType}}]){{#required}}.getOrElse {
{{#generateCustomExceptions}}
throw new OpenApiExceptions.MissingRequiredParameterException("body", "{{paramName}}")
{{/generateCustomExceptions}}
{{^generateCustomExceptions}}
throw new IllegalArgumentException("Missing required body parameter.")
{{/generateCustomExceptions}}
}{{/required}}
{{/bodyParams}}
{{#headerParams}}
val {{paramName}} = request.headers.get("{{baseName}}")
{{#lambda.indented_8}}{{>app/transformParamValues}}{{/lambda.indented_8}}
{{/headerParams}}
{{#queryParams}}
val {{paramName}} = request.{{#isCollectionFormatMulti}}queryString.get("{{baseName}}"){{/isCollectionFormatMulti}}{{^isCollectionFormatMulti}}getQueryString("{{baseName}}"){{/isCollectionFormatMulti}}
{{#lambda.indented_8}}{{>app/transformParamValues}}{{/lambda.indented_8}}
{{/queryParams}}
{{#formParams}}
{{#isFile}}
val {{paramName}} = request.body.asMultipartFormData.flatMap(_.file("{{baseName}}").map(_.ref: {{dataType}}))
{{#lambda.indented_8}}{{>app/transformParamValues}}{{/lambda.indented_8}}
{{/isFile}}
{{^isFile}}
{{! TODO: Check if this fallback is required }}
val {{paramName}} = (request.body.asMultipartFormData.map(_.asFormUrlEncoded) orElse request.body.asFormUrlEncoded)
.flatMap(_.get("{{baseName}}"))
{{^isCollectionFormatMulti}}
.flatMap(_.headOption)
{{/isCollectionFormatMulti}}
{{#lambda.indented_8}}{{>app/transformParamValues}}{{/lambda.indented_8}}
{{/isFile}}
{{/formParams}}
api.{{operationId}}({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}})
}
{{#supportAsync}}
executeApi().map { {{#returnType}}result{{/returnType}}{{^returnType}}_{{/returnType}} =>
{{/supportAsync}}
{{^supportAsync}}
{{#returnType}}
val result = executeApi()
{{/returnType}}
{{^returnType}}
executeApi()
{{/returnType}}
{{/supportAsync}}
{{#returnType}}
{{#supportAsync}} {{/supportAsync}}val json = Json.toJson(result)
{{#supportAsync}} {{/supportAsync}}Ok(json)
{{/returnType}}
{{^returnType}}
{{#supportAsync}} {{/supportAsync}}Ok
{{/returnType}}
{{#supportAsync}}
}
{{/supportAsync}}
}{{#hasMore}}
{{/hasMore}}
{{/operation}}
private def splitCollectionParam(paramValues: String, collectionFormat: String): List[String] = {
{{! Note: `+` is used to filter empty values when splitting }}
val splitBy =
collectionFormat match {
case "csv" => ",+"
case "tsv" => "\t+"
case "ssv" => " +"
case "pipes" => "|+"
}
paramValues.split(splitBy).toList
}
}
{{/operations}}

View File

@ -0,0 +1,11 @@
package {{apiPackage}}
import javax.inject.{Inject, Singleton}
import play.api.mvc._
@Singleton
class ApiDocController @Inject()(cc: ControllerComponents) extends AbstractController(cc) {
def api: Action[AnyContent] = Action {
Redirect("/assets/lib/swagger-ui/index.html?/url=/assets/openapi.json")
}
}

View File

@ -0,0 +1,30 @@
package {{package}}
{{#supportAsync}}
import scala.concurrent.Future
{{/supportAsync}}
{{#imports}}
import {{import}}
{{/imports}}
{{#operations}}
/**
* Provides a default implementation for [[{{classname}}]].
*/
{{>generatedAnnotation}}
class {{classname}}Impl extends {{classname}} {
{{#operation}}
/**
* @inheritdoc
*/
override {{>app/defOperationSignature}} = {
// TODO: Implement better logic
{{#supportAsync}}Future.successful({{/supportAsync}}{{^returnType}}{{#supportAsync}}(): Unit{{/supportAsync}}{{/returnType}}{{#returnType}}{{&defaultResponse}}{{/returnType}}{{#supportAsync}}){{/supportAsync}}
}
{{#hasMore}}
{{/hasMore}}
{{/operation}}
}
{{/operations}}

View File

@ -0,0 +1,33 @@
package {{package}}
{{#supportAsync}}
import scala.concurrent.Future
{{/supportAsync}}
{{#imports}}
import {{import}}
{{/imports}}
{{#operations}}
{{>generatedAnnotation}}
trait {{classname}} {
{{#operation}}
/**
{{#summary}}
* {{summary}}
{{/summary}}
{{#notes}}
* {{notes}}
{{/notes}}
{{#allParams}}
{{#description}}
* @param {{paramName}} {{description}}
{{/description}}
{{/allParams}}
*/
{{>app/defOperationSignature}}
{{#hasMore}}
{{/hasMore}}
{{/operation}}
}
{{/operations}}

View File

@ -0,0 +1 @@
def {{operationId}}({{#allParams}}{{paramName}}: {{^required}}Option[{{/required}}{{dataType}}{{^required}}]{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}): {{>returnTypeOrUnit}}

View File

@ -0,0 +1,25 @@
package {{basePackage}}
import play.api.http.DefaultHttpErrorHandler
import play.api.libs.json.JsResultException
import play.api.mvc.Results._
import play.api.mvc.{RequestHeader, Result}
import scala.concurrent.Future
class ErrorHandler extends DefaultHttpErrorHandler {
override def onServerError(request: RequestHeader, e: Throwable): Future[Result] = e match {
{{#generateCustomExceptions}}
case _: OpenApiExceptions.MissingRequiredParameterException =>
{{/generateCustomExceptions}}
{{^generateCustomExceptions}}
case _: IllegalArgumentException =>
{{/generateCustomExceptions}}
Future.successful(BadRequest(e.getMessage))
case _: JsResultException =>
Future.successful(BadRequest(e.getMessage))
case _ =>
// Handles dev mode properly, or otherwise returns internal server error in production mode
super.onServerError(request, e)
}
}

View File

@ -0,0 +1,5 @@
package {{basePackage}}
object OpenApiExceptions {
class MissingRequiredParameterException(paramName: String, paramType: String) extends Exception(s"Missing required $paramType parameter `$paramName`.")
}

View File

@ -0,0 +1,16 @@
package {{basePackage}}
import {{apiPackage}}._
import play.api.inject.{Binding, Module => PlayModule}
import play.api.{Configuration, Environment}
{{>generatedAnnotation}}
class Module extends PlayModule {
override def bindings(environment: Environment, configuration: Configuration): Seq[Binding[_]] = Seq(
{{#apiInfo}}
{{#apis}}
bind[{{classname}}].to[{{classname}}Impl]{{#hasMore}},{{/hasMore}}
{{/apis}}
{{/apiInfo}}
)
}

View File

@ -0,0 +1,30 @@
{{^isFile}}
{{#collectionFormat}}
{{^isCollectionFormatMulti}}
.map(values => splitCollectionParam(values, "{{collectionFormat}}"))
{{/isCollectionFormatMulti}}
{{#isCollectionFormatMulti}}
.map(_.toList)
{{/isCollectionFormatMulti}}
{{#items}}
{{^isString}}
.map(_.map(value => {{>convertParam}})
{{/isString}}
{{/items}}
{{/collectionFormat}}
{{^collectionFormat}}
{{^isString}}
.map(value => {{>convertParam}})
{{/isString}}
{{/collectionFormat}}
{{/isFile}}
{{#required}}
.getOrElse {
{{#generateCustomExceptions}}
throw new OpenApiExceptions.MissingRequiredParameterException("{{baseName}}", "{{#isHeaderParam}}header{{/isHeaderParam}}{{#isQueryParam}}query string{{/isQueryParam}}{{#isFormParam}}form{{/isFormParam}}")
{{/generateCustomExceptions}}
{{^generateCustomExceptions}}
throw new IllegalArgumentException("Missing {{#isHeaderParam}}header{{/isHeaderParam}}{{#isQueryParam}}query string{{/isQueryParam}}{{#isFormParam}}form{{/isFormParam}} parameter `{{baseName}}`.")
{{/generateCustomExceptions}}
}
{{/required}}

View File

@ -0,0 +1,7 @@
case class {{classname}}(items: {{parent}})
object {{classname}} {
implicit lazy val {{classVarName}}JsonFormat: Format[{{classname}}] = {
{{#lambda.indented_4}}{{>simpleParentJsonFormat}}{{/lambda.indented_4}}
}
}

View File

@ -0,0 +1,16 @@
organization := "{{groupId}}"
version := "{{artifactVersion}}"
name := "{{artifactId}}"
scalaVersion := "2.12.6"
enablePlugins(PlayScala)
libraryDependencies ++= Seq(
guice,
ws,
{{#useSwaggerUI}}
"org.webjars" % "swagger-ui" % "3.1.5",
{{/useSwaggerUI}}
"org.scalatest" %% "scalatest" % "3.0.4" % Test,
"org.scalatestplus.play" %% "scalatestplus-play" % "3.1.2" % Test
)

View File

@ -0,0 +1,35 @@
case class {{classname}}(
{{#vars}}
{{&name}}: {{^required}}Option[{{/required}}{{#isEnum}}{{classname}}.{{enumName}}.Value{{/isEnum}}{{^isEnum}}{{dataType}}{{/isEnum}}{{^required}}]{{/required}}{{#hasMore}},{{/hasMore}}{{^hasMore}}{{#isMapModel}},{{/isMapModel}}{{/hasMore}}
{{/vars}}
{{#isMapModel}}
additionalProperties: {{parent}}
{{/isMapModel}}
)
object {{classname}} {
{{^vendorExtensions.hasFiles}}
implicit lazy val {{classVarName}}JsonFormat: Format[{{classname}}] = {{^isMapModel}}Json.format[{{classname}}]{{/isMapModel}}{{#isMapModel}}{
{{#lambda.indented_4}}{{>extensibleObjectJsonFormat}}{{/lambda.indented_4}}
}{{/isMapModel}}
{{/vendorExtensions.hasFiles}}
{{#vendorExtensions.hasFiles}}
// NOTE: The JSON format for {{classname}} was not generated because it contains a file variable which cannot be encoded to JSON.
{{/vendorExtensions.hasFiles}}
{{#vars}}
{{#isEnum}}
// noinspection TypeAnnotation
object {{enumName}} extends Enumeration {
{{#allowableValues}}
{{#enumVars}}
val {{name}} = Value({{&value}})
{{/enumVars}}
{{/allowableValues}}
type {{enumName}} = Value
implicit lazy val {{enumName}}JsonFormat: Format[Value] = Format(Reads.enumNameReads(this), Writes.enumNameWrites[this.type])
}
{{/isEnum}}
{{/vars}}
}

View File

@ -0,0 +1,17 @@
# This is the main configuration file for the application.
# Refer to https://www.playframework.com/documentation/latest/ConfigFile for more information.
play {
filters.headers.contentSecurityPolicy = null
modules.enabled += "{{basePackage}}.Module"
http {
secret.key = "changeme"
errorHandler = "{{basePackage}}.ErrorHandler"
}
assets {
path = "/public"
urlPrefix = "/assets"
}
}

View File

@ -0,0 +1,41 @@
<!-- https://www.playframework.com/documentation/latest/SettingsLogger -->
<configuration>
<conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${application.home:-.}/logs/application.log</file>
<encoder>
<pattern>%date [%level] from %logger in %thread - %message%n%xException</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%coloredLevel %logger{15} - %message%n%xException{10}</pattern>
</encoder>
</appender>
<appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE" />
</appender>
<appender name="ASYNCSTDOUT" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="STDOUT" />
</appender>
<logger name="play" level="INFO" />
<logger name="application" level="DEBUG" />
<!-- Off these ones as they are annoying, and anyway we manage configuration ourselves -->
<logger name="com.avaje.ebean.config.PropertyMapLoader" level="OFF" />
<logger name="com.avaje.ebeaninternal.server.core.XmlConfigLoader" level="OFF" />
<logger name="com.avaje.ebeaninternal.server.lib.BackgroundThread" level="OFF" />
<logger name="com.gargoylesoftware.htmlunit.javascript" level="OFF" />
<root level="WARN">
<appender-ref ref="ASYNCFILE" />
<appender-ref ref="ASYNCSTDOUT" />
</root>
</configuration>

View File

@ -0,0 +1,35 @@
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
{{#apiInfo}}
{{#apis}}
# Routes for {{{baseName}}} API
{{! Note: operations with path parameters are emitted last to give them lower priority than operations with possibly more specific paths. }}
{{#operations}}
{{#operation}}
{{^vendorExtensions.hasPathParams}}
{{httpMethod}} {{{contextPath}}}{{{vendorExtensions.paddedPath}}} {{apiPackage}}.{{classname}}Controller.{{operationId}}()
{{/vendorExtensions.hasPathParams}}
{{/operation}}
{{/operations}}
{{#operations}}
{{#operation}}
{{#vendorExtensions.hasPathParams}}
{{httpMethod}} {{{contextPath}}}{{{vendorExtensions.paddedPath}}} {{apiPackage}}.{{classname}}Controller.{{operationId}}({{#pathParams}}{{paramName}}: {{#isUuid}}java.util.UUID{{/isUuid}}{{^isUuid}}{{{dataType}}}{{/isUuid}}{{#hasMore}}, {{/hasMore}}{{/pathParams}})
{{/vendorExtensions.hasPathParams}}
{{/operation}}
{{/operations}}
{{/apis}}
{{/apiInfo}}
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.at(file)
GET /versionedAssets/*file controllers.Assets.versioned(file)
{{#useSwaggerUI}}
# Swagger UI
GET /api {{apiPackage}}.ApiDocController.api
{{/useSwaggerUI}}

View File

@ -0,0 +1 @@
{{#isBoolean}}value.toBoolean{{/isBoolean}}{{#isInteger}}value.toInt{{/isInteger}}{{#isDouble}}value.toDouble{{/isDouble}}{{#isLong}}value.toLong{{/isLong}}{{#isFloat}}value.toFloat{{/isFloat}}{{#isUuid}}UUID.fromString(value){{/isUuid}}{{#isDateTime}}OffsetDateTime.parse(value){{/isDateTime}}{{#isDate}}LocalDate.parse(value){{/isDate}}{{#isByteArray}}value.getBytes(){{/isByteArray}}{{#isNumber}}BigDecimal(value){{/isNumber}}

View File

@ -0,0 +1 @@
{{^required}}Option[{{/required}}{{dataType}}{{^required}}]{{/required}}

View File

@ -0,0 +1,25 @@
{{#hasVars}}
val realJsonFormat = Json.format[{{classname}}]
val declaredPropNames = Set({{#vars}}"{{&name}}"{{#hasMore}}, {{/hasMore}}{{/vars}})
Format(
Reads {
case JsObject(xs) =>
val declaredProps = xs.filterKeys(declaredPropNames)
val additionalProps = JsObject(xs -- declaredPropNames)
val restructuredProps = declaredProps + ("additionalProperties" -> additionalProps)
val newObj = JsObject(restructuredProps)
realJsonFormat.reads(newObj)
case _ =>
JsError("error.expected.jsobject")
},
Writes { {{classVarName}} =>
val jsObj = realJsonFormat.writes({{classVarName}})
val additionalProps = jsObj.value("additionalProperties").as[JsObject]
val declaredProps = jsObj - "additionalProperties"
val newObj = declaredProps ++ additionalProps
newObj
}
){{/hasVars}}{{^hasVars}}
{{>simpleParentJsonFormat}}
{{/hasVars}}

View File

@ -0,0 +1 @@
{{^hideGenerationTimestamp}}@javax.annotation.Generated(value = Array("{{generatorClass}}"), date = "{{generatedDate}}"){{/hideGenerationTimestamp}}

View File

@ -0,0 +1,28 @@
package {{package}}
import play.api.libs.json._
{{#imports}}
import {{import}}
{{/imports}}
{{#models}}
{{#model}}
/**
* {{#description}}{{&.}}{{/description}}{{^description}}Represents the Swagger definition for {{name}}.{{/description}}{{#vars}}{{#description}}
* @param {{&name}} {{&description}}{{/description}}{{/vars}}
{{#isArrayModel}}
* @param items The items of this array-like object.
{{/isArrayModel}}
{{#isMapModel}}
* @param additionalProperties Any additional properties this model may have.
{{/isMapModel}}
*/
{{>generatedAnnotation}}
{{^isArrayModel}}
{{>caseClass}}
{{/isArrayModel}}
{{#isArrayModel}}
{{>arrayCaseClass}}
{{/isArrayModel}}
{{/model}}
{{/models}}

View File

@ -0,0 +1 @@
sbt.version=1.2.4

View File

@ -0,0 +1 @@
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.18")

View File

@ -0,0 +1 @@
{{{openapi-json}}}

View File

@ -0,0 +1 @@
{{#supportAsync}}Future[{{/supportAsync}}{{^returnType}}Unit{{/returnType}}{{#returnType}}{{returnType}}{{/returnType}}{{#supportAsync}}]{{/supportAsync}}

View File

@ -0,0 +1,6 @@
val innerFormat = Format.of[{{parent}}]
Format(
innerFormat.map(inner => {{classname}}(inner)),
{{classVarName}} => innerFormat.writes({{classVarName}}.{{#isArrayModel}}items{{/isArrayModel}}{{#isMapModel}}additionalProperties{{/isMapModel}}
)

View File

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

View File

@ -0,0 +1 @@
4.0.0-SNAPSHOT

View File

@ -0,0 +1,55 @@
# 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.
This Scala Play Framework project was generated by the OpenAPI generator tool at 2019-03-26T02:45:22.426+02:00[Asia/Jerusalem].
## API
### Pet
|Name|Role|
|----|----|
|`api.PetController`|Play Framework API controller|
|`api.PetApi`|Representing trait|
|`api.PetApiImpl`|Default implementation|
* `POST /pet` - Add a new pet to the store
* `DELETE /pet/:petId` - Deletes a pet
* `GET /pet/findByStatus?status=[value]` - Finds Pets by status
* `GET /pet/findByTags?tags=[value]` - Finds Pets by tags
* `GET /pet/:petId` - Find pet by ID
* `PUT /pet` - Update an existing pet
* `POST /pet/:petId` - Updates a pet in the store with form data
* `POST /pet/:petId/uploadImage` - uploads an image
### Store
|Name|Role|
|----|----|
|`api.StoreController`|Play Framework API controller|
|`api.StoreApi`|Representing trait|
|`api.StoreApiImpl`|Default implementation|
* `DELETE /store/order/:orderId` - Delete purchase order by ID
* `GET /store/inventory` - Returns pet inventories by status
* `GET /store/order/:orderId` - Find purchase order by ID
* `POST /store/order` - Place an order for a pet
### User
|Name|Role|
|----|----|
|`api.UserController`|Play Framework API controller|
|`api.UserApi`|Representing trait|
|`api.UserApiImpl`|Default implementation|
* `POST /user` - Create user
* `POST /user/createWithArray` - Creates list of users with given input array
* `POST /user/createWithList` - Creates list of users with given input array
* `DELETE /user/:username` - Delete user
* `GET /user/:username` - Get user by user name
* `GET /user/login?username=[value]&password=[value]` - Logs user into the system
* `GET /user/logout` - Logs out current logged in user session
* `PUT /user/:username` - Updated user

View File

@ -0,0 +1,11 @@
package api
import javax.inject.{Inject, Singleton}
import play.api.mvc._
@Singleton
class ApiDocController @Inject()(cc: ControllerComponents) extends AbstractController(cc) {
def api: Action[AnyContent] = Action {
Redirect("/assets/lib/swagger-ui/index.html?/url=/assets/openapi.json")
}
}

View File

@ -0,0 +1,63 @@
package api
import model.ApiResponse
import model.Pet
import play.api.libs.Files.TemporaryFile
@javax.annotation.Generated(value = Array("org.openapitools.codegen.languages.ScalaPlayFrameworkServerCodegen"), date = "2019-03-26T02:45:22.426+02:00[Asia/Jerusalem]")
trait PetApi {
/**
* Add a new pet to the store
* @param body Pet object that needs to be added to the store
*/
def addPet(body: Pet): Unit
/**
* Deletes a pet
* @param petId Pet id to delete
*/
def deletePet(petId: Long, apiKey: Option[String]): Unit
/**
* Finds Pets by status
* Multiple status values can be provided with comma separated strings
* @param status Status values that need to be considered for filter
*/
def findPetsByStatus(status: List[String]): List[Pet]
/**
* Finds Pets by tags
* Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
* @param tags Tags to filter by
*/
def findPetsByTags(tags: List[String]): List[Pet]
/**
* Find pet by ID
* Returns a single pet
* @param petId ID of pet to return
*/
def getPetById(petId: Long): Pet
/**
* Update an existing pet
* @param body Pet object that needs to be added to the store
*/
def updatePet(body: Pet): Unit
/**
* Updates a pet in the store with form data
* @param petId ID of pet that needs to be updated
* @param name Updated name of the pet
* @param status Updated status of the pet
*/
def updatePetWithForm(petId: Long, name: Option[String], status: Option[String]): Unit
/**
* uploads an image
* @param petId ID of pet to update
* @param additionalMetadata Additional data to pass to server
* @param file file to upload
*/
def uploadFile(petId: Long, additionalMetadata: Option[String], file: Option[TemporaryFile]): ApiResponse
}

View File

@ -0,0 +1,161 @@
package api
import org.openapitools.OpenApiExceptions
import javax.inject.{Inject, Singleton}
import play.api.libs.json._
import play.api.mvc._
import model.ApiResponse
import model.Pet
import play.api.libs.Files.TemporaryFile
@javax.annotation.Generated(value = Array("org.openapitools.codegen.languages.ScalaPlayFrameworkServerCodegen"), date = "2019-03-26T02:45:22.426+02:00[Asia/Jerusalem]")
@Singleton
class PetApiController @Inject()(cc: ControllerComponents, api: PetApi) extends AbstractController(cc) {
/**
* POST /pet
*/
def addPet(): Action[AnyContent] = Action { request =>
def executeApi(): Unit = {
val body = request.body.asJson.map(_.as[Pet]).getOrElse {
throw new OpenApiExceptions.MissingRequiredParameterException("body", "body")
}
api.addPet(body)
}
executeApi()
Ok
}
/**
* DELETE /pet/:petId
* @param petId Pet id to delete
*/
def deletePet(petId: Long): Action[AnyContent] = Action { request =>
def executeApi(): Unit = {
val apiKey = request.headers.get("api_key")
api.deletePet(petId, apiKey)
}
executeApi()
Ok
}
/**
* GET /pet/findByStatus?status=[value]
*/
def findPetsByStatus(): Action[AnyContent] = Action { request =>
def executeApi(): List[Pet] = {
val status = request.getQueryString("status")
.map(values => splitCollectionParam(values, "csv"))
.getOrElse {
throw new OpenApiExceptions.MissingRequiredParameterException("status", "query string")
}
api.findPetsByStatus(status)
}
val result = executeApi()
val json = Json.toJson(result)
Ok(json)
}
/**
* GET /pet/findByTags?tags=[value]
*/
def findPetsByTags(): Action[AnyContent] = Action { request =>
def executeApi(): List[Pet] = {
val tags = request.getQueryString("tags")
.map(values => splitCollectionParam(values, "csv"))
.getOrElse {
throw new OpenApiExceptions.MissingRequiredParameterException("tags", "query string")
}
api.findPetsByTags(tags)
}
val result = executeApi()
val json = Json.toJson(result)
Ok(json)
}
/**
* GET /pet/:petId
* @param petId ID of pet to return
*/
def getPetById(petId: Long): Action[AnyContent] = Action { request =>
def executeApi(): Pet = {
api.getPetById(petId)
}
val result = executeApi()
val json = Json.toJson(result)
Ok(json)
}
/**
* PUT /pet
*/
def updatePet(): Action[AnyContent] = Action { request =>
def executeApi(): Unit = {
val body = request.body.asJson.map(_.as[Pet]).getOrElse {
throw new OpenApiExceptions.MissingRequiredParameterException("body", "body")
}
api.updatePet(body)
}
executeApi()
Ok
}
/**
* POST /pet/:petId
* @param petId ID of pet that needs to be updated
*/
def updatePetWithForm(petId: Long): Action[AnyContent] = Action { request =>
def executeApi(): Unit = {
val name = (request.body.asMultipartFormData.map(_.asFormUrlEncoded) orElse request.body.asFormUrlEncoded)
.flatMap(_.get("name"))
.flatMap(_.headOption)
val status = (request.body.asMultipartFormData.map(_.asFormUrlEncoded) orElse request.body.asFormUrlEncoded)
.flatMap(_.get("status"))
.flatMap(_.headOption)
api.updatePetWithForm(petId, name, status)
}
executeApi()
Ok
}
/**
* POST /pet/:petId/uploadImage
* @param petId ID of pet to update
*/
def uploadFile(petId: Long): Action[AnyContent] = Action { request =>
def executeApi(): ApiResponse = {
val additionalMetadata = (request.body.asMultipartFormData.map(_.asFormUrlEncoded) orElse request.body.asFormUrlEncoded)
.flatMap(_.get("additionalMetadata"))
.flatMap(_.headOption)
val file = request.body.asMultipartFormData.flatMap(_.file("file").map(_.ref: TemporaryFile))
api.uploadFile(petId, additionalMetadata, file)
}
val result = executeApi()
val json = Json.toJson(result)
Ok(json)
}
private def splitCollectionParam(paramValues: String, collectionFormat: String): List[String] = {
val splitBy =
collectionFormat match {
case "csv" => ",+"
case "tsv" => "\t+"
case "ssv" => " +"
case "pipes" => "|+"
}
paramValues.split(splitBy).toList
}
}

View File

@ -0,0 +1,83 @@
package api
import model.ApiResponse
import model.Pet
import play.api.libs.Files.TemporaryFile
/**
* Provides a default implementation for [[PetApi]].
*/
@javax.annotation.Generated(value = Array("org.openapitools.codegen.languages.ScalaPlayFrameworkServerCodegen"), date = "2019-03-26T02:45:22.426+02:00[Asia/Jerusalem]")
class PetApiImpl extends PetApi {
/**
* @inheritdoc
*/
override def addPet(body: Pet): Unit = {
// TODO: Implement better logic
}
/**
* @inheritdoc
*/
override def deletePet(petId: Long, apiKey: Option[String]): Unit = {
// TODO: Implement better logic
}
/**
* @inheritdoc
*/
override def findPetsByStatus(status: List[String]): List[Pet] = {
// TODO: Implement better logic
List.empty[Pet]
}
/**
* @inheritdoc
*/
override def findPetsByTags(tags: List[String]): List[Pet] = {
// TODO: Implement better logic
List.empty[Pet]
}
/**
* @inheritdoc
*/
override def getPetById(petId: Long): Pet = {
// TODO: Implement better logic
Pet(None, None, "", List.empty[String], None, None)
}
/**
* @inheritdoc
*/
override def updatePet(body: Pet): Unit = {
// TODO: Implement better logic
}
/**
* @inheritdoc
*/
override def updatePetWithForm(petId: Long, name: Option[String], status: Option[String]): Unit = {
// TODO: Implement better logic
}
/**
* @inheritdoc
*/
override def uploadFile(petId: Long, additionalMetadata: Option[String], file: Option[TemporaryFile]): ApiResponse = {
// TODO: Implement better logic
ApiResponse(None, None, None)
}
}

View File

@ -0,0 +1,32 @@
package api
import model.Order
@javax.annotation.Generated(value = Array("org.openapitools.codegen.languages.ScalaPlayFrameworkServerCodegen"), date = "2019-03-26T02:45:22.426+02:00[Asia/Jerusalem]")
trait StoreApi {
/**
* Delete purchase order by ID
* For valid response try integer IDs with value &lt; 1000. Anything above 1000 or nonintegers will generate API errors
* @param orderId ID of the order that needs to be deleted
*/
def deleteOrder(orderId: String): Unit
/**
* Returns pet inventories by status
* Returns a map of status codes to quantities
*/
def getInventory(): Map[String, Int]
/**
* Find purchase order by ID
* For valid response try integer IDs with value &lt;&#x3D; 5 or &gt; 10. Other values will generated exceptions
* @param orderId ID of pet that needs to be fetched
*/
def getOrderById(orderId: Long): Order
/**
* Place an order for a pet
* @param body order placed for purchasing the pet
*/
def placeOrder(body: Order): Order
}

View File

@ -0,0 +1,79 @@
package api
import org.openapitools.OpenApiExceptions
import javax.inject.{Inject, Singleton}
import play.api.libs.json._
import play.api.mvc._
import model.Order
@javax.annotation.Generated(value = Array("org.openapitools.codegen.languages.ScalaPlayFrameworkServerCodegen"), date = "2019-03-26T02:45:22.426+02:00[Asia/Jerusalem]")
@Singleton
class StoreApiController @Inject()(cc: ControllerComponents, api: StoreApi) extends AbstractController(cc) {
/**
* DELETE /store/order/:orderId
* @param orderId ID of the order that needs to be deleted
*/
def deleteOrder(orderId: String): Action[AnyContent] = Action { request =>
def executeApi(): Unit = {
api.deleteOrder(orderId)
}
executeApi()
Ok
}
/**
* GET /store/inventory
*/
def getInventory(): Action[AnyContent] = Action { request =>
def executeApi(): Map[String, Int] = {
api.getInventory()
}
val result = executeApi()
val json = Json.toJson(result)
Ok(json)
}
/**
* GET /store/order/:orderId
* @param orderId ID of pet that needs to be fetched
*/
def getOrderById(orderId: Long): Action[AnyContent] = Action { request =>
def executeApi(): Order = {
api.getOrderById(orderId)
}
val result = executeApi()
val json = Json.toJson(result)
Ok(json)
}
/**
* POST /store/order
*/
def placeOrder(): Action[AnyContent] = Action { request =>
def executeApi(): Order = {
val body = request.body.asJson.map(_.as[Order]).getOrElse {
throw new OpenApiExceptions.MissingRequiredParameterException("body", "body")
}
api.placeOrder(body)
}
val result = executeApi()
val json = Json.toJson(result)
Ok(json)
}
private def splitCollectionParam(paramValues: String, collectionFormat: String): List[String] = {
val splitBy =
collectionFormat match {
case "csv" => ",+"
case "tsv" => "\t+"
case "ssv" => " +"
case "pipes" => "|+"
}
paramValues.split(splitBy).toList
}
}

View File

@ -0,0 +1,45 @@
package api
import model.Order
/**
* Provides a default implementation for [[StoreApi]].
*/
@javax.annotation.Generated(value = Array("org.openapitools.codegen.languages.ScalaPlayFrameworkServerCodegen"), date = "2019-03-26T02:45:22.426+02:00[Asia/Jerusalem]")
class StoreApiImpl extends StoreApi {
/**
* @inheritdoc
*/
override def deleteOrder(orderId: String): Unit = {
// TODO: Implement better logic
}
/**
* @inheritdoc
*/
override def getInventory(): Map[String, Int] = {
// TODO: Implement better logic
Map.empty[String, Int]
}
/**
* @inheritdoc
*/
override def getOrderById(orderId: Long): Order = {
// TODO: Implement better logic
Order(None, None, None, None, None, None)
}
/**
* @inheritdoc
*/
override def placeOrder(body: Order): Order = {
// TODO: Implement better logic
Order(None, None, None, None, None, None)
}
}

View File

@ -0,0 +1,58 @@
package api
import model.User
@javax.annotation.Generated(value = Array("org.openapitools.codegen.languages.ScalaPlayFrameworkServerCodegen"), date = "2019-03-26T02:45:22.426+02:00[Asia/Jerusalem]")
trait UserApi {
/**
* Create user
* This can only be done by the logged in user.
* @param body Created user object
*/
def createUser(body: User): Unit
/**
* Creates list of users with given input array
* @param body List of user object
*/
def createUsersWithArrayInput(body: List[User]): Unit
/**
* Creates list of users with given input array
* @param body List of user object
*/
def createUsersWithListInput(body: List[User]): Unit
/**
* Delete user
* This can only be done by the logged in user.
* @param username The name that needs to be deleted
*/
def deleteUser(username: String): Unit
/**
* Get user by user name
* @param username The name that needs to be fetched. Use user1 for testing.
*/
def getUserByName(username: String): User
/**
* Logs user into the system
* @param username The user name for login
* @param password The password for login in clear text
*/
def loginUser(username: String, password: String): String
/**
* Logs out current logged in user session
*/
def logoutUser(): Unit
/**
* Updated user
* This can only be done by the logged in user.
* @param username name that need to be deleted
* @param body Updated user object
*/
def updateUser(username: String, body: User): Unit
}

View File

@ -0,0 +1,144 @@
package api
import org.openapitools.OpenApiExceptions
import javax.inject.{Inject, Singleton}
import play.api.libs.json._
import play.api.mvc._
import model.User
@javax.annotation.Generated(value = Array("org.openapitools.codegen.languages.ScalaPlayFrameworkServerCodegen"), date = "2019-03-26T02:45:22.426+02:00[Asia/Jerusalem]")
@Singleton
class UserApiController @Inject()(cc: ControllerComponents, api: UserApi) extends AbstractController(cc) {
/**
* POST /user
*/
def createUser(): Action[AnyContent] = Action { request =>
def executeApi(): Unit = {
val body = request.body.asJson.map(_.as[User]).getOrElse {
throw new OpenApiExceptions.MissingRequiredParameterException("body", "body")
}
api.createUser(body)
}
executeApi()
Ok
}
/**
* POST /user/createWithArray
*/
def createUsersWithArrayInput(): Action[AnyContent] = Action { request =>
def executeApi(): Unit = {
val body = request.body.asJson.map(_.as[List[User]]).getOrElse {
throw new OpenApiExceptions.MissingRequiredParameterException("body", "body")
}
api.createUsersWithArrayInput(body)
}
executeApi()
Ok
}
/**
* POST /user/createWithList
*/
def createUsersWithListInput(): Action[AnyContent] = Action { request =>
def executeApi(): Unit = {
val body = request.body.asJson.map(_.as[List[User]]).getOrElse {
throw new OpenApiExceptions.MissingRequiredParameterException("body", "body")
}
api.createUsersWithListInput(body)
}
executeApi()
Ok
}
/**
* DELETE /user/:username
* @param username The name that needs to be deleted
*/
def deleteUser(username: String): Action[AnyContent] = Action { request =>
def executeApi(): Unit = {
api.deleteUser(username)
}
executeApi()
Ok
}
/**
* GET /user/:username
* @param username The name that needs to be fetched. Use user1 for testing.
*/
def getUserByName(username: String): Action[AnyContent] = Action { request =>
def executeApi(): User = {
api.getUserByName(username)
}
val result = executeApi()
val json = Json.toJson(result)
Ok(json)
}
/**
* GET /user/login?username=[value]&password=[value]
*/
def loginUser(): Action[AnyContent] = Action { request =>
def executeApi(): String = {
val username = request.getQueryString("username")
.getOrElse {
throw new OpenApiExceptions.MissingRequiredParameterException("username", "query string")
}
val password = request.getQueryString("password")
.getOrElse {
throw new OpenApiExceptions.MissingRequiredParameterException("password", "query string")
}
api.loginUser(username, password)
}
val result = executeApi()
val json = Json.toJson(result)
Ok(json)
}
/**
* GET /user/logout
*/
def logoutUser(): Action[AnyContent] = Action { request =>
def executeApi(): Unit = {
api.logoutUser()
}
executeApi()
Ok
}
/**
* PUT /user/:username
* @param username name that need to be deleted
*/
def updateUser(username: String): Action[AnyContent] = Action { request =>
def executeApi(): Unit = {
val body = request.body.asJson.map(_.as[User]).getOrElse {
throw new OpenApiExceptions.MissingRequiredParameterException("body", "body")
}
api.updateUser(username, body)
}
executeApi()
Ok
}
private def splitCollectionParam(paramValues: String, collectionFormat: String): List[String] = {
val splitBy =
collectionFormat match {
case "csv" => ",+"
case "tsv" => "\t+"
case "ssv" => " +"
case "pipes" => "|+"
}
paramValues.split(splitBy).toList
}
}

View File

@ -0,0 +1,81 @@
package api
import model.User
/**
* Provides a default implementation for [[UserApi]].
*/
@javax.annotation.Generated(value = Array("org.openapitools.codegen.languages.ScalaPlayFrameworkServerCodegen"), date = "2019-03-26T02:45:22.426+02:00[Asia/Jerusalem]")
class UserApiImpl extends UserApi {
/**
* @inheritdoc
*/
override def createUser(body: User): Unit = {
// TODO: Implement better logic
}
/**
* @inheritdoc
*/
override def createUsersWithArrayInput(body: List[User]): Unit = {
// TODO: Implement better logic
}
/**
* @inheritdoc
*/
override def createUsersWithListInput(body: List[User]): Unit = {
// TODO: Implement better logic
}
/**
* @inheritdoc
*/
override def deleteUser(username: String): Unit = {
// TODO: Implement better logic
}
/**
* @inheritdoc
*/
override def getUserByName(username: String): User = {
// TODO: Implement better logic
User(None, None, None, None, None, None, None, None)
}
/**
* @inheritdoc
*/
override def loginUser(username: String, password: String): String = {
// TODO: Implement better logic
""
}
/**
* @inheritdoc
*/
override def logoutUser(): Unit = {
// TODO: Implement better logic
}
/**
* @inheritdoc
*/
override def updateUser(username: String, body: User): Unit = {
// TODO: Implement better logic
}
}

View File

@ -0,0 +1,18 @@
package model
import play.api.libs.json._
/**
* Describes the result of uploading an image resource
*/
@javax.annotation.Generated(value = Array("org.openapitools.codegen.languages.ScalaPlayFrameworkServerCodegen"), date = "2019-03-26T02:45:22.426+02:00[Asia/Jerusalem]")
case class ApiResponse(
code: Option[Int],
`type`: Option[String],
message: Option[String]
)
object ApiResponse {
implicit lazy val apiResponseJsonFormat: Format[ApiResponse] = Json.format[ApiResponse]
}

View File

@ -0,0 +1,17 @@
package model
import play.api.libs.json._
/**
* A category for a pet
*/
@javax.annotation.Generated(value = Array("org.openapitools.codegen.languages.ScalaPlayFrameworkServerCodegen"), date = "2019-03-26T02:45:22.426+02:00[Asia/Jerusalem]")
case class Category(
id: Option[Long],
name: Option[String]
)
object Category {
implicit lazy val categoryJsonFormat: Format[Category] = Json.format[Category]
}

View File

@ -0,0 +1,33 @@
package model
import play.api.libs.json._
import java.time.OffsetDateTime
/**
* An order for a pets from the pet store
* @param status Order Status
*/
@javax.annotation.Generated(value = Array("org.openapitools.codegen.languages.ScalaPlayFrameworkServerCodegen"), date = "2019-03-26T02:45:22.426+02:00[Asia/Jerusalem]")
case class Order(
id: Option[Long],
petId: Option[Long],
quantity: Option[Int],
shipDate: Option[OffsetDateTime],
status: Option[Order.Status.Value],
complete: Option[Boolean]
)
object Order {
implicit lazy val orderJsonFormat: Format[Order] = Json.format[Order]
// noinspection TypeAnnotation
object Status extends Enumeration {
val Placed = Value("placed")
val Approved = Value("approved")
val Delivered = Value("delivered")
type Status = Value
implicit lazy val StatusJsonFormat: Format[Value] = Format(Reads.enumNameReads(this), Writes.enumNameWrites[this.type])
}
}

View File

@ -0,0 +1,32 @@
package model
import play.api.libs.json._
/**
* A pet for sale in the pet store
* @param status pet status in the store
*/
@javax.annotation.Generated(value = Array("org.openapitools.codegen.languages.ScalaPlayFrameworkServerCodegen"), date = "2019-03-26T02:45:22.426+02:00[Asia/Jerusalem]")
case class Pet(
id: Option[Long],
category: Option[Category],
name: String,
photoUrls: List[String],
tags: Option[List[Tag]],
status: Option[Pet.Status.Value]
)
object Pet {
implicit lazy val petJsonFormat: Format[Pet] = Json.format[Pet]
// noinspection TypeAnnotation
object Status extends Enumeration {
val Available = Value("available")
val Pending = Value("pending")
val Sold = Value("sold")
type Status = Value
implicit lazy val StatusJsonFormat: Format[Value] = Format(Reads.enumNameReads(this), Writes.enumNameWrites[this.type])
}
}

View File

@ -0,0 +1,17 @@
package model
import play.api.libs.json._
/**
* A tag for a pet
*/
@javax.annotation.Generated(value = Array("org.openapitools.codegen.languages.ScalaPlayFrameworkServerCodegen"), date = "2019-03-26T02:45:22.426+02:00[Asia/Jerusalem]")
case class Tag(
id: Option[Long],
name: Option[String]
)
object Tag {
implicit lazy val tagJsonFormat: Format[Tag] = Json.format[Tag]
}

View File

@ -0,0 +1,24 @@
package model
import play.api.libs.json._
/**
* A User who is purchasing from the pet store
* @param userStatus User Status
*/
@javax.annotation.Generated(value = Array("org.openapitools.codegen.languages.ScalaPlayFrameworkServerCodegen"), date = "2019-03-26T02:45:22.426+02:00[Asia/Jerusalem]")
case class User(
id: Option[Long],
username: Option[String],
firstName: Option[String],
lastName: Option[String],
email: Option[String],
password: Option[String],
phone: Option[String],
userStatus: Option[Int]
)
object User {
implicit lazy val userJsonFormat: Format[User] = Json.format[User]
}

View File

@ -0,0 +1,20 @@
package org.openapitools
import play.api.http.DefaultHttpErrorHandler
import play.api.libs.json.JsResultException
import play.api.mvc.Results._
import play.api.mvc.{RequestHeader, Result}
import scala.concurrent.Future
class ErrorHandler extends DefaultHttpErrorHandler {
override def onServerError(request: RequestHeader, e: Throwable): Future[Result] = e match {
case _: OpenApiExceptions.MissingRequiredParameterException =>
Future.successful(BadRequest(e.getMessage))
case _: JsResultException =>
Future.successful(BadRequest(e.getMessage))
case _ =>
// Handles dev mode properly, or otherwise returns internal server error in production mode
super.onServerError(request, e)
}
}

View File

@ -0,0 +1,14 @@
package org.openapitools
import api._
import play.api.inject.{Binding, Module => PlayModule}
import play.api.{Configuration, Environment}
@javax.annotation.Generated(value = Array("org.openapitools.codegen.languages.ScalaPlayFrameworkServerCodegen"), date = "2019-03-26T02:45:22.426+02:00[Asia/Jerusalem]")
class Module extends PlayModule {
override def bindings(environment: Environment, configuration: Configuration): Seq[Binding[_]] = Seq(
bind[PetApi].to[PetApiImpl],
bind[StoreApi].to[StoreApiImpl],
bind[UserApi].to[UserApiImpl]
)
}

View File

@ -0,0 +1,5 @@
package org.openapitools
object OpenApiExceptions {
class MissingRequiredParameterException(paramName: String, paramType: String) extends Exception(s"Missing required $paramType parameter `$paramName`.")
}

View File

@ -0,0 +1,14 @@
organization := ""
version := ""
name := ""
scalaVersion := "2.12.6"
enablePlugins(PlayScala)
libraryDependencies ++= Seq(
guice,
ws,
"org.webjars" % "swagger-ui" % "3.1.5",
"org.scalatest" %% "scalatest" % "3.0.4" % Test,
"org.scalatestplus.play" %% "scalatestplus-play" % "3.1.2" % Test
)

View File

@ -0,0 +1,17 @@
# This is the main configuration file for the application.
# Refer to https://www.playframework.com/documentation/latest/ConfigFile for more information.
play {
filters.headers.contentSecurityPolicy = null
modules.enabled += "org.openapitools.Module"
http {
secret.key = "changeme"
errorHandler = "org.openapitools.ErrorHandler"
}
assets {
path = "/public"
urlPrefix = "/assets"
}
}

View File

@ -0,0 +1,41 @@
<!-- https://www.playframework.com/documentation/latest/SettingsLogger -->
<configuration>
<conversionRule conversionWord="coloredLevel" converterClass="play.api.libs.logback.ColoredLevel" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${application.home:-.}/logs/application.log</file>
<encoder>
<pattern>%date [%level] from %logger in %thread - %message%n%xException</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%coloredLevel %logger{15} - %message%n%xException{10}</pattern>
</encoder>
</appender>
<appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE" />
</appender>
<appender name="ASYNCSTDOUT" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="STDOUT" />
</appender>
<logger name="play" level="INFO" />
<logger name="application" level="DEBUG" />
<!-- Off these ones as they are annoying, and anyway we manage configuration ourselves -->
<logger name="com.avaje.ebean.config.PropertyMapLoader" level="OFF" />
<logger name="com.avaje.ebeaninternal.server.core.XmlConfigLoader" level="OFF" />
<logger name="com.avaje.ebeaninternal.server.lib.BackgroundThread" level="OFF" />
<logger name="com.gargoylesoftware.htmlunit.javascript" level="OFF" />
<root level="WARN">
<appender-ref ref="ASYNCFILE" />
<appender-ref ref="ASYNCSTDOUT" />
</root>
</configuration>

View File

@ -0,0 +1,40 @@
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
# Routes for Pet API
POST /v2/pet api.PetApiController.addPet()
GET /v2/pet/findByStatus api.PetApiController.findPetsByStatus()
GET /v2/pet/findByTags api.PetApiController.findPetsByTags()
PUT /v2/pet api.PetApiController.updatePet()
DELETE /v2/pet/:petId api.PetApiController.deletePet(petId: Long)
GET /v2/pet/:petId api.PetApiController.getPetById(petId: Long)
POST /v2/pet/:petId api.PetApiController.updatePetWithForm(petId: Long)
POST /v2/pet/:petId/uploadImage api.PetApiController.uploadFile(petId: Long)
# Routes for Store API
GET /v2/store/inventory api.StoreApiController.getInventory()
POST /v2/store/order api.StoreApiController.placeOrder()
DELETE /v2/store/order/:orderId api.StoreApiController.deleteOrder(orderId: String)
GET /v2/store/order/:orderId api.StoreApiController.getOrderById(orderId: Long)
# Routes for User API
POST /v2/user api.UserApiController.createUser()
POST /v2/user/createWithArray api.UserApiController.createUsersWithArrayInput()
POST /v2/user/createWithList api.UserApiController.createUsersWithListInput()
GET /v2/user/login api.UserApiController.loginUser()
GET /v2/user/logout api.UserApiController.logoutUser()
DELETE /v2/user/:username api.UserApiController.deleteUser(username: String)
GET /v2/user/:username api.UserApiController.getUserByName(username: String)
PUT /v2/user/:username api.UserApiController.updateUser(username: String)
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.at(file)
GET /versionedAssets/*file controllers.Assets.versioned(file)
# Swagger UI
GET /api api.ApiDocController.api

View File

@ -0,0 +1 @@
sbt.version=1.2.4

View File

@ -0,0 +1 @@
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.18")

File diff suppressed because it is too large Load Diff