From 28ae33cb1387fdf1b3ceccc9ee5f8f21d5bc3a19 Mon Sep 17 00:00:00 2001 From: Adi Gerber <16128031+adigerber@users.noreply.github.com> Date: Tue, 26 Mar 2019 10:04:48 +0200 Subject: [PATCH] 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 --- bin/scala-play-framework-petstore.sh | 32 + bin/windows/scala-play-framework-petstore.bat | 10 + .../ScalaPlayFrameworkServerCodegen.java | 401 +++++++ .../org.openapitools.codegen.CodegenConfig | 1 + .../scala-play-framework/README.md.mustache | 32 + .../app/apiController.scala.mustache | 108 ++ .../app/apiDocController.scala.mustache | 11 + .../app/apiImplStubs.scala.mustache | 30 + .../app/apiTrait.scala.mustache | 33 + .../app/defOperationSignature.mustache | 1 + .../app/errorHandler.scala.mustache | 25 + .../app/exceptions.scala.mustache | 5 + .../app/module.scala.mustache | 16 + .../app/transformParamValues.mustache | 30 + .../arrayCaseClass.mustache | 7 + .../scala-play-framework/build.sbt.mustache | 16 + .../scala-play-framework/caseClass.mustache | 35 + .../conf/application.conf.mustache | 17 + .../conf/logback.xml.mustache | 41 + .../scala-play-framework/conf/routes.mustache | 35 + .../convertParam.mustache | 1 + .../dataTypeOption.mustache | 1 + .../extensibleObjectJsonFormat.mustache | 25 + .../generatedAnnotation.mustache | 1 + .../scala-play-framework/model.mustache | 28 + .../project/build.properties.mustache | 1 + .../project/plugins.sbt.mustache | 1 + .../public/openapi.json.mustache | 1 + .../returnTypeOrUnit.mustache | 1 + .../simpleParentJsonFormat.mustache | 6 + .../.openapi-generator-ignore | 23 + .../.openapi-generator/VERSION | 1 + .../petstore/scala-play-framework/README.md | 55 + .../app/api/ApiDocController.scala | 11 + .../scala-play-framework/app/api/PetApi.scala | 63 + .../app/api/PetApiController.scala | 161 +++ .../app/api/PetApiImpl.scala | 83 ++ .../app/api/StoreApi.scala | 32 + .../app/api/StoreApiController.scala | 79 ++ .../app/api/StoreApiImpl.scala | 45 + .../app/api/UserApi.scala | 58 + .../app/api/UserApiController.scala | 144 +++ .../app/api/UserApiImpl.scala | 81 ++ .../app/model/ApiResponse.scala | 18 + .../app/model/Category.scala | 17 + .../app/model/Order.scala | 33 + .../scala-play-framework/app/model/Pet.scala | 32 + .../scala-play-framework/app/model/Tag.scala | 17 + .../scala-play-framework/app/model/User.scala | 24 + .../app/org/openapitools/ErrorHandler.scala | 20 + .../app/org/openapitools/Module.scala | 14 + .../org/openapitools/OpenApiExceptions.scala | 5 + .../petstore/scala-play-framework/build.sbt | 14 + .../conf/application.conf | 17 + .../scala-play-framework/conf/logback.xml | 41 + .../petstore/scala-play-framework/conf/routes | 40 + .../project/build.properties | 1 + .../scala-play-framework/project/plugins.sbt | 1 + .../scala-play-framework/public/openapi.json | 1012 +++++++++++++++++ 59 files changed, 3094 insertions(+) create mode 100755 bin/scala-play-framework-petstore.sh create mode 100755 bin/windows/scala-play-framework-petstore.bat create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaPlayFrameworkServerCodegen.java create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/README.md.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/app/apiController.scala.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/app/apiDocController.scala.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/app/apiImplStubs.scala.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/app/apiTrait.scala.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/app/defOperationSignature.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/app/errorHandler.scala.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/app/exceptions.scala.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/app/module.scala.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/app/transformParamValues.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/arrayCaseClass.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/build.sbt.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/caseClass.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/conf/application.conf.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/conf/logback.xml.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/conf/routes.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/convertParam.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/dataTypeOption.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/extensibleObjectJsonFormat.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/generatedAnnotation.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/model.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/project/build.properties.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/project/plugins.sbt.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/public/openapi.json.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/returnTypeOrUnit.mustache create mode 100644 modules/openapi-generator/src/main/resources/scala-play-framework/simpleParentJsonFormat.mustache create mode 100644 samples/server/petstore/scala-play-framework/.openapi-generator-ignore create mode 100644 samples/server/petstore/scala-play-framework/.openapi-generator/VERSION create mode 100644 samples/server/petstore/scala-play-framework/README.md create mode 100644 samples/server/petstore/scala-play-framework/app/api/ApiDocController.scala create mode 100644 samples/server/petstore/scala-play-framework/app/api/PetApi.scala create mode 100644 samples/server/petstore/scala-play-framework/app/api/PetApiController.scala create mode 100644 samples/server/petstore/scala-play-framework/app/api/PetApiImpl.scala create mode 100644 samples/server/petstore/scala-play-framework/app/api/StoreApi.scala create mode 100644 samples/server/petstore/scala-play-framework/app/api/StoreApiController.scala create mode 100644 samples/server/petstore/scala-play-framework/app/api/StoreApiImpl.scala create mode 100644 samples/server/petstore/scala-play-framework/app/api/UserApi.scala create mode 100644 samples/server/petstore/scala-play-framework/app/api/UserApiController.scala create mode 100644 samples/server/petstore/scala-play-framework/app/api/UserApiImpl.scala create mode 100644 samples/server/petstore/scala-play-framework/app/model/ApiResponse.scala create mode 100644 samples/server/petstore/scala-play-framework/app/model/Category.scala create mode 100644 samples/server/petstore/scala-play-framework/app/model/Order.scala create mode 100644 samples/server/petstore/scala-play-framework/app/model/Pet.scala create mode 100644 samples/server/petstore/scala-play-framework/app/model/Tag.scala create mode 100644 samples/server/petstore/scala-play-framework/app/model/User.scala create mode 100644 samples/server/petstore/scala-play-framework/app/org/openapitools/ErrorHandler.scala create mode 100644 samples/server/petstore/scala-play-framework/app/org/openapitools/Module.scala create mode 100644 samples/server/petstore/scala-play-framework/app/org/openapitools/OpenApiExceptions.scala create mode 100644 samples/server/petstore/scala-play-framework/build.sbt create mode 100644 samples/server/petstore/scala-play-framework/conf/application.conf create mode 100644 samples/server/petstore/scala-play-framework/conf/logback.xml create mode 100644 samples/server/petstore/scala-play-framework/conf/routes create mode 100644 samples/server/petstore/scala-play-framework/project/build.properties create mode 100644 samples/server/petstore/scala-play-framework/project/plugins.sbt create mode 100644 samples/server/petstore/scala-play-framework/public/openapi.json diff --git a/bin/scala-play-framework-petstore.sh b/bin/scala-play-framework-petstore.sh new file mode 100755 index 00000000000..bbdad2fcc59 --- /dev/null +++ b/bin/scala-play-framework-petstore.sh @@ -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 diff --git a/bin/windows/scala-play-framework-petstore.bat b/bin/windows/scala-play-framework-petstore.bat new file mode 100755 index 00000000000..2f10aa0d69d --- /dev/null +++ b/bin/windows/scala-play-framework-petstore.bat @@ -0,0 +1,10 @@ +set executable=.\modules\openapi-generator-cli\target\openapi-generator-cli.jar + +If Not Exist %executable% ( + mvn clean package +) + +REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M +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% diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaPlayFrameworkServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaPlayFrameworkServerCodegen.java new file mode 100644 index 00000000000..b034b0c6f44 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaPlayFrameworkServerCodegen.java @@ -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 objs) { + Map lambdas = new ImmutableMap.Builder() + .put("indented_4", new IndentedLambda(4, " ")) + .put("indented_8", new IndentedLambda(8, " ")) + .build(); + objs.put("lambda", lambdas); + } + + @SuppressWarnings("unchecked") + @Override + public Map postProcessOperationsWithModels(Map objs, List allModels) { + Map models = new HashMap<>(); + + for (Object _mo : allModels) { + CodegenModel model = (CodegenModel)((Map)_mo).get("model"); + models.put(model.classname, model); + } + + Map operations = (Map)objs.get("operations"); + if (operations != null) { + List ops = (List)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 postProcessAllModels(Map objs) { + objs = super.postProcessAllModels(objs); + Map modelsByClassName = new HashMap<>(); + + for (Object _outer : objs.values()) { + Map outer = (Map)_outer; + List> models = (List>)outer.get("models"); + + for (Map 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 postProcessSupportingFileData(Map objs) { + objs = super.postProcessSupportingFileData(objs); + generateJSONSpecFile(objs); + + // Prettify routes file + Map apiInfo = (Map)objs.get("apiInfo"); + List> apis = (List>)apiInfo.get("apis"); + List ops = apis.stream() + .map(api -> (Map)api.get("operations")) + .flatMap(operations -> ((List)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 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(); + } +} diff --git a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig index 7fe2f245b9f..24b65ad72c3 100644 --- a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig +++ b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig @@ -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 diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/README.md.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/README.md.mustache new file mode 100644 index 00000000000..733bf107e08 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/README.md.mustache @@ -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}} diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/app/apiController.scala.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/app/apiController.scala.mustache new file mode 100644 index 00000000000..c9b77efb529 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/app/apiController.scala.mustache @@ -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}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/app/apiDocController.scala.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/app/apiDocController.scala.mustache new file mode 100644 index 00000000000..d6a85a65963 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/app/apiDocController.scala.mustache @@ -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") + } +} diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/app/apiImplStubs.scala.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/app/apiImplStubs.scala.mustache new file mode 100644 index 00000000000..f91f6bf4eab --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/app/apiImplStubs.scala.mustache @@ -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}} diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/app/apiTrait.scala.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/app/apiTrait.scala.mustache new file mode 100644 index 00000000000..d8db48e92f5 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/app/apiTrait.scala.mustache @@ -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}} diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/app/defOperationSignature.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/app/defOperationSignature.mustache new file mode 100644 index 00000000000..4e9a2bb0c28 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/app/defOperationSignature.mustache @@ -0,0 +1 @@ +def {{operationId}}({{#allParams}}{{paramName}}: {{^required}}Option[{{/required}}{{dataType}}{{^required}}]{{/required}}{{#hasMore}}, {{/hasMore}}{{/allParams}}): {{>returnTypeOrUnit}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/app/errorHandler.scala.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/app/errorHandler.scala.mustache new file mode 100644 index 00000000000..5c4c15114f2 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/app/errorHandler.scala.mustache @@ -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) + } +} diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/app/exceptions.scala.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/app/exceptions.scala.mustache new file mode 100644 index 00000000000..c1ed38dc68a --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/app/exceptions.scala.mustache @@ -0,0 +1,5 @@ +package {{basePackage}} + +object OpenApiExceptions { + class MissingRequiredParameterException(paramName: String, paramType: String) extends Exception(s"Missing required $paramType parameter `$paramName`.") +} diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/app/module.scala.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/app/module.scala.mustache new file mode 100644 index 00000000000..c8ea2de9169 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/app/module.scala.mustache @@ -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}} + ) +} diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/app/transformParamValues.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/app/transformParamValues.mustache new file mode 100644 index 00000000000..17ec4fbe31c --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/app/transformParamValues.mustache @@ -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}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/arrayCaseClass.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/arrayCaseClass.mustache new file mode 100644 index 00000000000..7fb69a3e365 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/arrayCaseClass.mustache @@ -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}} + } +} diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/build.sbt.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/build.sbt.mustache new file mode 100644 index 00000000000..6e5f6cb339c --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/build.sbt.mustache @@ -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 +) diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/caseClass.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/caseClass.mustache new file mode 100644 index 00000000000..eb76524d1aa --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/caseClass.mustache @@ -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}} +} diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/conf/application.conf.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/conf/application.conf.mustache new file mode 100644 index 00000000000..3b610494d83 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/conf/application.conf.mustache @@ -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" + } +} diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/conf/logback.xml.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/conf/logback.xml.mustache new file mode 100644 index 00000000000..01f301ab73a --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/conf/logback.xml.mustache @@ -0,0 +1,41 @@ + + + + + + + ${application.home:-.}/logs/application.log + + %date [%level] from %logger in %thread - %message%n%xException + + + + + + %coloredLevel %logger{15} - %message%n%xException{10} + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/conf/routes.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/conf/routes.mustache new file mode 100644 index 00000000000..81124723a8d --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/conf/routes.mustache @@ -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}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/convertParam.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/convertParam.mustache new file mode 100644 index 00000000000..7a31560052d --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/convertParam.mustache @@ -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}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/dataTypeOption.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/dataTypeOption.mustache new file mode 100644 index 00000000000..a3f296bc8f9 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/dataTypeOption.mustache @@ -0,0 +1 @@ +{{^required}}Option[{{/required}}{{dataType}}{{^required}}]{{/required}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/extensibleObjectJsonFormat.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/extensibleObjectJsonFormat.mustache new file mode 100644 index 00000000000..92f7f2954e4 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/extensibleObjectJsonFormat.mustache @@ -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}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/generatedAnnotation.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/generatedAnnotation.mustache new file mode 100644 index 00000000000..9b5d96902a8 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/generatedAnnotation.mustache @@ -0,0 +1 @@ +{{^hideGenerationTimestamp}}@javax.annotation.Generated(value = Array("{{generatorClass}}"), date = "{{generatedDate}}"){{/hideGenerationTimestamp}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/model.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/model.mustache new file mode 100644 index 00000000000..4e5cc13d6fe --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/model.mustache @@ -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}} diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/project/build.properties.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/project/build.properties.mustache new file mode 100644 index 00000000000..6d441921c88 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/project/build.properties.mustache @@ -0,0 +1 @@ +sbt.version=1.2.4 diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/project/plugins.sbt.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/project/plugins.sbt.mustache new file mode 100644 index 00000000000..3121e6ba95f --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/project/plugins.sbt.mustache @@ -0,0 +1 @@ +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.18") diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/public/openapi.json.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/public/openapi.json.mustache new file mode 100644 index 00000000000..7710186d9d5 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/public/openapi.json.mustache @@ -0,0 +1 @@ +{{{openapi-json}}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/returnTypeOrUnit.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/returnTypeOrUnit.mustache new file mode 100644 index 00000000000..d7a19289a4e --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/returnTypeOrUnit.mustache @@ -0,0 +1 @@ +{{#supportAsync}}Future[{{/supportAsync}}{{^returnType}}Unit{{/returnType}}{{#returnType}}{{returnType}}{{/returnType}}{{#supportAsync}}]{{/supportAsync}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/scala-play-framework/simpleParentJsonFormat.mustache b/modules/openapi-generator/src/main/resources/scala-play-framework/simpleParentJsonFormat.mustache new file mode 100644 index 00000000000..60a21e6727c --- /dev/null +++ b/modules/openapi-generator/src/main/resources/scala-play-framework/simpleParentJsonFormat.mustache @@ -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}} +) \ No newline at end of file diff --git a/samples/server/petstore/scala-play-framework/.openapi-generator-ignore b/samples/server/petstore/scala-play-framework/.openapi-generator-ignore new file mode 100644 index 00000000000..7484ee590a3 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/samples/server/petstore/scala-play-framework/.openapi-generator/VERSION b/samples/server/petstore/scala-play-framework/.openapi-generator/VERSION new file mode 100644 index 00000000000..afa63656064 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/.openapi-generator/VERSION @@ -0,0 +1 @@ +4.0.0-SNAPSHOT \ No newline at end of file diff --git a/samples/server/petstore/scala-play-framework/README.md b/samples/server/petstore/scala-play-framework/README.md new file mode 100644 index 00000000000..9950d875db7 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/README.md @@ -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 + diff --git a/samples/server/petstore/scala-play-framework/app/api/ApiDocController.scala b/samples/server/petstore/scala-play-framework/app/api/ApiDocController.scala new file mode 100644 index 00000000000..c86ec2b87b9 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/app/api/ApiDocController.scala @@ -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") + } +} diff --git a/samples/server/petstore/scala-play-framework/app/api/PetApi.scala b/samples/server/petstore/scala-play-framework/app/api/PetApi.scala new file mode 100644 index 00000000000..4a9dc26ac20 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/app/api/PetApi.scala @@ -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 +} diff --git a/samples/server/petstore/scala-play-framework/app/api/PetApiController.scala b/samples/server/petstore/scala-play-framework/app/api/PetApiController.scala new file mode 100644 index 00000000000..16765acc4a0 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/app/api/PetApiController.scala @@ -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 + } +} diff --git a/samples/server/petstore/scala-play-framework/app/api/PetApiImpl.scala b/samples/server/petstore/scala-play-framework/app/api/PetApiImpl.scala new file mode 100644 index 00000000000..0852695805d --- /dev/null +++ b/samples/server/petstore/scala-play-framework/app/api/PetApiImpl.scala @@ -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) + } +} diff --git a/samples/server/petstore/scala-play-framework/app/api/StoreApi.scala b/samples/server/petstore/scala-play-framework/app/api/StoreApi.scala new file mode 100644 index 00000000000..a71dfb6f327 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/app/api/StoreApi.scala @@ -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 < 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 <= 5 or > 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 +} diff --git a/samples/server/petstore/scala-play-framework/app/api/StoreApiController.scala b/samples/server/petstore/scala-play-framework/app/api/StoreApiController.scala new file mode 100644 index 00000000000..b20c32a447c --- /dev/null +++ b/samples/server/petstore/scala-play-framework/app/api/StoreApiController.scala @@ -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 + } +} diff --git a/samples/server/petstore/scala-play-framework/app/api/StoreApiImpl.scala b/samples/server/petstore/scala-play-framework/app/api/StoreApiImpl.scala new file mode 100644 index 00000000000..60b6b8362b9 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/app/api/StoreApiImpl.scala @@ -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) + } +} diff --git a/samples/server/petstore/scala-play-framework/app/api/UserApi.scala b/samples/server/petstore/scala-play-framework/app/api/UserApi.scala new file mode 100644 index 00000000000..1a1718cd923 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/app/api/UserApi.scala @@ -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 +} diff --git a/samples/server/petstore/scala-play-framework/app/api/UserApiController.scala b/samples/server/petstore/scala-play-framework/app/api/UserApiController.scala new file mode 100644 index 00000000000..8c943f7b3cb --- /dev/null +++ b/samples/server/petstore/scala-play-framework/app/api/UserApiController.scala @@ -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 + } +} diff --git a/samples/server/petstore/scala-play-framework/app/api/UserApiImpl.scala b/samples/server/petstore/scala-play-framework/app/api/UserApiImpl.scala new file mode 100644 index 00000000000..a65243f1df3 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/app/api/UserApiImpl.scala @@ -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 + + + } +} diff --git a/samples/server/petstore/scala-play-framework/app/model/ApiResponse.scala b/samples/server/petstore/scala-play-framework/app/model/ApiResponse.scala new file mode 100644 index 00000000000..8e9079158b6 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/app/model/ApiResponse.scala @@ -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] +} + diff --git a/samples/server/petstore/scala-play-framework/app/model/Category.scala b/samples/server/petstore/scala-play-framework/app/model/Category.scala new file mode 100644 index 00000000000..a4273d39bf1 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/app/model/Category.scala @@ -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] +} + diff --git a/samples/server/petstore/scala-play-framework/app/model/Order.scala b/samples/server/petstore/scala-play-framework/app/model/Order.scala new file mode 100644 index 00000000000..7088d37f5bb --- /dev/null +++ b/samples/server/petstore/scala-play-framework/app/model/Order.scala @@ -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]) + } +} + diff --git a/samples/server/petstore/scala-play-framework/app/model/Pet.scala b/samples/server/petstore/scala-play-framework/app/model/Pet.scala new file mode 100644 index 00000000000..39f2d010ea0 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/app/model/Pet.scala @@ -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]) + } +} + diff --git a/samples/server/petstore/scala-play-framework/app/model/Tag.scala b/samples/server/petstore/scala-play-framework/app/model/Tag.scala new file mode 100644 index 00000000000..ae2ad81d6aa --- /dev/null +++ b/samples/server/petstore/scala-play-framework/app/model/Tag.scala @@ -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] +} + diff --git a/samples/server/petstore/scala-play-framework/app/model/User.scala b/samples/server/petstore/scala-play-framework/app/model/User.scala new file mode 100644 index 00000000000..2fb57e273a2 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/app/model/User.scala @@ -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] +} + diff --git a/samples/server/petstore/scala-play-framework/app/org/openapitools/ErrorHandler.scala b/samples/server/petstore/scala-play-framework/app/org/openapitools/ErrorHandler.scala new file mode 100644 index 00000000000..61c1f772430 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/app/org/openapitools/ErrorHandler.scala @@ -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) + } +} diff --git a/samples/server/petstore/scala-play-framework/app/org/openapitools/Module.scala b/samples/server/petstore/scala-play-framework/app/org/openapitools/Module.scala new file mode 100644 index 00000000000..d7b95b71fa2 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/app/org/openapitools/Module.scala @@ -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] + ) +} diff --git a/samples/server/petstore/scala-play-framework/app/org/openapitools/OpenApiExceptions.scala b/samples/server/petstore/scala-play-framework/app/org/openapitools/OpenApiExceptions.scala new file mode 100644 index 00000000000..319c8f52783 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/app/org/openapitools/OpenApiExceptions.scala @@ -0,0 +1,5 @@ +package org.openapitools + +object OpenApiExceptions { + class MissingRequiredParameterException(paramName: String, paramType: String) extends Exception(s"Missing required $paramType parameter `$paramName`.") +} diff --git a/samples/server/petstore/scala-play-framework/build.sbt b/samples/server/petstore/scala-play-framework/build.sbt new file mode 100644 index 00000000000..5d0c149bee4 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/build.sbt @@ -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 +) diff --git a/samples/server/petstore/scala-play-framework/conf/application.conf b/samples/server/petstore/scala-play-framework/conf/application.conf new file mode 100644 index 00000000000..d84d2ce80df --- /dev/null +++ b/samples/server/petstore/scala-play-framework/conf/application.conf @@ -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" + } +} diff --git a/samples/server/petstore/scala-play-framework/conf/logback.xml b/samples/server/petstore/scala-play-framework/conf/logback.xml new file mode 100644 index 00000000000..01f301ab73a --- /dev/null +++ b/samples/server/petstore/scala-play-framework/conf/logback.xml @@ -0,0 +1,41 @@ + + + + + + + ${application.home:-.}/logs/application.log + + %date [%level] from %logger in %thread - %message%n%xException + + + + + + %coloredLevel %logger{15} - %message%n%xException{10} + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/server/petstore/scala-play-framework/conf/routes b/samples/server/petstore/scala-play-framework/conf/routes new file mode 100644 index 00000000000..70bb287b068 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/conf/routes @@ -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 diff --git a/samples/server/petstore/scala-play-framework/project/build.properties b/samples/server/petstore/scala-play-framework/project/build.properties new file mode 100644 index 00000000000..6d441921c88 --- /dev/null +++ b/samples/server/petstore/scala-play-framework/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.2.4 diff --git a/samples/server/petstore/scala-play-framework/project/plugins.sbt b/samples/server/petstore/scala-play-framework/project/plugins.sbt new file mode 100644 index 00000000000..3121e6ba95f --- /dev/null +++ b/samples/server/petstore/scala-play-framework/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.18") diff --git a/samples/server/petstore/scala-play-framework/public/openapi.json b/samples/server/petstore/scala-play-framework/public/openapi.json new file mode 100644 index 00000000000..c683401cf0e --- /dev/null +++ b/samples/server/petstore/scala-play-framework/public/openapi.json @@ -0,0 +1,1012 @@ +{ + "openapi" : "3.0.1", + "info" : { + "title" : "OpenAPI Petstore", + "description" : "This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.", + "license" : { + "name" : "Apache-2.0", + "url" : "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version" : "1.0.0" + }, + "servers" : [ { + "url" : "http://petstore.swagger.io/v2" + } ], + "tags" : [ { + "name" : "pet", + "description" : "Everything about your Pets" + }, { + "name" : "store", + "description" : "Access to Petstore orders" + }, { + "name" : "user", + "description" : "Operations about user" + } ], + "paths" : { + "/pet" : { + "put" : { + "tags" : [ "pet" ], + "summary" : "Update an existing pet", + "operationId" : "updatePet", + "requestBody" : { + "description" : "Pet object that needs to be added to the store", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Pet" + } + }, + "application/xml" : { + "schema" : { + "$ref" : "#/components/schemas/Pet" + } + } + }, + "required" : true + }, + "responses" : { + "400" : { + "description" : "Invalid ID supplied", + "content" : { } + }, + "404" : { + "description" : "Pet not found", + "content" : { } + }, + "405" : { + "description" : "Validation exception", + "content" : { } + } + }, + "security" : [ { + "petstore_auth" : [ "write:pets", "read:pets" ] + } ], + "x-codegen-request-body-name" : "body" + }, + "post" : { + "tags" : [ "pet" ], + "summary" : "Add a new pet to the store", + "operationId" : "addPet", + "requestBody" : { + "description" : "Pet object that needs to be added to the store", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Pet" + } + }, + "application/xml" : { + "schema" : { + "$ref" : "#/components/schemas/Pet" + } + } + }, + "required" : true + }, + "responses" : { + "405" : { + "description" : "Invalid input", + "content" : { } + } + }, + "security" : [ { + "petstore_auth" : [ "write:pets", "read:pets" ] + } ], + "x-codegen-request-body-name" : "body" + } + }, + "/pet/findByStatus" : { + "get" : { + "tags" : [ "pet" ], + "summary" : "Finds Pets by status", + "description" : "Multiple status values can be provided with comma separated strings", + "operationId" : "findPetsByStatus", + "parameters" : [ { + "name" : "status", + "in" : "query", + "description" : "Status values that need to be considered for filter", + "required" : true, + "style" : "form", + "explode" : false, + "schema" : { + "type" : "array", + "items" : { + "type" : "string", + "enum" : [ "available", "pending", "sold" ], + "default" : "available" + } + } + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "content" : { + "application/xml" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Pet" + } + } + }, + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Pet" + } + } + } + } + }, + "400" : { + "description" : "Invalid status value", + "content" : { } + } + }, + "security" : [ { + "petstore_auth" : [ "write:pets", "read:pets" ] + } ] + } + }, + "/pet/findByTags" : { + "get" : { + "tags" : [ "pet" ], + "summary" : "Finds Pets by tags", + "description" : "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "operationId" : "findPetsByTags", + "parameters" : [ { + "name" : "tags", + "in" : "query", + "description" : "Tags to filter by", + "required" : true, + "style" : "form", + "explode" : false, + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + } + } + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "content" : { + "application/xml" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Pet" + } + } + }, + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Pet" + } + } + } + } + }, + "400" : { + "description" : "Invalid tag value", + "content" : { } + } + }, + "deprecated" : true, + "security" : [ { + "petstore_auth" : [ "write:pets", "read:pets" ] + } ] + } + }, + "/pet/{petId}" : { + "get" : { + "tags" : [ "pet" ], + "summary" : "Find pet by ID", + "description" : "Returns a single pet", + "operationId" : "getPetById", + "parameters" : [ { + "name" : "petId", + "in" : "path", + "description" : "ID of pet to return", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "content" : { + "application/xml" : { + "schema" : { + "$ref" : "#/components/schemas/Pet" + } + }, + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Pet" + } + } + } + }, + "400" : { + "description" : "Invalid ID supplied", + "content" : { } + }, + "404" : { + "description" : "Pet not found", + "content" : { } + } + }, + "security" : [ { + "api_key" : [ ] + } ] + }, + "post" : { + "tags" : [ "pet" ], + "summary" : "Updates a pet in the store with form data", + "operationId" : "updatePetWithForm", + "parameters" : [ { + "name" : "petId", + "in" : "path", + "description" : "ID of pet that needs to be updated", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "requestBody" : { + "content" : { + "application/x-www-form-urlencoded" : { + "schema" : { + "properties" : { + "name" : { + "type" : "string", + "description" : "Updated name of the pet" + }, + "status" : { + "type" : "string", + "description" : "Updated status of the pet" + } + } + } + } + } + }, + "responses" : { + "405" : { + "description" : "Invalid input", + "content" : { } + } + }, + "security" : [ { + "petstore_auth" : [ "write:pets", "read:pets" ] + } ] + }, + "delete" : { + "tags" : [ "pet" ], + "summary" : "Deletes a pet", + "operationId" : "deletePet", + "parameters" : [ { + "name" : "api_key", + "in" : "header", + "schema" : { + "type" : "string" + } + }, { + "name" : "petId", + "in" : "path", + "description" : "Pet id to delete", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "responses" : { + "400" : { + "description" : "Invalid pet value", + "content" : { } + } + }, + "security" : [ { + "petstore_auth" : [ "write:pets", "read:pets" ] + } ] + } + }, + "/pet/{petId}/uploadImage" : { + "post" : { + "tags" : [ "pet" ], + "summary" : "uploads an image", + "operationId" : "uploadFile", + "parameters" : [ { + "name" : "petId", + "in" : "path", + "description" : "ID of pet to update", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64" + } + } ], + "requestBody" : { + "content" : { + "multipart/form-data" : { + "schema" : { + "properties" : { + "additionalMetadata" : { + "type" : "string", + "description" : "Additional data to pass to server" + }, + "file" : { + "type" : "string", + "description" : "file to upload", + "format" : "binary" + } + } + } + } + } + }, + "responses" : { + "200" : { + "description" : "successful operation", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ApiResponse" + } + } + } + } + }, + "security" : [ { + "petstore_auth" : [ "write:pets", "read:pets" ] + } ] + } + }, + "/store/inventory" : { + "get" : { + "tags" : [ "store" ], + "summary" : "Returns pet inventories by status", + "description" : "Returns a map of status codes to quantities", + "operationId" : "getInventory", + "responses" : { + "200" : { + "description" : "successful operation", + "content" : { + "application/json" : { + "schema" : { + "type" : "object", + "additionalProperties" : { + "type" : "integer", + "format" : "int32" + } + } + } + } + } + }, + "security" : [ { + "api_key" : [ ] + } ] + } + }, + "/store/order" : { + "post" : { + "tags" : [ "store" ], + "summary" : "Place an order for a pet", + "operationId" : "placeOrder", + "requestBody" : { + "description" : "order placed for purchasing the pet", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/Order" + } + } + }, + "required" : true + }, + "responses" : { + "200" : { + "description" : "successful operation", + "content" : { + "application/xml" : { + "schema" : { + "$ref" : "#/components/schemas/Order" + } + }, + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Order" + } + } + } + }, + "400" : { + "description" : "Invalid Order", + "content" : { } + } + }, + "x-codegen-request-body-name" : "body" + } + }, + "/store/order/{orderId}" : { + "get" : { + "tags" : [ "store" ], + "summary" : "Find purchase order by ID", + "description" : "For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions", + "operationId" : "getOrderById", + "parameters" : [ { + "name" : "orderId", + "in" : "path", + "description" : "ID of pet that needs to be fetched", + "required" : true, + "schema" : { + "maximum" : 5, + "minimum" : 1, + "type" : "integer", + "format" : "int64" + } + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "content" : { + "application/xml" : { + "schema" : { + "$ref" : "#/components/schemas/Order" + } + }, + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Order" + } + } + } + }, + "400" : { + "description" : "Invalid ID supplied", + "content" : { } + }, + "404" : { + "description" : "Order not found", + "content" : { } + } + } + }, + "delete" : { + "tags" : [ "store" ], + "summary" : "Delete purchase order by ID", + "description" : "For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors", + "operationId" : "deleteOrder", + "parameters" : [ { + "name" : "orderId", + "in" : "path", + "description" : "ID of the order that needs to be deleted", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "400" : { + "description" : "Invalid ID supplied", + "content" : { } + }, + "404" : { + "description" : "Order not found", + "content" : { } + } + } + } + }, + "/user" : { + "post" : { + "tags" : [ "user" ], + "summary" : "Create user", + "description" : "This can only be done by the logged in user.", + "operationId" : "createUser", + "requestBody" : { + "description" : "Created user object", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/User" + } + } + }, + "required" : true + }, + "responses" : { + "default" : { + "description" : "successful operation", + "content" : { } + } + }, + "x-codegen-request-body-name" : "body" + } + }, + "/user/createWithArray" : { + "post" : { + "tags" : [ "user" ], + "summary" : "Creates list of users with given input array", + "operationId" : "createUsersWithArrayInput", + "requestBody" : { + "description" : "List of user object", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/User" + } + } + } + }, + "required" : true + }, + "responses" : { + "default" : { + "description" : "successful operation", + "content" : { } + } + }, + "x-codegen-request-body-name" : "body" + } + }, + "/user/createWithList" : { + "post" : { + "tags" : [ "user" ], + "summary" : "Creates list of users with given input array", + "operationId" : "createUsersWithListInput", + "requestBody" : { + "description" : "List of user object", + "content" : { + "*/*" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/User" + } + } + } + }, + "required" : true + }, + "responses" : { + "default" : { + "description" : "successful operation", + "content" : { } + } + }, + "x-codegen-request-body-name" : "body" + } + }, + "/user/login" : { + "get" : { + "tags" : [ "user" ], + "summary" : "Logs user into the system", + "operationId" : "loginUser", + "parameters" : [ { + "name" : "username", + "in" : "query", + "description" : "The user name for login", + "required" : true, + "schema" : { + "type" : "string" + } + }, { + "name" : "password", + "in" : "query", + "description" : "The password for login in clear text", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "headers" : { + "X-Rate-Limit" : { + "description" : "calls per hour allowed by the user", + "schema" : { + "type" : "integer", + "format" : "int32" + } + }, + "X-Expires-After" : { + "description" : "date in UTC when toekn expires", + "schema" : { + "type" : "string", + "format" : "date-time" + } + } + }, + "content" : { + "application/xml" : { + "schema" : { + "type" : "string" + } + }, + "application/json" : { + "schema" : { + "type" : "string" + } + } + } + }, + "400" : { + "description" : "Invalid username/password supplied", + "content" : { } + } + } + } + }, + "/user/logout" : { + "get" : { + "tags" : [ "user" ], + "summary" : "Logs out current logged in user session", + "operationId" : "logoutUser", + "responses" : { + "default" : { + "description" : "successful operation", + "content" : { } + } + } + } + }, + "/user/{username}" : { + "get" : { + "tags" : [ "user" ], + "summary" : "Get user by user name", + "operationId" : "getUserByName", + "parameters" : [ { + "name" : "username", + "in" : "path", + "description" : "The name that needs to be fetched. Use user1 for testing.", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "content" : { + "application/xml" : { + "schema" : { + "$ref" : "#/components/schemas/User" + } + }, + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/User" + } + } + } + }, + "400" : { + "description" : "Invalid username supplied", + "content" : { } + }, + "404" : { + "description" : "User not found", + "content" : { } + } + } + }, + "put" : { + "tags" : [ "user" ], + "summary" : "Updated user", + "description" : "This can only be done by the logged in user.", + "operationId" : "updateUser", + "parameters" : [ { + "name" : "username", + "in" : "path", + "description" : "name that need to be deleted", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "Updated user object", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/User" + } + } + }, + "required" : true + }, + "responses" : { + "400" : { + "description" : "Invalid user supplied", + "content" : { } + }, + "404" : { + "description" : "User not found", + "content" : { } + } + }, + "x-codegen-request-body-name" : "body" + }, + "delete" : { + "tags" : [ "user" ], + "summary" : "Delete user", + "description" : "This can only be done by the logged in user.", + "operationId" : "deleteUser", + "parameters" : [ { + "name" : "username", + "in" : "path", + "description" : "The name that needs to be deleted", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "400" : { + "description" : "Invalid username supplied", + "content" : { } + }, + "404" : { + "description" : "User not found", + "content" : { } + } + } + } + } + }, + "components" : { + "schemas" : { + "Order" : { + "title" : "Pet Order", + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int64" + }, + "petId" : { + "type" : "integer", + "format" : "int64" + }, + "quantity" : { + "type" : "integer", + "format" : "int32" + }, + "shipDate" : { + "type" : "string", + "format" : "date-time" + }, + "status" : { + "type" : "string", + "description" : "Order Status", + "enum" : [ "placed", "approved", "delivered" ] + }, + "complete" : { + "type" : "boolean", + "default" : false + } + }, + "description" : "An order for a pets from the pet store", + "example" : { + "petId" : 6, + "quantity" : 1, + "id" : 0, + "shipDate" : "2000-01-23T04:56:07.000+00:00", + "complete" : false, + "status" : "placed" + }, + "xml" : { + "name" : "Order" + } + }, + "Category" : { + "title" : "Pet category", + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int64" + }, + "name" : { + "type" : "string" + } + }, + "description" : "A category for a pet", + "example" : { + "name" : "name", + "id" : 6 + }, + "xml" : { + "name" : "Category" + } + }, + "User" : { + "title" : "a User", + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int64" + }, + "username" : { + "type" : "string" + }, + "firstName" : { + "type" : "string" + }, + "lastName" : { + "type" : "string" + }, + "email" : { + "type" : "string" + }, + "password" : { + "type" : "string" + }, + "phone" : { + "type" : "string" + }, + "userStatus" : { + "type" : "integer", + "description" : "User Status", + "format" : "int32" + } + }, + "description" : "A User who is purchasing from the pet store", + "example" : { + "firstName" : "firstName", + "lastName" : "lastName", + "password" : "password", + "userStatus" : 6, + "phone" : "phone", + "id" : 0, + "email" : "email", + "username" : "username" + }, + "xml" : { + "name" : "User" + } + }, + "Tag" : { + "title" : "Pet Tag", + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int64" + }, + "name" : { + "type" : "string" + } + }, + "description" : "A tag for a pet", + "example" : { + "name" : "name", + "id" : 1 + }, + "xml" : { + "name" : "Tag" + } + }, + "Pet" : { + "title" : "a Pet", + "required" : [ "name", "photoUrls" ], + "type" : "object", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int64" + }, + "category" : { + "$ref" : "#/components/schemas/Category" + }, + "name" : { + "type" : "string", + "example" : "doggie" + }, + "photoUrls" : { + "type" : "array", + "xml" : { + "name" : "photoUrl", + "wrapped" : true + }, + "items" : { + "type" : "string" + } + }, + "tags" : { + "type" : "array", + "xml" : { + "name" : "tag", + "wrapped" : true + }, + "items" : { + "$ref" : "#/components/schemas/Tag" + } + }, + "status" : { + "type" : "string", + "description" : "pet status in the store", + "enum" : [ "available", "pending", "sold" ] + } + }, + "description" : "A pet for sale in the pet store", + "example" : { + "photoUrls" : [ "photoUrls", "photoUrls" ], + "name" : "doggie", + "id" : 0, + "category" : { + "name" : "name", + "id" : 6 + }, + "tags" : [ { + "name" : "name", + "id" : 1 + }, { + "name" : "name", + "id" : 1 + } ], + "status" : "available" + }, + "xml" : { + "name" : "Pet" + } + }, + "ApiResponse" : { + "title" : "An uploaded response", + "type" : "object", + "properties" : { + "code" : { + "type" : "integer", + "format" : "int32" + }, + "type" : { + "type" : "string" + }, + "message" : { + "type" : "string" + } + }, + "description" : "Describes the result of uploading an image resource", + "example" : { + "code" : 0, + "type" : "type", + "message" : "message" + } + } + }, + "securitySchemes" : { + "petstore_auth" : { + "type" : "oauth2", + "flows" : { + "implicit" : { + "authorizationUrl" : "http://petstore.swagger.io/api/oauth/dialog", + "scopes" : { + "write:pets" : "modify pets in your account", + "read:pets" : "read your pets" + } + } + } + }, + "api_key" : { + "type" : "apiKey", + "name" : "api_key", + "in" : "header" + } + } + } +} \ No newline at end of file