All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.unicredit.swagger.generators.DefaultServerGenerator.scala Maven / Gradle / Ivy

/* Copyright 2015 UniCredit S.p.A.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package eu.unicredit.swagger.generators

import treehugger.forest._
import definitions._
import treehuggerDSL._
import eu.unicredit.swagger.StringUtils._

import io.swagger.parser.SwaggerParser
import io.swagger.models._
import io.swagger.models.parameters._
import scala.collection.JavaConverters._

class DefaultServerGenerator extends ServerGenerator with SharedServerClientCode {

  def controllerNameFromFileName(fn: String) =
    objectNameFromFileName(fn, "Controller")

  def serviceNameFromFileName(fn: String) =
    objectNameFromFileName(fn, "Service")

  override def generateRoutes(fileName: String, packageName: String): Option[String] = {
    val swagger = new SwaggerParser().read(fileName)

    val basePath = Option(swagger.getBasePath).getOrElse("/")

    val completePaths =
      swagger.getPaths.asScala.keySet.toSeq

    def composeRoutes(p: String): Seq[String] = {
      val path = swagger.getPath(p)
      if (path == null) return Seq()

      val ops: Map[String, Operation] =
        Seq(Option(path.getDelete) map ("DELETE" -> _),
            Option(path.getGet) map ("GET" -> _),
            Option(path.getPost) map ("POST" -> _),
            Option(path.getPut) map ("PUT" -> _)).flatten.toMap

      val controllerName =
        packageName + ".controller" + "." + controllerNameFromFileName(fileName)

      (for {
        (verb, op) <- ops
      } yield {

        val url =
          doUrl(basePath, p)

        val methodName =
          if (op.getOperationId != null) op.getOperationId
          else throw new Exception("Please provide an operationId in: " + p)

        def genMethodCall(className: String, methodName: String, params: Seq[Parameter]): String = {
          val p = getMethodParams(params).map {
            case (n, v) => s"$n: ${treeToString(v.tpt)}"
          }
          // since it is a route definition, this is not Scala code, so we generate it manually
          s"$className.$methodName" + p.mkString("(", ", ", ")")
        }

        val methodCall =
          genMethodCall(controllerName, methodName, op.getParameters.asScala)

        s"${padTo(8, verb)}            ${padTo(50, url)}          ${padTo(20, methodCall)}"
      }).toSeq
    }

    val routes =
      completePaths.flatMap(composeRoutes)

    if (routes.nonEmpty) Some(routes.mkString("\n\n", "\n\n", "\n"))
    else None
  }

  def generateImports(packageName: String, codeProvidedPackage: String, serviceName: String): Seq[Tree] =
    Seq(
      IMPORT(packageName, "_"),
      IMPORT(packageName + ".json", "_"),
      IMPORT("play.api.mvc.Results", "_"),
      IMPORT("play.api.mvc", "_"),
      IMPORT("play.api.libs.json", "_"),
      IMPORT("javax.inject", "_"),
      IMPORT(codeProvidedPackage, serviceName)
    )

  def generate(fileName: String, packageName: String, codeProvidedPackage: String): Iterable[SyntaxString] = {
    val swagger = new SwaggerParser().read(fileName)

    val controllerPackageName =
      packageName + ".controller"

    val controllerName =
      controllerNameFromFileName(fileName)

    val serviceName =
      serviceNameFromFileName(fileName)

    val completePaths =
      swagger.getPaths.asScala.keySet.toSeq

    def composeController(p: String): Seq[Tree] = {
      val path = swagger.getPath(p)
      if (path == null) return Seq()

      val ops: Seq[Operation] =
        Seq(Option(path.getDelete), Option(path.getGet), Option(path.getPost), Option(path.getPut)).flatten

      for {
        op <- ops
      } yield {

        try if (!op.getProduces.asScala.forall(_ == "application/json"))
          println("WARNING - only 'application/json' is supported")
        catch {
          case _: Throwable =>
        }

        val methodName =
          if (op.getOperationId != null) op.getOperationId
          else throw new Exception("Please provide an operationId in: " + p)

        val okRespType: (String, Option[Type]) =
          getOkRespType(op) getOrElse {
            throw new Exception(s"cannot determine Ok result type for $methodName")
          }

        genControllerMethod(methodName, op.getParameters.asScala, okRespType)
      }
    }

    val imports =
      BLOCK {
        generateImports(packageName, codeProvidedPackage, serviceName)
      } inPackage controllerPackageName

    val tree = CLASSDEF(controllerName + " @Inject()") withParams PARAM("service", TYPE_REF(serviceName)) :=
      BLOCK(completePaths.map(composeController).flatten)

    Seq(SyntaxString(controllerName + ".scala", treeToString(imports), treeToString(tree)))
  }

  def genControllerMethod(methodName: String, params: Seq[Parameter], resType: (String, Option[Type])): Tree = {
    val bodyParams = getParamsFromBody(params)

    if (bodyParams.size > 1) throw new Exception(s"Only one parameter in body is allowed in method $methodName")

    val methodParams = getMethodParams(params)

    val ACTION =
      REF("Action")

    val ANSWER =
      resType._2.map { typ =>
        REF(resType._1) APPLY (
          REF("Json") DOT "toJson" APPLYTYPE typ APPLY (
            REF("service") DOT methodName APPLY (methodParams ++ bodyParams).map(x => REF(x._1))
          )
        )
      }.getOrElse(
        BLOCK {
          Seq(
            REF("service") DOT methodName APPLY (methodParams ++ bodyParams).map(x => REF(x._1)),
            REF(resType._1)
          )
        }
      )

    val ERROR =
      REF("BadRequest") APPLY (REF("service") DOT "onError" APPLY (LIT(methodName), REF("err")))

    val BODY_WITH_EXCEPTION_HANDLE =
      TRY {
        BLOCK {
          (bodyParams.values: Seq[Tree]) :+
            ANSWER
        }
      } CATCH (CASE(REF("err") withType RootClass.newClass("Throwable")) ==> BLOCK {
        REF("err") DOT "printStackTrace"
        ERROR
      }) ENDTRY

    val tree: Tree =
      DEFINFER(methodName) withParams methodParams.values := BLOCK {
        ACTION APPLY {
          LAMBDA(PARAM("request")) ==>
            BODY_WITH_EXCEPTION_HANDLE
        }
      }

    tree
  }

  def getParamsFromBody(params: Seq[Parameter]): Map[String, ValDef] =
    params
      .filter {
        case body: BodyParameter => true
        case _ => false
      }
      .flatMap {
        case bp: BodyParameter =>
          val tree: ValDef = VAL(bp.getName) :=
              REF("Json") DOT "fromJson" APPLYTYPE noOptParamType(bp) APPLY (REF("request") DOT "body" DOT "asJson" DOT "get") DOT "get"

          Some(bp.getName -> tree)
        case _ =>
          None
      }
      .toMap
}

class DefaultAsyncServerGenerator extends DefaultServerGenerator {

  override def generateImports(packageName: String, codeProvidedPackage: String, serviceName: String): Seq[Tree] =
    super.generateImports(packageName, codeProvidedPackage, serviceName) :+
      IMPORT("play.api.libs.concurrent.Execution.Implicits", "_")

  override def genControllerMethod(methodName: String, params: Seq[Parameter], resType: (String, Option[Type])): Tree = {
    val bodyParams = getParamsFromBody(params)

    if (bodyParams.size > 1) throw new Exception(s"Only one parameter in body is allowed in method $methodName")

    val methodParams = getMethodParams(params)

    val ACTION =
      REF("Action.async")

    val ANSWER =
      REF("service") DOT methodName APPLY (methodParams ++ bodyParams).map(x => REF(x._1)) DOT "map" APPLY (
        LAMBDA(PARAM("answer")) ==> resType._2.map { typ =>
          REF(resType._1) APPLY (
            REF("Json") DOT "toJson" APPLYTYPE typ APPLY REF("answer")
          )
        }.getOrElse(
          REF(resType._1)
        )
      )

    val ERROR =
      REF("service") DOT "onError" APPLY (LIT(methodName), REF("err")) DOT "map" APPLY (LAMBDA(PARAM("errAnswer")) ==> REF(
        "BadRequest") APPLY REF("errAnswer"))

    val BODY_WITH_EXCEPTION_HANDLE =
      BLOCK {
        (bodyParams.values: Seq[Tree]) :+
          ANSWER
      } DOT "recoverWith" APPLY BLOCK(CASE(REF("err") withType RootClass.newClass("Throwable")) ==> BLOCK {
        REF("err") DOT "printStackTrace"
        ERROR
      })

    val tree: Tree =
      DEFINFER(methodName) withParams methodParams.values := BLOCK {
        ACTION APPLY {
          LAMBDA(PARAM("request")) ==>
            BODY_WITH_EXCEPTION_HANDLE
        }
      }

    tree
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy