diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/FinchServerCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/FinchServerCodegen.java index 612c0b7a68d..aa02184d833 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/FinchServerCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/FinchServerCodegen.java @@ -1,6 +1,5 @@ package io.swagger.codegen.languages; -import com.google.common.base.Strings; import io.swagger.codegen.*; import io.swagger.models.Model; import io.swagger.models.properties.ArrayProperty; @@ -81,7 +80,7 @@ public class FinchServerCodegen extends DefaultCodegen implements CodegenConfig typeMapping.put("long", "Long"); typeMapping.put("double", "Double"); typeMapping.put("number", "BigDecimal"); - typeMapping.put("date-time", "LocalDateTime"); + typeMapping.put("date-time", "ZonedDateTime"); typeMapping.put("date", "LocalDateTime"); typeMapping.put("file", "File"); typeMapping.put("array", "Seq"); @@ -90,7 +89,7 @@ public class FinchServerCodegen extends DefaultCodegen implements CodegenConfig typeMapping.put("object", "Object"); typeMapping.put("binary", "Array[Byte]"); typeMapping.put("Date", "LocalDateTime"); - typeMapping.put("DateTime", "LocalDateTime"); + typeMapping.put("DateTime", "ZonedDateTime"); additionalProperties.put("modelPackage", modelPackage()); additionalProperties.put("apiPackage", apiPackage()); @@ -154,6 +153,7 @@ public class FinchServerCodegen extends DefaultCodegen implements CodegenConfig importMapping.put("LocalDateTime", "java.time.LocalDateTime"); importMapping.put("LocalDate", "java.time.LocalDate"); importMapping.put("LocalTime", "java.time.LocalTime"); + importMapping.put("ZonedDateTime", "java.time.ZonedDateTime"); cliOptions.clear(); cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Finch package name (e.g. io.swagger).") @@ -206,81 +206,31 @@ public class FinchServerCodegen extends DefaultCodegen implements CodegenConfig return codegenModel; } + + + @Override public Map postProcessOperations(Map objs) { Map operations = (Map) objs.get("operations"); List operationList = (List) operations.get("operation"); for (CodegenOperation op : operationList) { - op.httpMethod = op.httpMethod.toLowerCase(); - String path = new String(op.path); - // remove first / - if (path.startsWith("/")) { - path = path.substring(1); - } - // remove last / - if (path.endsWith("/")) { - path = path.substring(0, path.length()-1); - } + // Converts GET /foo/bar => get("foo" :: "bar") + generateScalaPath(op); - String[] items = path.split("/", -1); - String scalaPath = ""; - int pathParamIndex = 0; + // Generates e.g. uuid :: header("boo") :: params("baa") under key "x-codegen-pathParams" + // Generates e.g. (id: UUID, headerBoo: String, paramBaa: String) under key "x-codegen-typedInputParams" + // Generates e.g. (id, headerBoo, paramBaa) under key "x-codegen-inputParams" + generateInputParameters(op); - for (int i = 0; i < items.length; ++i) { - if (items[i].matches("^\\{(.*)\\}$")) { // wrap in {} - // find the datatype of the parameter - final CodegenParameter cp = op.pathParams.get(pathParamIndex); + //Generate Auth parameters using security: definition + //Results in header("apiKey") or param("apiKey") + authParameters(op); - // TODO: Handle non-primitives… - scalaPath = scalaPath + cp.dataType.toLowerCase(); - - pathParamIndex++; - } else { - scalaPath = scalaPath + "\"" + items[i] + "\""; - } - - if (i != items.length -1) { - scalaPath = scalaPath + " :: "; - } - } - - for (CodegenParameter p : op.allParams) { - // TODO: This hacky, should be converted to mappings if possible to keep it clean. - // This could also be done using template imports - if(p.isPathParam && p.isPrimitiveType) { - p.vendorExtensions.put("x-codegen-normalized-path-type", p.dataType.toLowerCase()); - p.vendorExtensions.put("x-codegen-normalized-input-type", p.dataType); - } else if(p.isHeaderParam) { - if(p.required) { - p.vendorExtensions.put("x-codegen-normalized-path-type", "header(\"" + p.baseName + "\")"); - p.vendorExtensions.put("x-codegen-normalized-input-type", p.dataType); - } else { - p.vendorExtensions.put("x-codegen-normalized-path-type", "headerOption(\"" + p.baseName + "\")"); - p.vendorExtensions.put("x-codegen-normalized-input-type", "Option["+ p.dataType + "]"); - } - } else if(p.isQueryParam) { - if(p.isContainer || p.isListContainer) { - p.vendorExtensions.put("x-codegen-normalized-path-type", "params(\"" + p.baseName + "\")"); - p.vendorExtensions.put("x-codegen-normalized-input-type", p.dataType.replaceAll("^[^\\[]+", "Seq")); - } else { - p.vendorExtensions.put("x-codegen-normalized-path-type", "param(\"" + p.baseName + "\")"); - p.vendorExtensions.put("x-codegen-normalized-input-type", p.dataType); - } - } else if(p.isBodyParam) { - p.vendorExtensions.put("x-codegen-normalized-path-type", "jsonBody["+ p.dataType + "]"); - p.vendorExtensions.put("x-codegen-normalized-input-type", p.dataType); - } else if(p.isFile) { - p.vendorExtensions.put("x-codegen-normalized-path-type", "fileUpload(\""+ p.baseName + "\")"); - p.vendorExtensions.put("x-codegen-normalized-input-type", "FileUpload"); - } else { - p.vendorExtensions.put("x-codegen-normalized-path-type", p.dataType); - p.vendorExtensions.put("x-codegen-normalized-input-type", p.dataType); - } - } - - op.vendorExtensions.put("x-codegen-path", scalaPath); + //Concatenates all parameters + concatParameters(op); } + return objs; } @@ -330,4 +280,172 @@ public class FinchServerCodegen extends DefaultCodegen implements CodegenConfig this.packageName = packageName; } + + /** + * + * @param prim + * @param isRequired + * @param canBeOptional + * @return + */ + private String toPrimitive(String prim, Boolean isRequired, Boolean canBeOptional) { + + String converter = ".map(_.to" + prim + ")"; + return (canBeOptional ? (isRequired ? converter : ".map(_" + converter +")") : ""); + } + + //All path parameters are String initially, for primitives these need to be converted + private String toPathParameter(CodegenParameter p, String paramType, Boolean canBeOptional ) { + + Boolean isNotAString = !p.dataType.equals("String"); + + return paramType + (canBeOptional && !p.required ? "Option" : "") + "(\""+ p.baseName + "\")" + (isNotAString ? toPrimitive(p.dataType,p.required,canBeOptional) : "") ; + } + + private String toInputParameter(CodegenParameter p){ + return (p.required ? "" : "Option[")+p.dataType+(p.required ? "" : "]"); + } + + private String concat(String original, String addition, String op) { + return original + (original.isEmpty() ? "" : (addition.isEmpty() ? "" : op)) + addition; + } + + // a, b + private String csvConcat(String original, String addition) { + return concat(original, addition,", "); + } + // a :: b + private String colConcat(String original, String addition) { + return concat(original, addition," :: "); + } + + private void authParameters(CodegenOperation op) { + + String authParams = ""; + String authInputParams = ""; + String typedAuthInputParams = ""; + //Append apikey security to path params and create input parameters for functions + if(op.authMethods != null){ + + for(CodegenSecurity s : op.authMethods) { + if(s.isApiKey && s.isKeyInHeader){ + authParams = colConcat(authParams, "header(\""+ s.keyParamName + "\")"); + } else if(s.isApiKey && s.isKeyInQuery){ + authParams = colConcat(authParams, "param(\""+ s.keyParamName + "\")"); + } + if(s.isApiKey) { + typedAuthInputParams = csvConcat(typedAuthInputParams, "authParam"+ s.name + ": String"); + authInputParams = csvConcat(authInputParams,"authParam"+ s.name); + } + } + } + + op.vendorExtensions.put("x-codegen-authParams", authParams); + op.vendorExtensions.put("x-codegen-authInputParams", authInputParams); + op.vendorExtensions.put("x-codegen-typedAuthInputParams", typedAuthInputParams); + + } + + private void generateScalaPath(CodegenOperation op) { + op.httpMethod = op.httpMethod.toLowerCase(); + + String path = new String(op.path); + + // remove first / + if (path.startsWith("/")) { + path = path.substring(1); + } + + // remove last / + if (path.endsWith("/")) { + path = path.substring(0, path.length()-1); + } + + String[] items = path.split("/", -1); + String scalaPath = ""; + Integer pathParamIndex = 0; + + for (int i = 0; i < items.length; ++i) { + + if (items[i].matches("^\\{(.*)\\}$")) { // wrap in {} + // find the datatype of the parameter + final CodegenParameter cp = op.pathParams.get(pathParamIndex); + + // TODO: Handle non-primitives… + scalaPath = colConcat(scalaPath, cp.dataType.toLowerCase()); + + pathParamIndex++; + } else { + scalaPath = colConcat(scalaPath, "\"" + items[i] + "\""); + } + } + + op.vendorExtensions.put("x-codegen-path", scalaPath); + + } + + + private void concatParameters(CodegenOperation op) { + + String path = colConcat(colConcat(op.vendorExtensions.get("x-codegen-path").toString(),op.vendorExtensions.get("x-codegen-pathParams").toString()), op.vendorExtensions.get("x-codegen-authParams").toString()); + String parameters = csvConcat(op.vendorExtensions.get("x-codegen-inputParams").toString(), op.vendorExtensions.get("x-codegen-authInputParams").toString()); + String typedParameters = csvConcat(op.vendorExtensions.get("x-codegen-typedInputParams").toString(), op.vendorExtensions.get("x-codegen-typedAuthInputParams").toString()); + + // The input parameters for functions + op.vendorExtensions.put("x-codegen-paths",path); + op.vendorExtensions.put("x-codegen-params", parameters); + op.vendorExtensions.put("x-codegen-typedParams", typedParameters); + + } + + + private void generateInputParameters(CodegenOperation op) { + + String inputParams = ""; + String typedInputParams = ""; + String pathParams = ""; + + for (CodegenParameter p : op.allParams) { + // TODO: This hacky, should be converted to mappings if possible to keep it clean. + // This could also be done using template imports + + if(p.isBodyParam) { + p.vendorExtensions.put("x-codegen-normalized-path-type", "jsonBody["+ p.dataType + "]"); + p.vendorExtensions.put("x-codegen-normalized-input-type", p.dataType); + } else if(p.isContainer || p.isListContainer) { + p.vendorExtensions.put("x-codegen-normalized-path-type", toPathParameter(p,"params", false)); + p.vendorExtensions.put("x-codegen-normalized-input-type", p.dataType.replaceAll("^[^\\[]+", "Seq")); + } else if(p.isQueryParam) { + p.vendorExtensions.put("x-codegen-normalized-path-type", toPathParameter(p, "param",true)); + p.vendorExtensions.put("x-codegen-normalized-input-type", toInputParameter(p)); + } else if(p.isHeaderParam) { + p.vendorExtensions.put("x-codegen-normalized-path-type", toPathParameter(p,"header", true)); + p.vendorExtensions.put("x-codegen-normalized-input-type", toInputParameter(p)); + } else if(p.isFile) { + p.vendorExtensions.put("x-codegen-normalized-path-type", "fileUpload(\""+ p.paramName + "\")"); + p.vendorExtensions.put("x-codegen-normalized-input-type", "FileUpload"); + } else if(p.isPrimitiveType && !p.isPathParam) { + p.vendorExtensions.put("x-codegen-normalized-path-type", p.dataType.toLowerCase()); + p.vendorExtensions.put("x-codegen-normalized-input-type", toInputParameter(p)); + } else { + //Path paremeters are handled in generateScalaPath() + p.vendorExtensions.put("x-codegen-normalized-input-type", p.dataType); + } + if(p.vendorExtensions.get("x-codegen-normalized-path-type") != null){ + pathParams = colConcat(pathParams , p.vendorExtensions.get("x-codegen-normalized-path-type").toString()); + } + inputParams = csvConcat(inputParams, p.paramName); + typedInputParams = csvConcat(typedInputParams , p.paramName + ": " + p.vendorExtensions.get("x-codegen-normalized-input-type")); + + } + + // All body, path, query and header parameters + op.vendorExtensions.put("x-codegen-pathParams", pathParams); + + // The input parameters for functions + op.vendorExtensions.put("x-codegen-inputParams", inputParams); + op.vendorExtensions.put("x-codegen-typedInputParams", typedInputParams); + + } + } diff --git a/modules/swagger-codegen/src/main/resources/finch/DataAccessor.mustache b/modules/swagger-codegen/src/main/resources/finch/DataAccessor.mustache index e9700efc726..ca4e3a8f851 100644 --- a/modules/swagger-codegen/src/main/resources/finch/DataAccessor.mustache +++ b/modules/swagger-codegen/src/main/resources/finch/DataAccessor.mustache @@ -2,7 +2,9 @@ package {{packageName}} // TODO: properly handle custom imports import java.io._ -import java.util.Date +import java.util.UUID +import java.time._ + import {{modelPackage}}._ @@ -18,7 +20,7 @@ trait DataAccessor { * {{{description}}} * @return A {{#returnType}}{{returnType}}{{/returnType}}{{^returnType}}Unit{{/returnType}} */ - def {{baseName}}_{{operationId}}({{#allParams}}{{paramName}}: {{{dataType}}}{{^-last}}, {{/-last}}{{/allParams}}): {{#returnType}}{{returnType}}{{/returnType}}{{^returnType}}Unit{{/returnType}} = ??? + def {{baseName}}_{{operationId}}({{{vendorExtensions.x-codegen-typedParams}}}): Either[CommonError,{{#returnType}}{{returnType}}{{/returnType}}{{^returnType}}Unit{{/returnType}}] = ??? {{/operation}} {{/operations}} diff --git a/modules/swagger-codegen/src/main/resources/finch/README.mustache b/modules/swagger-codegen/src/main/resources/finch/README.mustache index 327870827fd..6c9bf19fe7b 100644 --- a/modules/swagger-codegen/src/main/resources/finch/README.mustache +++ b/modules/swagger-codegen/src/main/resources/finch/README.mustache @@ -5,6 +5,11 @@ This server was generated by the [swagger-codegen](https://github.com/swagger-ap [OpenAPI-Spec](https://github.com/swagger-api/swagger-core/wiki) from a remote server, you can easily generate a server stub. This is an example of building a swagger-enabled scalatra server. -This example uses the [scalatra](http://scalatra.org/) framework. To see how to make this your own, look here: +This example uses the [finch](http://github.com/finagle/finch/) framework. To see how to make this your own, look here: -[README](https://github.com/swagger-api/swagger-codegen/tree/master/samples/server-generator/scalatra) \ No newline at end of file +[README](https://github.com/swagger-api/swagger-codegen/tree/master/samples/server-generator/finch) + + +### After generation + +Run `scalafix RemoveUnusedImports` to cleanup unused imports. \ No newline at end of file diff --git a/modules/swagger-codegen/src/main/resources/finch/Server.mustache b/modules/swagger-codegen/src/main/resources/finch/Server.mustache index 97f0fb79a09..dc051c48652 100644 --- a/modules/swagger-codegen/src/main/resources/finch/Server.mustache +++ b/modules/swagger-codegen/src/main/resources/finch/Server.mustache @@ -17,10 +17,8 @@ import com.twitter.util.{Await, Future} class Server { // Loads implementation defined in resources/META-INF/services/{{packageName}}.DataAccessor - val db = LoadService[DataAccessor]() match { - case accessor :: _ => accessor - case _ => new DataAccessor { } - } + val impls: Seq[DataAccessor] = LoadService[DataAccessor]() + val db = if (impls.isEmpty) new DataAccessor { } else impls.head val service = endpoint.makeService(db) @@ -32,7 +30,7 @@ class Server { } /** - * Launches the PetstoreAPI service when the system is ready. + * Launches the API service when the system is ready. */ object Server extends Server with App { Await.ready(server) diff --git a/modules/swagger-codegen/src/main/resources/finch/api.mustache b/modules/swagger-codegen/src/main/resources/finch/api.mustache index 8758174f6d3..8365f59b5bd 100644 --- a/modules/swagger-codegen/src/main/resources/finch/api.mustache +++ b/modules/swagger-codegen/src/main/resources/finch/api.mustache @@ -1,7 +1,6 @@ package {{apiPackage}} import java.io._ -import java.util.Date import {{packageName}}._ import {{modelPackage}}._ {{#imports}}import {{import}} @@ -17,6 +16,7 @@ import com.twitter.util.Future import com.twitter.io.Buf import io.finch._, items._ import java.io.File +import java.time._ object {{classname}} { /** @@ -26,25 +26,43 @@ object {{classname}} { def endpoints(da: DataAccessor) = {{#operations}} {{#operation}} - {{{operationId}}}(da){{^-last}} :+:{{/-last}} + {{{operationId}}}(da){{^-last}} :+:{{/-last}} {{/operation}} {{/operations}} + + private def checkError(e: CommonError) = e match { + case InvalidInput(_) => BadRequest(e) + case MissingIdentifier(_) => BadRequest(e) + case RecordNotFound(_) => NotFound(e) + case _ => InternalServerError(e) + } + + implicit class StringOps(s: String) { + + import java.time.format.DateTimeFormatter + + lazy val localformatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") + lazy val datetimeformatter: DateTimeFormatter = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + + def toLocalDateTime: LocalDateTime = LocalDateTime.parse(s,localformatter) + def toZonedDateTime: ZonedDateTime = ZonedDateTime.parse(s, datetimeformatter) + + } + {{#operations}} {{#operation}} /** * {{{description}}} - * @return And endpoint representing a {{#returnType}}{{returnType}}{{/returnType}}{{^returnType}}Unit{{/returnType}} + * @return An endpoint representing a {{#returnType}}{{returnType}}{{/returnType}}{{^returnType}}Unit{{/returnType}} */ private def {{operationId}}(da: DataAccessor): Endpoint[{{#returnType}}{{returnType}}{{/returnType}}{{^returnType}}Unit{{/returnType}}] = - {{httpMethod}}({{{vendorExtensions.x-codegen-path}}} {{#allParams}}{{^isPathParam}} :: {{& vendorExtensions.x-codegen-normalized-path-type}}{{/isPathParam}}{{/allParams}}) { {{#hasParams}}({{#allParams}}{{paramName}}: {{{vendorExtensions.x-codegen-normalized-input-type}}}{{^-last}}, {{/-last}}{{/allParams}}) => {{/hasParams}} - {{#returnType}} - Ok(da.{{baseName}}_{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}})) - {{/returnType}} - {{^returnType}} - da.{{baseName}}_{{operationId}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) - NoContent[Unit] - {{/returnType}} + {{httpMethod}}({{{vendorExtensions.x-codegen-paths}}}) { {{#hasParams}}({{{vendorExtensions.x-codegen-typedParams}}}) => {{/hasParams}} + da.{{baseName}}_{{operationId}}({{{vendorExtensions.x-codegen-params}}}) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } @@ -53,20 +71,20 @@ object {{classname}} { {{/operations}} implicit private def fileUploadToFile(fileUpload: FileUpload) : File = { - fileUpload match { - case upload: InMemoryFileUpload => - bytesToFile(Buf.ByteArray.Owned.extract(upload.content)) - case upload: OnDiskFileUpload => - upload.content - case _ => null - } + fileUpload match { + case upload: InMemoryFileUpload => + bytesToFile(Buf.ByteArray.Owned.extract(upload.content)) + case upload: OnDiskFileUpload => + upload.content + case _ => null + } } private def bytesToFile(input: Array[Byte]): java.io.File = { - val file = File.createTempFile("tmp{{classname}}", null) - val output = new FileOutputStream(file) - output.write(input) - file + val file = File.createTempFile("tmp{{classname}}", null) + val output = new FileOutputStream(file) + output.write(input) + file } // This assists in params(string) application (which must be Seq[A] in parameter list) when the param is used as a List[A] elsewhere. diff --git a/modules/swagger-codegen/src/main/resources/finch/build.sbt b/modules/swagger-codegen/src/main/resources/finch/build.sbt index b58544b46bd..95f655766be 100644 --- a/modules/swagger-codegen/src/main/resources/finch/build.sbt +++ b/modules/swagger-codegen/src/main/resources/finch/build.sbt @@ -6,7 +6,7 @@ name := "finch-sample" version := "0.1.0-SNAPSHOT" -scalaVersion := "2.11.8" +scalaVersion := "2.11.11" resolvers += Resolver.sonatypeRepo("snapshots") @@ -20,6 +20,11 @@ resolvers += "Sonatype OSS Releases" at "http://oss.sonatype.org/content/reposit Defaults.itSettings +lazy val circeVersion = "0.8.0" +lazy val finagleVersion = "6.45.0" +lazy val finchVersion = "0.15.1" +lazy val scalaTestVersion = "3.0.0" + scalacOptions ++= Seq( "-deprecation", "-encoding", "UTF-8", @@ -33,21 +38,21 @@ scalacOptions ++= Seq( "-Ywarn-numeric-widen", "-Xfuture", "-Xlint", -// "-Ywarn-unused-import", + "-Ywarn-unused-import", "-language:postfixOps" ) lazy val `it-config-sbt-project` = project.in(file(".")).configs(IntegrationTest) libraryDependencies ++= Seq( - "com.github.finagle" %% "finch-core" % "0.12.0", - "com.github.finagle" %% "finch-circe" % "0.12.0", - "io.circe" %% "circe-generic" % "0.7.0", - "io.circe" %% "circe-java8" % "0.7.0", - "com.twitter" %% "util-core" % "6.40.0", - "com.github.finagle" %% "finch-test" % "0.12.0" % "test", - "org.scalacheck" %% "scalacheck" % "1.13.4" % "test", - "org.scalatest" %% "scalatest" % "3.0.0" % "test" + "com.github.finagle" %% "finch-core" % finchVersion, + "com.github.finagle" %% "finch-circe" % finchVersion, + "io.circe" %% "circe-generic" % circeVersion, + "io.circe" %% "circe-java8" % circeVersion, + "com.twitter" %% "util-core" % finagleVersion, + "com.github.finagle" %% "finch-test" % finchVersion % "test", + "org.scalacheck" %% "scalacheck" % "1.13.4" % "test", + "org.scalatest" %% "scalatest" % scalaTestVersion % "test" ) assemblyMergeStrategy in assembly := { diff --git a/modules/swagger-codegen/src/main/resources/finch/endpoint.mustache b/modules/swagger-codegen/src/main/resources/finch/endpoint.mustache index bb66defbe4f..31763220d76 100644 --- a/modules/swagger-codegen/src/main/resources/finch/endpoint.mustache +++ b/modules/swagger-codegen/src/main/resources/finch/endpoint.mustache @@ -23,7 +23,7 @@ object endpoint { Json.obj("error" -> Json.fromString("something_not_parsed")) case Error.NotValid(_, _) => Json.obj("error" -> Json.fromString("something_not_valid")) - case error: PetstoreError => + case error: CommonError => Json.obj("error" -> Json.fromString(error.message)) } @@ -44,7 +44,7 @@ object endpoint { {{/apis}} {{/apiInfo}} ).handle({ - case e: PetstoreError => NotFound(e) + case e: CommonError => NotFound(e) }).toService } \ No newline at end of file diff --git a/modules/swagger-codegen/src/main/resources/finch/errors.mustache b/modules/swagger-codegen/src/main/resources/finch/errors.mustache index eb90b48052a..e4d8dbce8fd 100644 --- a/modules/swagger-codegen/src/main/resources/finch/errors.mustache +++ b/modules/swagger-codegen/src/main/resources/finch/errors.mustache @@ -1,9 +1,9 @@ package {{packageName}} /** - * The parent error from which most PetstoreAPI errors extend. Thrown whenever something in the api goes wrong. + * The parent error from which most API errors extend. Thrown whenever something in the api goes wrong. */ -abstract class PetstoreError(msg: String) extends Exception(msg) { +abstract class CommonError(msg: String) extends Exception(msg) { def message: String } @@ -11,17 +11,17 @@ abstract class PetstoreError(msg: String) extends Exception(msg) { * Thrown when the object given is invalid * @param message An error message */ -case class InvalidInput(message: String) extends PetstoreError(message) +case class InvalidInput(message: String) extends CommonError(message) /** * Thrown when the given object is missing a unique ID. * @param message An error message */ -case class MissingIdentifier(message: String) extends PetstoreError(message) +case class MissingIdentifier(message: String) extends CommonError(message) /** * Thrown when the given record does not exist in the database. * @param message An error message */ -case class RecordNotFound(message: String) extends PetstoreError(message) +case class RecordNotFound(message: String) extends CommonError(message) diff --git a/modules/swagger-codegen/src/main/resources/finch/project/plugins.sbt b/modules/swagger-codegen/src/main/resources/finch/project/plugins.sbt index 761afa5688e..d005ee813b4 100644 --- a/modules/swagger-codegen/src/main/resources/finch/project/plugins.sbt +++ b/modules/swagger-codegen/src/main/resources/finch/project/plugins.sbt @@ -2,6 +2,6 @@ resolvers += Resolver.typesafeRepo("releases") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.3") -// addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.1.4") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.5.3") addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.6.0") diff --git a/samples/server/petstore/finch/README.md b/samples/server/petstore/finch/README.md index 327870827fd..6c9bf19fe7b 100644 --- a/samples/server/petstore/finch/README.md +++ b/samples/server/petstore/finch/README.md @@ -5,6 +5,11 @@ This server was generated by the [swagger-codegen](https://github.com/swagger-ap [OpenAPI-Spec](https://github.com/swagger-api/swagger-core/wiki) from a remote server, you can easily generate a server stub. This is an example of building a swagger-enabled scalatra server. -This example uses the [scalatra](http://scalatra.org/) framework. To see how to make this your own, look here: +This example uses the [finch](http://github.com/finagle/finch/) framework. To see how to make this your own, look here: -[README](https://github.com/swagger-api/swagger-codegen/tree/master/samples/server-generator/scalatra) \ No newline at end of file +[README](https://github.com/swagger-api/swagger-codegen/tree/master/samples/server-generator/finch) + + +### After generation + +Run `scalafix RemoveUnusedImports` to cleanup unused imports. \ No newline at end of file diff --git a/samples/server/petstore/finch/build.sbt b/samples/server/petstore/finch/build.sbt index b58544b46bd..95f655766be 100644 --- a/samples/server/petstore/finch/build.sbt +++ b/samples/server/petstore/finch/build.sbt @@ -6,7 +6,7 @@ name := "finch-sample" version := "0.1.0-SNAPSHOT" -scalaVersion := "2.11.8" +scalaVersion := "2.11.11" resolvers += Resolver.sonatypeRepo("snapshots") @@ -20,6 +20,11 @@ resolvers += "Sonatype OSS Releases" at "http://oss.sonatype.org/content/reposit Defaults.itSettings +lazy val circeVersion = "0.8.0" +lazy val finagleVersion = "6.45.0" +lazy val finchVersion = "0.15.1" +lazy val scalaTestVersion = "3.0.0" + scalacOptions ++= Seq( "-deprecation", "-encoding", "UTF-8", @@ -33,21 +38,21 @@ scalacOptions ++= Seq( "-Ywarn-numeric-widen", "-Xfuture", "-Xlint", -// "-Ywarn-unused-import", + "-Ywarn-unused-import", "-language:postfixOps" ) lazy val `it-config-sbt-project` = project.in(file(".")).configs(IntegrationTest) libraryDependencies ++= Seq( - "com.github.finagle" %% "finch-core" % "0.12.0", - "com.github.finagle" %% "finch-circe" % "0.12.0", - "io.circe" %% "circe-generic" % "0.7.0", - "io.circe" %% "circe-java8" % "0.7.0", - "com.twitter" %% "util-core" % "6.40.0", - "com.github.finagle" %% "finch-test" % "0.12.0" % "test", - "org.scalacheck" %% "scalacheck" % "1.13.4" % "test", - "org.scalatest" %% "scalatest" % "3.0.0" % "test" + "com.github.finagle" %% "finch-core" % finchVersion, + "com.github.finagle" %% "finch-circe" % finchVersion, + "io.circe" %% "circe-generic" % circeVersion, + "io.circe" %% "circe-java8" % circeVersion, + "com.twitter" %% "util-core" % finagleVersion, + "com.github.finagle" %% "finch-test" % finchVersion % "test", + "org.scalacheck" %% "scalacheck" % "1.13.4" % "test", + "org.scalatest" %% "scalatest" % scalaTestVersion % "test" ) assemblyMergeStrategy in assembly := { diff --git a/samples/server/petstore/finch/project/plugins.sbt b/samples/server/petstore/finch/project/plugins.sbt index 761afa5688e..d005ee813b4 100644 --- a/samples/server/petstore/finch/project/plugins.sbt +++ b/samples/server/petstore/finch/project/plugins.sbt @@ -2,6 +2,6 @@ resolvers += Resolver.typesafeRepo("releases") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.3") -// addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.1.4") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.5.3") addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.6.0") diff --git a/samples/server/petstore/finch/src/main/scala/DataAccessor.scala b/samples/server/petstore/finch/src/main/scala/DataAccessor.scala index 4c9d06b2ea9..2fcb7429b18 100644 --- a/samples/server/petstore/finch/src/main/scala/DataAccessor.scala +++ b/samples/server/petstore/finch/src/main/scala/DataAccessor.scala @@ -2,7 +2,9 @@ package io.swagger // TODO: properly handle custom imports import java.io._ -import java.util.Date +import java.util.UUID +import java.time._ + import io.swagger.models._ @@ -14,120 +16,120 @@ trait DataAccessor { * * @return A Unit */ - def Pet_addPet(body: Pet): Unit = ??? + def Pet_addPet(body: Pet): Either[CommonError,Unit] = ??? /** * * @return A Unit */ - def Pet_deletePet(petId: Long, apiKey: String): Unit = ??? + def Pet_deletePet(petId: Long, apiKey: Option[String]): Either[CommonError,Unit] = ??? /** * * @return A Seq[Pet] */ - def Pet_findPetsByStatus(status: Seq[String]): Seq[Pet] = ??? + def Pet_findPetsByStatus(status: Seq[String]): Either[CommonError,Seq[Pet]] = ??? /** * * @return A Seq[Pet] */ - def Pet_findPetsByTags(tags: Seq[String]): Seq[Pet] = ??? + def Pet_findPetsByTags(tags: Seq[String]): Either[CommonError,Seq[Pet]] = ??? /** * * @return A Pet */ - def Pet_getPetById(petId: Long): Pet = ??? + def Pet_getPetById(petId: Long, authParamapi_key: String): Either[CommonError,Pet] = ??? /** * * @return A Unit */ - def Pet_updatePet(body: Pet): Unit = ??? + def Pet_updatePet(body: Pet): Either[CommonError,Unit] = ??? /** * * @return A Unit */ - def Pet_updatePetWithForm(petId: Long, name: String, status: String): Unit = ??? + def Pet_updatePetWithForm(petId: Long, name: Option[String], status: Option[String]): Either[CommonError,Unit] = ??? /** * * @return A ApiResponse */ - def Pet_uploadFile(petId: Long, additionalMetadata: String, file: File): ApiResponse = ??? + def Pet_uploadFile(petId: Long, additionalMetadata: Option[String], file: FileUpload): Either[CommonError,ApiResponse] = ??? /** * * @return A Unit */ - def Store_deleteOrder(orderId: String): Unit = ??? + def Store_deleteOrder(orderId: String): Either[CommonError,Unit] = ??? /** * * @return A Map[String, Int] */ - def Store_getInventory(): Map[String, Int] = ??? + def Store_getInventory(authParamapi_key: String): Either[CommonError,Map[String, Int]] = ??? /** * * @return A Order */ - def Store_getOrderById(orderId: Long): Order = ??? + def Store_getOrderById(orderId: Long): Either[CommonError,Order] = ??? /** * * @return A Order */ - def Store_placeOrder(body: Order): Order = ??? + def Store_placeOrder(body: Order): Either[CommonError,Order] = ??? /** * * @return A Unit */ - def User_createUser(body: User): Unit = ??? + def User_createUser(body: User): Either[CommonError,Unit] = ??? /** * * @return A Unit */ - def User_createUsersWithArrayInput(body: Seq[User]): Unit = ??? + def User_createUsersWithArrayInput(body: Seq[User]): Either[CommonError,Unit] = ??? /** * * @return A Unit */ - def User_createUsersWithListInput(body: Seq[User]): Unit = ??? + def User_createUsersWithListInput(body: Seq[User]): Either[CommonError,Unit] = ??? /** * * @return A Unit */ - def User_deleteUser(username: String): Unit = ??? + def User_deleteUser(username: String): Either[CommonError,Unit] = ??? /** * * @return A User */ - def User_getUserByName(username: String): User = ??? + def User_getUserByName(username: String): Either[CommonError,User] = ??? /** * * @return A String */ - def User_loginUser(username: String, password: String): String = ??? + def User_loginUser(username: String, password: String): Either[CommonError,String] = ??? /** * * @return A Unit */ - def User_logoutUser(): Unit = ??? + def User_logoutUser(): Either[CommonError,Unit] = ??? /** * * @return A Unit */ - def User_updateUser(username: String, body: User): Unit = ??? + def User_updateUser(username: String, body: User): Either[CommonError,Unit] = ??? } \ No newline at end of file diff --git a/samples/server/petstore/finch/src/main/scala/Server.scala b/samples/server/petstore/finch/src/main/scala/Server.scala index ec3ed482e27..d53b3e8cea0 100644 --- a/samples/server/petstore/finch/src/main/scala/Server.scala +++ b/samples/server/petstore/finch/src/main/scala/Server.scala @@ -15,10 +15,8 @@ import com.twitter.util.{Await, Future} class Server { // Loads implementation defined in resources/META-INF/services/io.swagger.DataAccessor - val db = LoadService[DataAccessor]() match { - case accessor :: _ => accessor - case _ => new DataAccessor { } - } + val impls: Seq[DataAccessor] = LoadService[DataAccessor]() + val db = if (impls.isEmpty) new DataAccessor { } else impls.head val service = endpoint.makeService(db) @@ -30,7 +28,7 @@ class Server { } /** - * Launches the PetstoreAPI service when the system is ready. + * Launches the API service when the system is ready. */ object Server extends Server with App { Await.ready(server) diff --git a/samples/server/petstore/finch/src/main/scala/endpoint.scala b/samples/server/petstore/finch/src/main/scala/endpoint.scala index 5241b51bbd8..5f139a03708 100644 --- a/samples/server/petstore/finch/src/main/scala/endpoint.scala +++ b/samples/server/petstore/finch/src/main/scala/endpoint.scala @@ -23,7 +23,7 @@ object endpoint { Json.obj("error" -> Json.fromString("something_not_parsed")) case Error.NotValid(_, _) => Json.obj("error" -> Json.fromString("something_not_valid")) - case error: PetstoreError => + case error: CommonError => Json.obj("error" -> Json.fromString(error.message)) } @@ -42,7 +42,7 @@ object endpoint { StoreApi.endpoints(da) :+: UserApi.endpoints(da) ).handle({ - case e: PetstoreError => NotFound(e) + case e: CommonError => NotFound(e) }).toService } \ No newline at end of file diff --git a/samples/server/petstore/finch/src/main/scala/errors.scala b/samples/server/petstore/finch/src/main/scala/errors.scala index 162549e0f23..379bc34b39c 100644 --- a/samples/server/petstore/finch/src/main/scala/errors.scala +++ b/samples/server/petstore/finch/src/main/scala/errors.scala @@ -1,9 +1,9 @@ package io.swagger /** - * The parent error from which most PetstoreAPI errors extend. Thrown whenever something in the api goes wrong. + * The parent error from which most API errors extend. Thrown whenever something in the api goes wrong. */ -abstract class PetstoreError(msg: String) extends Exception(msg) { +abstract class CommonError(msg: String) extends Exception(msg) { def message: String } @@ -11,17 +11,17 @@ abstract class PetstoreError(msg: String) extends Exception(msg) { * Thrown when the object given is invalid * @param message An error message */ -case class InvalidInput(message: String) extends PetstoreError(message) +case class InvalidInput(message: String) extends CommonError(message) /** * Thrown when the given object is missing a unique ID. * @param message An error message */ -case class MissingIdentifier(message: String) extends PetstoreError(message) +case class MissingIdentifier(message: String) extends CommonError(message) /** * Thrown when the given record does not exist in the database. * @param message An error message */ -case class RecordNotFound(message: String) extends PetstoreError(message) +case class RecordNotFound(message: String) extends CommonError(message) diff --git a/samples/server/petstore/finch/src/main/scala/io/swagger/apis/PetApi.scala b/samples/server/petstore/finch/src/main/scala/io/swagger/apis/PetApi.scala index be9bc36a557..3f35d7b0123 100644 --- a/samples/server/petstore/finch/src/main/scala/io/swagger/apis/PetApi.scala +++ b/samples/server/petstore/finch/src/main/scala/io/swagger/apis/PetApi.scala @@ -1,7 +1,6 @@ package io.swagger.apis import java.io._ -import java.util.Date import io.swagger._ import io.swagger.models._ import io.swagger.models.ApiResponse @@ -18,6 +17,7 @@ import com.twitter.util.Future import com.twitter.io.Buf import io.finch._, items._ import java.io.File +import java.time._ object PetApi { /** @@ -25,123 +25,164 @@ object PetApi { * @return Bundled compilation of all service endpoints. */ def endpoints(da: DataAccessor) = - addPet(da) :+: - deletePet(da) :+: - findPetsByStatus(da) :+: - findPetsByTags(da) :+: - getPetById(da) :+: - updatePet(da) :+: - updatePetWithForm(da) :+: - uploadFile(da) + addPet(da) :+: + deletePet(da) :+: + findPetsByStatus(da) :+: + findPetsByTags(da) :+: + getPetById(da) :+: + updatePet(da) :+: + updatePetWithForm(da) :+: + uploadFile(da) + + + private def checkError(e: CommonError) = e match { + case InvalidInput(_) => BadRequest(e) + case MissingIdentifier(_) => BadRequest(e) + case RecordNotFound(_) => NotFound(e) + case _ => InternalServerError(e) + } + + implicit class StringOps(s: String) { + + import java.time.format.DateTimeFormatter + + lazy val localformatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") + lazy val datetimeformatter: DateTimeFormatter = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + + def toLocalDateTime: LocalDateTime = LocalDateTime.parse(s,localformatter) + def toZonedDateTime: ZonedDateTime = ZonedDateTime.parse(s, datetimeformatter) + + } /** * - * @return And endpoint representing a Unit + * @return An endpoint representing a Unit */ private def addPet(da: DataAccessor): Endpoint[Unit] = - post("pet" :: jsonBody[Pet]) { (body: Pet) => - da.Pet_addPet(body) - NoContent[Unit] + post("pet" :: jsonBody[Pet]) { (body: Pet) => + da.Pet_addPet(body) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } /** * - * @return And endpoint representing a Unit + * @return An endpoint representing a Unit */ private def deletePet(da: DataAccessor): Endpoint[Unit] = - delete("pet" :: long :: headerOption("api_key")) { (petId: Long, apiKey: Option[String]) => - da.Pet_deletePet(petId, apiKey) - NoContent[Unit] + delete("pet" :: long :: headerOption("api_key")) { (petId: Long, apiKey: Option[String]) => + da.Pet_deletePet(petId, apiKey) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } /** * - * @return And endpoint representing a Seq[Pet] + * @return An endpoint representing a Seq[Pet] */ private def findPetsByStatus(da: DataAccessor): Endpoint[Seq[Pet]] = - get("pet" :: "findByStatus" :: params("status")) { (status: Seq[String]) => - Ok(da.Pet_findPetsByStatus(status)) + get("pet" :: "findByStatus" :: params("status")) { (status: Seq[String]) => + da.Pet_findPetsByStatus(status) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } /** * - * @return And endpoint representing a Seq[Pet] + * @return An endpoint representing a Seq[Pet] */ private def findPetsByTags(da: DataAccessor): Endpoint[Seq[Pet]] = - get("pet" :: "findByTags" :: params("tags")) { (tags: Seq[String]) => - Ok(da.Pet_findPetsByTags(tags)) + get("pet" :: "findByTags" :: params("tags")) { (tags: Seq[String]) => + da.Pet_findPetsByTags(tags) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } /** * - * @return And endpoint representing a Pet + * @return An endpoint representing a Pet */ private def getPetById(da: DataAccessor): Endpoint[Pet] = - get("pet" :: long ) { (petId: Long) => - Ok(da.Pet_getPetById(petId)) + get("pet" :: long :: header("api_key")) { (petId: Long, authParamapi_key: String) => + da.Pet_getPetById(petId, authParamapi_key) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } /** * - * @return And endpoint representing a Unit + * @return An endpoint representing a Unit */ private def updatePet(da: DataAccessor): Endpoint[Unit] = - put("pet" :: jsonBody[Pet]) { (body: Pet) => - da.Pet_updatePet(body) - NoContent[Unit] + put("pet" :: jsonBody[Pet]) { (body: Pet) => + da.Pet_updatePet(body) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } /** * - * @return And endpoint representing a Unit + * @return An endpoint representing a Unit */ private def updatePetWithForm(da: DataAccessor): Endpoint[Unit] = - post("pet" :: long :: String :: String) { (petId: Long, name: String, status: String) => - da.Pet_updatePetWithForm(petId, name, status) - NoContent[Unit] + post("pet" :: long :: string :: string) { (petId: Long, name: Option[String], status: Option[String]) => + da.Pet_updatePetWithForm(petId, name, status) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } /** * - * @return And endpoint representing a ApiResponse + * @return An endpoint representing a ApiResponse */ private def uploadFile(da: DataAccessor): Endpoint[ApiResponse] = - post("pet" :: long :: "uploadImage" :: String :: fileUpload("file")) { (petId: Long, additionalMetadata: String, file: FileUpload) => - Ok(da.Pet_uploadFile(petId, additionalMetadata, file)) + post("pet" :: long :: "uploadImage" :: string :: fileUpload("file")) { (petId: Long, additionalMetadata: Option[String], file: FileUpload) => + da.Pet_uploadFile(petId, additionalMetadata, file) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } implicit private def fileUploadToFile(fileUpload: FileUpload) : File = { - fileUpload match { - case upload: InMemoryFileUpload => - bytesToFile(Buf.ByteArray.Owned.extract(upload.content)) - case upload: OnDiskFileUpload => - upload.content - case _ => null - } + fileUpload match { + case upload: InMemoryFileUpload => + bytesToFile(Buf.ByteArray.Owned.extract(upload.content)) + case upload: OnDiskFileUpload => + upload.content + case _ => null + } } private def bytesToFile(input: Array[Byte]): java.io.File = { - val file = File.createTempFile("tmpPetApi", null) - val output = new FileOutputStream(file) - output.write(input) - file + val file = File.createTempFile("tmpPetApi", null) + val output = new FileOutputStream(file) + output.write(input) + file } // This assists in params(string) application (which must be Seq[A] in parameter list) when the param is used as a List[A] elsewhere. diff --git a/samples/server/petstore/finch/src/main/scala/io/swagger/apis/StoreApi.scala b/samples/server/petstore/finch/src/main/scala/io/swagger/apis/StoreApi.scala index 02186ce1f65..c45ef8153fd 100644 --- a/samples/server/petstore/finch/src/main/scala/io/swagger/apis/StoreApi.scala +++ b/samples/server/petstore/finch/src/main/scala/io/swagger/apis/StoreApi.scala @@ -1,7 +1,6 @@ package io.swagger.apis import java.io._ -import java.util.Date import io.swagger._ import io.swagger.models._ import io.swagger.models.Order @@ -16,6 +15,7 @@ import com.twitter.util.Future import com.twitter.io.Buf import io.finch._, items._ import java.io.File +import java.time._ object StoreApi { /** @@ -23,72 +23,104 @@ object StoreApi { * @return Bundled compilation of all service endpoints. */ def endpoints(da: DataAccessor) = - deleteOrder(da) :+: - getInventory(da) :+: - getOrderById(da) :+: - placeOrder(da) + deleteOrder(da) :+: + getInventory(da) :+: + getOrderById(da) :+: + placeOrder(da) + + + private def checkError(e: CommonError) = e match { + case InvalidInput(_) => BadRequest(e) + case MissingIdentifier(_) => BadRequest(e) + case RecordNotFound(_) => NotFound(e) + case _ => InternalServerError(e) + } + + implicit class StringOps(s: String) { + + import java.time.format.DateTimeFormatter + + lazy val localformatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") + lazy val datetimeformatter: DateTimeFormatter = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + + def toLocalDateTime: LocalDateTime = LocalDateTime.parse(s,localformatter) + def toZonedDateTime: ZonedDateTime = ZonedDateTime.parse(s, datetimeformatter) + + } /** * - * @return And endpoint representing a Unit + * @return An endpoint representing a Unit */ private def deleteOrder(da: DataAccessor): Endpoint[Unit] = - delete("store" :: "order" :: string ) { (orderId: String) => - da.Store_deleteOrder(orderId) - NoContent[Unit] + delete("store" :: "order" :: string) { (orderId: String) => + da.Store_deleteOrder(orderId) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } /** * - * @return And endpoint representing a Map[String, Int] + * @return An endpoint representing a Map[String, Int] */ private def getInventory(da: DataAccessor): Endpoint[Map[String, Int]] = - get("store" :: "inventory" ) { - Ok(da.Store_getInventory()) + get("store" :: "inventory" :: header("api_key")) { + da.Store_getInventory(authParamapi_key) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } /** * - * @return And endpoint representing a Order + * @return An endpoint representing a Order */ private def getOrderById(da: DataAccessor): Endpoint[Order] = - get("store" :: "order" :: long ) { (orderId: Long) => - Ok(da.Store_getOrderById(orderId)) + get("store" :: "order" :: long) { (orderId: Long) => + da.Store_getOrderById(orderId) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } /** * - * @return And endpoint representing a Order + * @return An endpoint representing a Order */ private def placeOrder(da: DataAccessor): Endpoint[Order] = - post("store" :: "order" :: jsonBody[Order]) { (body: Order) => - Ok(da.Store_placeOrder(body)) + post("store" :: "order" :: jsonBody[Order]) { (body: Order) => + da.Store_placeOrder(body) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } implicit private def fileUploadToFile(fileUpload: FileUpload) : File = { - fileUpload match { - case upload: InMemoryFileUpload => - bytesToFile(Buf.ByteArray.Owned.extract(upload.content)) - case upload: OnDiskFileUpload => - upload.content - case _ => null - } + fileUpload match { + case upload: InMemoryFileUpload => + bytesToFile(Buf.ByteArray.Owned.extract(upload.content)) + case upload: OnDiskFileUpload => + upload.content + case _ => null + } } private def bytesToFile(input: Array[Byte]): java.io.File = { - val file = File.createTempFile("tmpStoreApi", null) - val output = new FileOutputStream(file) - output.write(input) - file + val file = File.createTempFile("tmpStoreApi", null) + val output = new FileOutputStream(file) + output.write(input) + file } // This assists in params(string) application (which must be Seq[A] in parameter list) when the param is used as a List[A] elsewhere. diff --git a/samples/server/petstore/finch/src/main/scala/io/swagger/apis/UserApi.scala b/samples/server/petstore/finch/src/main/scala/io/swagger/apis/UserApi.scala index bf1579835aa..c33effe4417 100644 --- a/samples/server/petstore/finch/src/main/scala/io/swagger/apis/UserApi.scala +++ b/samples/server/petstore/finch/src/main/scala/io/swagger/apis/UserApi.scala @@ -1,7 +1,6 @@ package io.swagger.apis import java.io._ -import java.util.Date import io.swagger._ import io.swagger.models._ import scala.collection.immutable.Seq @@ -17,6 +16,7 @@ import com.twitter.util.Future import com.twitter.io.Buf import io.finch._, items._ import java.io.File +import java.time._ object UserApi { /** @@ -24,125 +24,164 @@ object UserApi { * @return Bundled compilation of all service endpoints. */ def endpoints(da: DataAccessor) = - createUser(da) :+: - createUsersWithArrayInput(da) :+: - createUsersWithListInput(da) :+: - deleteUser(da) :+: - getUserByName(da) :+: - loginUser(da) :+: - logoutUser(da) :+: - updateUser(da) + createUser(da) :+: + createUsersWithArrayInput(da) :+: + createUsersWithListInput(da) :+: + deleteUser(da) :+: + getUserByName(da) :+: + loginUser(da) :+: + logoutUser(da) :+: + updateUser(da) + + + private def checkError(e: CommonError) = e match { + case InvalidInput(_) => BadRequest(e) + case MissingIdentifier(_) => BadRequest(e) + case RecordNotFound(_) => NotFound(e) + case _ => InternalServerError(e) + } + + implicit class StringOps(s: String) { + + import java.time.format.DateTimeFormatter + + lazy val localformatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") + lazy val datetimeformatter: DateTimeFormatter = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") + + def toLocalDateTime: LocalDateTime = LocalDateTime.parse(s,localformatter) + def toZonedDateTime: ZonedDateTime = ZonedDateTime.parse(s, datetimeformatter) + + } /** * - * @return And endpoint representing a Unit + * @return An endpoint representing a Unit */ private def createUser(da: DataAccessor): Endpoint[Unit] = - post("user" :: jsonBody[User]) { (body: User) => - da.User_createUser(body) - NoContent[Unit] + post("user" :: jsonBody[User]) { (body: User) => + da.User_createUser(body) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } /** * - * @return And endpoint representing a Unit + * @return An endpoint representing a Unit */ private def createUsersWithArrayInput(da: DataAccessor): Endpoint[Unit] = - post("user" :: "createWithArray" :: jsonBody[Seq[User]]) { (body: Seq[User]) => - da.User_createUsersWithArrayInput(body) - NoContent[Unit] + post("user" :: "createWithArray" :: jsonBody[Seq[User]]) { (body: Seq[User]) => + da.User_createUsersWithArrayInput(body) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } /** * - * @return And endpoint representing a Unit + * @return An endpoint representing a Unit */ private def createUsersWithListInput(da: DataAccessor): Endpoint[Unit] = - post("user" :: "createWithList" :: jsonBody[Seq[User]]) { (body: Seq[User]) => - da.User_createUsersWithListInput(body) - NoContent[Unit] + post("user" :: "createWithList" :: jsonBody[Seq[User]]) { (body: Seq[User]) => + da.User_createUsersWithListInput(body) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } /** * - * @return And endpoint representing a Unit + * @return An endpoint representing a Unit */ private def deleteUser(da: DataAccessor): Endpoint[Unit] = - delete("user" :: string ) { (username: String) => - da.User_deleteUser(username) - NoContent[Unit] + delete("user" :: string) { (username: String) => + da.User_deleteUser(username) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } /** * - * @return And endpoint representing a User + * @return An endpoint representing a User */ private def getUserByName(da: DataAccessor): Endpoint[User] = - get("user" :: string ) { (username: String) => - Ok(da.User_getUserByName(username)) + get("user" :: string) { (username: String) => + da.User_getUserByName(username) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } /** * - * @return And endpoint representing a String + * @return An endpoint representing a String */ private def loginUser(da: DataAccessor): Endpoint[String] = - get("user" :: "login" :: param("username") :: param("password")) { (username: String, password: String) => - Ok(da.User_loginUser(username, password)) + get("user" :: "login" :: param("username") :: param("password")) { (username: String, password: String) => + da.User_loginUser(username, password) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } /** * - * @return And endpoint representing a Unit + * @return An endpoint representing a Unit */ private def logoutUser(da: DataAccessor): Endpoint[Unit] = - get("user" :: "logout" ) { - da.User_logoutUser() - NoContent[Unit] + get("user" :: "logout") { + da.User_logoutUser() match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } /** * - * @return And endpoint representing a Unit + * @return An endpoint representing a Unit */ private def updateUser(da: DataAccessor): Endpoint[Unit] = - put("user" :: string :: jsonBody[User]) { (username: String, body: User) => - da.User_updateUser(username, body) - NoContent[Unit] + put("user" :: string :: jsonBody[User]) { (username: String, body: User) => + da.User_updateUser(username, body) match { + case Left(error) => checkError(error) + case Right(data) => Ok(data) + } } handle { case e: Exception => BadRequest(e) } implicit private def fileUploadToFile(fileUpload: FileUpload) : File = { - fileUpload match { - case upload: InMemoryFileUpload => - bytesToFile(Buf.ByteArray.Owned.extract(upload.content)) - case upload: OnDiskFileUpload => - upload.content - case _ => null - } + fileUpload match { + case upload: InMemoryFileUpload => + bytesToFile(Buf.ByteArray.Owned.extract(upload.content)) + case upload: OnDiskFileUpload => + upload.content + case _ => null + } } private def bytesToFile(input: Array[Byte]): java.io.File = { - val file = File.createTempFile("tmpUserApi", null) - val output = new FileOutputStream(file) - output.write(input) - file + val file = File.createTempFile("tmpUserApi", null) + val output = new FileOutputStream(file) + output.write(input) + file } // This assists in params(string) application (which must be Seq[A] in parameter list) when the param is used as a List[A] elsewhere. diff --git a/samples/server/petstore/finch/src/main/scala/io/swagger/models/Order.scala b/samples/server/petstore/finch/src/main/scala/io/swagger/models/Order.scala index 2934ee921b3..5078b676332 100644 --- a/samples/server/petstore/finch/src/main/scala/io/swagger/models/Order.scala +++ b/samples/server/petstore/finch/src/main/scala/io/swagger/models/Order.scala @@ -5,7 +5,7 @@ import io.finch.circe._ import io.circe.generic.semiauto._ import io.circe.java8.time._ import io.swagger._ -import java.time.LocalDateTime +import java.time.ZonedDateTime /** * An order for a pets from the pet store @@ -19,7 +19,7 @@ import java.time.LocalDateTime case class Order(id: Option[Long], petId: Option[Long], quantity: Option[Int], - shipDate: Option[LocalDateTime], + shipDate: Option[ZonedDateTime], status: Option[String], complete: Option[Boolean] )