From 5d9a93473e3988b67ecdb06e1a6271865dde723f Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Fri, 21 Dec 2018 17:19:51 -0800 Subject: [PATCH 01/86] Add initial Java language types --- build.sbt | 16 ++++---- .../guardrail/languages/JavaLanguage.scala | 38 +++++++++++++++++++ 2 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 modules/codegen/src/main/scala/com/twilio/guardrail/languages/JavaLanguage.scala diff --git a/build.sbt b/build.sbt index 46da14c54f..199c3a2235 100644 --- a/build.sbt +++ b/build.sbt @@ -17,6 +17,7 @@ val catsEffectVersion = "1.0.0" val circeVersion = "0.10.1" val http4sVersion = "0.19.0" val scalatestVersion = "3.0.7" +val javaparserVersion = "3.7.1" val endpointsVersion = "0.8.0" mainClass in assembly := Some("com.twilio.guardrail.CLI") @@ -150,13 +151,14 @@ lazy val codegen = (project in file("modules/codegen")) (name := "guardrail") +: codegenSettings, libraryDependencies ++= testDependencies ++ Seq( - "org.scalameta" %% "scalameta" % "4.1.0", - "io.swagger.parser.v3" % "swagger-parser" % "2.0.8", - "org.tpolecat" %% "atto-core" % "0.6.3", - "org.typelevel" %% "cats-core" % catsVersion, - "org.typelevel" %% "cats-kernel" % catsVersion, - "org.typelevel" %% "cats-macros" % catsVersion, - "org.typelevel" %% "cats-free" % catsVersion + "org.scalameta" %% "scalameta" % "4.1.0", + "com.github.javaparser" % "javaparser-symbol-solver-core" % javaparserVersion, + "io.swagger.parser.v3" % "swagger-parser" % "2.0.8", + "org.tpolecat" %% "atto-core" % "0.6.3", + "org.typelevel" %% "cats-core" % catsVersion, + "org.typelevel" %% "cats-kernel" % catsVersion, + "org.typelevel" %% "cats-macros" % catsVersion, + "org.typelevel" %% "cats-free" % catsVersion ), scalacOptions += "-language:higherKinds", bintrayRepository := { diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/languages/JavaLanguage.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/languages/JavaLanguage.scala new file mode 100644 index 0000000000..b1c10b3e1a --- /dev/null +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/languages/JavaLanguage.scala @@ -0,0 +1,38 @@ +package com.twilio.guardrail.languages + +class JavaLanguage extends LanguageAbstraction { + + type Statement = com.github.javaparser.ast.stmt.Statement + + type Import = com.github.javaparser.ast.ImportDeclaration + + // Terms + + type Term = com.github.javaparser.ast.Node + type TermName = com.github.javaparser.ast.expr.Name + type TermSelect = com.github.javaparser.ast.expr.Name + + // Declarations + type MethodDeclaration = com.github.javaparser.ast.body.MethodDeclaration + + // Definitions + type Definition = com.github.javaparser.ast.body.BodyDeclaration[_] + type AbstractClass = Nothing + type ClassDefinition = com.github.javaparser.ast.body.ClassOrInterfaceDeclaration + type InterfaceDefinition = com.github.javaparser.ast.body.ClassOrInterfaceDeclaration + type ObjectDefinition = Nothing + type Trait = com.github.javaparser.ast.body.ClassOrInterfaceDeclaration + + // Functions + type InstanceMethod = Nothing + type StaticMethod = Nothing + + // Values + type ValueDefinition = com.github.javaparser.ast.body.VariableDeclarator + type MethodParameter = com.github.javaparser.ast.body.Parameter + type Type = com.github.javaparser.ast.`type`.Type + type TypeName = com.github.javaparser.ast.expr.Name + + // Result + type FileContents = com.github.javaparser.ast.CompilationUnit +} From 97327d446f13ea12530c0cbe7196ed4948016b9c Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Sat, 12 Jan 2019 00:06:50 -0800 Subject: [PATCH 02/86] simplify StaticDefns to just one 'definitions' member --- .../com/twilio/guardrail/ProtocolElems.scala | 6 +- .../generators/AkkaHttpClientGenerator.scala | 4 +- .../generators/CirceProtocolGenerator.scala | 22 +- .../generators/EndpointsClientGenerator.scala | 2 +- .../generators/Http4sClientGenerator.scala | 4 +- .../guardrail/generators/JavaGenerator.scala | 229 ++++++++++++++++++ .../guardrail/generators/syntax/Java.scala | 34 +++ .../guardrail/generators/syntax/Scala.scala | 2 - 8 files changed, 276 insertions(+), 27 deletions(-) create mode 100644 modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala create mode 100644 modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolElems.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolElems.scala index f7f997be44..a9696a8182 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolElems.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolElems.scala @@ -8,11 +8,7 @@ import com.twilio.guardrail.languages.LA import com.twilio.guardrail.terms.{ ScalaTerms, SwaggerTerms } import com.twilio.guardrail.protocol.terms.protocol.ProtocolSupportTerms -case class StaticDefns[L <: LA](className: String, - extraImports: List[L#Import], - members: List[L#ObjectDefinition], - definitions: List[L#Definition], - values: List[L#ValueDefinition]) +case class StaticDefns[L <: LA](className: String, extraImports: List[L#Import], definitions: List[L#Definition]) sealed trait ProtocolElems[L <: LA] diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpClientGenerator.scala index 1dfa3b6551..311abf35a0 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpClientGenerator.scala @@ -363,9 +363,7 @@ object AkkaHttpClientGenerator { StaticDefns[ScalaLanguage]( className = clientName, extraImports = List.empty, - members = List.empty, - definitions = decls, - values = List.empty + definitions = decls ) ) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala index fb27e4221e..17057631e5 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala @@ -84,11 +84,11 @@ object CirceProtocolGenerator { StaticDefns[ScalaLanguage]( className = clsName, extraImports = List.empty[Import], - members = List(members), - definitions = List( - q"def parse(value: String): Option[${Type.Name(clsName)}] = values.find(_.value == value)" - ), - values = terms ++ List(values) ++ List(encoder, decoder) ++ implicits + definitions = List(members) ++ + terms ++ + List(values, encoder, decoder) ++ + implicits ++ + List(q"def parse(value: String): Option[${Type.Name(clsName)}] = values.find(_.value == value)") ) ) case BuildAccessor(clsName, termName) => @@ -299,9 +299,7 @@ object CirceProtocolGenerator { StaticDefns[ScalaLanguage]( className = clsName, extraImports = extraImports, - members = List.empty, - definitions = List.empty, - values = List(encoder, decoder) + definitions = List(encoder, decoder) ) ) } @@ -389,12 +387,10 @@ object CirceProtocolGenerator { case RenderADTStaticDefns(clsName, discriminator, encoder, decoder) => Target.pure( - StaticDefns( + StaticDefns[ScalaLanguage]( className = clsName, - extraImports = List.empty, - members = List.empty, - definitions = List.empty, - values = List( + extraImports = List.empty[Import], + definitions = List[Defn]( q"val discriminator: String = ${Lit.String(discriminator)}", encoder, decoder diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsClientGenerator.scala index c7ea731769..c749231783 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsClientGenerator.scala @@ -419,7 +419,7 @@ object EndpointsClientGenerator { q"def apply(...${ctorArgs}): ${Type.Name(clientName)} = ${ctorCall}" ) - Target.pure(StaticDefns[ScalaLanguage](clientName, List.empty, List.empty, definitions, List.empty)) + Target.pure(StaticDefns[ScalaLanguage](clientName, List.empty, definitions)) case BuildClient(clientName, tracingName, serverUrls, basePath, ctorArgs, clientCalls, supportDefinitions, tracing) => val (endpointDefs, rest0) = supportDefinitions.partition { case q"val $name = endpoint(...$_)" => true diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sClientGenerator.scala index cbb088a345..d5890090c4 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sClientGenerator.scala @@ -356,9 +356,7 @@ object Http4sClientGenerator { StaticDefns[ScalaLanguage]( className = clientName, extraImports = List.empty, - members = List.empty, - definitions = decls, - values = List.empty + definitions = decls ) ) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala new file mode 100644 index 0000000000..b8ce6121c0 --- /dev/null +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala @@ -0,0 +1,229 @@ +package com.twilio.guardrail.generators + +import cats.~> +import cats.instances.option._ +import cats.syntax.traverse._ +import com.github.javaparser.ast.{ CompilationUnit, ImportDeclaration, PackageDeclaration } +import com.github.javaparser.ast.`type`.{ PrimitiveType, Type } +import com.github.javaparser.ast.body.{ BodyDeclaration, ClassOrInterfaceDeclaration, Parameter } +import com.github.javaparser.ast.expr._ +import com.github.javaparser.ast.stmt.Statement +import com.twilio.guardrail._ +import com.twilio.guardrail.Common.resolveFile +import com.twilio.guardrail.generators.syntax.Java._ +import com.twilio.guardrail.languages.JavaLanguage +import com.twilio.guardrail.terms._ +import java.nio.charset.StandardCharsets + +object JavaGenerator { + object JavaInterp extends (ScalaTerm[JavaLanguage, ?] ~> Target) { + def buildPkgDecl(parts: List[String]): Target[PackageDeclaration] = safeParseName(parts.mkString(".")).map(new PackageDeclaration(_)) + + def apply[T](term: ScalaTerm[JavaLanguage, T]): Target[T] = term match { + case LitString(value) => Target.pure(new StringLiteralExpr(value)) + case LitFloat(value) => Target.pure(new DoubleLiteralExpr(value)) + case LitDouble(value) => Target.pure(new DoubleLiteralExpr(value)) + case LitInt(value) => Target.pure(new IntegerLiteralExpr(value)) + case LitLong(value) => Target.pure(new LongLiteralExpr(value)) + case LitBoolean(value) => Target.pure(new BooleanLiteralExpr(value)) + case LiftOptionalType(value) => safeParseType(s"java.util.Optional<${value}>") + case LiftOptionalTerm(value) => safeParseExpression[MethodCallExpr](s"java.util.Optional.ofNullable(${value}") + case EmptyOptionalTerm() => safeParseExpression[MethodCallExpr]("java.util.Optional.empty()") + case LiftVectorType(value) => safeParseType(s"java.util.List<${value}>") + case LiftVectorTerm(value) => safeParseExpression[MethodCallExpr](s"java.util.Collections.singletonList(${value})") + case LiftMapType(value) => safeParseType(s"java.util.Map") + case LookupEnumDefaultValue(tpe, defaultValue, values) => { + // FIXME: Is there a better way to do this? There's a gap of coverage here + defaultValue match { + case s: StringLiteralExpr => + values + .find(_._1 == s.getValue) + .fold(Target.raiseError[Expression](s"Enumeration ${tpe} is not defined for default value ${s.getValue}"))(value => Target.pure(value._3)) + case _ => + Target.raiseError[Expression](s"Enumeration ${tpe} somehow has a default value that isn't a string") + } + } + case EmbedArray(tpe) => + tpe match { + case SwaggerUtil.Deferred(tpe) => + Target.pure(SwaggerUtil.DeferredArray(tpe)) + case SwaggerUtil.DeferredArray(_) => + Target.raiseError("FIXME: Got an Array of Arrays, currently not supported") + case SwaggerUtil.DeferredMap(_) => + Target.raiseError("FIXME: Got an Array of Maps, currently not supported") + } + case EmbedMap(tpe) => + tpe match { + case SwaggerUtil.Deferred(inner) => Target.pure(SwaggerUtil.DeferredMap(inner)) + case SwaggerUtil.DeferredMap(_) => + Target.raiseError("FIXME: Got a map of maps, currently not supported") + case SwaggerUtil.DeferredArray(_) => + Target.raiseError("FIXME: Got a map of arrays, currently not supported") + } + case ParseType(tpe) => + safeParseType(tpe) + .fold({ err => + println(s"Warning: Unparsable x-java-type: ${tpe} ${err}") + None + }, Option.apply) + case ParseTypeName(tpe) => + Option(tpe).map(_.trim).filterNot(_.isEmpty).map(safeParseType).sequence + + case PureTermName(tpe) => + Option(tpe).map(_.trim).filterNot(_.isEmpty).map(safeParseName).getOrElse(Target.raiseError("A structure's name is empty")) + + case PureTypeName(tpe) => + Option(tpe).map(_.trim).filterNot(_.isEmpty).map(safeParseName).getOrElse(Target.raiseError("A structure's name is empty")) + + case PureMethodParameter(name, tpe, default) => + // FIXME: java methods do not support default param values -- what should we do here? + safeParseParameter(s"final ${tpe} ${name}") + + case TypeNamesEqual(a, b) => + Target.pure(a.asString == b.asString) + + case TypesEqual(a, b) => + Target.pure(a.equals(b)) + + case ExtractTypeName(tpe) => + Target.pure(tpe match { + case x: Name => Option(x) + case _ => Option.empty + }) + case ExtractTermName(term) => + Target.pure(term.getIdentifier) + + case AlterMethodParameterName(param, name) => + safeParseSimpleName(name.asString).map(new Parameter(param.getType, _)) + + case DateType() => safeParseType("java.time.LocalDate") + case DateTimeType() => safeParseType("java.time.OffsetDateTime") + case StringType(format) => format.fold(safeParseType("String"))(safeParseType) + case FloatType() => Target.pure(PrimitiveType.floatType) + case DoubleType() => Target.pure(PrimitiveType.doubleType) + case NumberType(format) => safeParseType("java.math.BigDecimal") + case IntType() => Target.pure(PrimitiveType.intType) + case LongType() => Target.pure(PrimitiveType.longType) + case IntegerType(format) => safeParseType("java.math.BigInteger") + case BooleanType(format) => Target.pure(PrimitiveType.booleanType) + case ArrayType(format) => safeParseType("java.util.List") + case FallbackType(tpe, format) => safeParseType(tpe) + + case WidenTypeName(tpe) => safeParseType(tpe.asString) + case WidenTermSelect(value) => Target.pure(value) // FIXME: what is a TermSelect??? + + case RenderImplicits(pkgPath, pkgName, frameworkImports, jsonImports, customImports) => + // FIXME + Target.pure(WriteTree(pkgPath.resolve("Implicits.java"), new Array[Byte](0))) + + case RenderFrameworkImplicits(pkgPath, pkgName, frameworkImports, jsonImports, frameworkImplicits, frameworkImplicitName) => + // FIXME + Target.pure(WriteTree(pkgPath.resolve(s"FrameworkImplicits.java"), new Array[Byte](0))) + + case WritePackageObject(dtoPackagePath, dtoComponents, customImports, packageObjectImports, protocolImports, packageObjectContents, extraTypes) => + // FIXME + Target.pure(WriteTree(dtoPackagePath.resolve("Package.java"), new Array[Byte](0))) + + case WriteProtocolDefinition(outputPath, pkgName, definitions, dtoComponents, imports, elem) => + elem match { + case EnumDefinition(_, _, _, cls, staticDecls) => + val clsCopy = cls.clone() + buildPkgDecl(pkgName).map { pkgDecl => + val cu = new CompilationUnit() + cu.setPackageDeclaration(pkgDecl) + imports.map(cu.addImport) + val clsCopy = cls.clone() + staticDecls.foreach(clsCopy.addMember) + cu.addType(clsCopy) + ( + List( + WriteTree( + resolveFile(outputPath)(dtoComponents).resolve(s"${cls.getName.getIdentifier}.java"), + cu.toString(printer).getBytes(StandardCharsets.UTF_8) + ) + ), + List.empty[Statement] + ) + } + + case ClassDefinition(_, _, cls, staticDecls, _) => + buildPkgDecl(pkgName).map { pkgDecl => + val cu = new CompilationUnit() + cu.setPackageDeclaration(pkgDecl) + imports.map(cu.addImport) + val clsCopy = cls.clone() + staticDecls.foreach(clsCopy.addMember) + cu.addType(clsCopy) + ( + List( + WriteTree( + resolveFile(outputPath)(dtoComponents).resolve(s"${cls.getName.getIdentifier}.java"), + cu.toString(printer).getBytes(StandardCharsets.UTF_8) + ) + ), + List.empty[Statement] + ) + } + + case ADT(name, tpe, trt, staticDecls) => + buildPkgDecl(pkgName).map { pkgDecl => + val cu = new CompilationUnit() + cu.setPackageDeclaration(pkgDecl) + imports.map(cu.addImport) + val trtCopy = trt.clone() + staticDecls.foreach(trtCopy.addMember) + cu.addType(trtCopy) + ( + List( + WriteTree( + resolveFile(outputPath)(dtoComponents).resolve(s"${name}.java"), + cu.toString(printer).getBytes(StandardCharsets.UTF_8) + ) + ), + List.empty[Statement] + ) + } + + case RandomType(_, _) => + (List.empty, List.empty) + } + case WriteClient(pkgPath, + pkgName, + customImports, + frameworkImplicitName, + dtoComponents, + Client(pkg, clientName, imports, staticDecls, client, responseDefinitions)) => + for { + pkgDecl <- buildPkgDecl(pkgName ++ pkg) + implicitsImport <- safeParseName((pkgName ++ List("Implicits", "*")).mkString(".")).map(name => new ImportDeclaration(name, false, true)) + frameworkImplicitsImport <- safeParseName((pkgName ++ List(frameworkImplicitName.getIdentifier, "*")).mkString(".")) + .map(name => new ImportDeclaration(name, false, true)) + dtoComponentsImport <- safeParseName((dtoComponents :+ "*").mkString(".")).map(name => new ImportDeclaration(name, false, true)) + } yield { + val cu = new CompilationUnit() + cu.setPackageDeclaration(pkgDecl) + imports.map(cu.addImport) + customImports.map(cu.addImport) + cu.addImport(implicitsImport) + cu.addImport(frameworkImplicitsImport) + cu.addImport(dtoComponentsImport) + val clientCopy = client.head.merge.clone() // FIXME: WriteClient needs to be altered to return `NonEmptyList[WriteTree]` to accommodate Java not being able to put multiple classes in the same file. Scala just jams them all together, but this could be improved over there as well. + staticDecls.foreach(clientCopy.addMember) + responseDefinitions.foreach(clientCopy.addMember) + cu.addType(clientCopy) + ( + List( + WriteTree( + resolveFile(pkgPath)(pkg :+ s"${clientName}.java"), + cu.toString(printer).getBytes(StandardCharsets.UTF_8) + ) + ), + List.empty[Statement] + ) + } + case WriteServer(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, Server(pkg, extraImports, src)) => + Target.raiseError("TODO: java server generation") + } + } + +} diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala new file mode 100644 index 0000000000..56606a40b7 --- /dev/null +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -0,0 +1,34 @@ +package com.twilio.guardrail.generators.syntax + +import com.github.javaparser.JavaParser +import com.github.javaparser.ast.CompilationUnit +import com.github.javaparser.ast.`type`.Type +import com.github.javaparser.ast.body.Parameter +import com.github.javaparser.ast.expr.{ Expression, Name, SimpleName } +import com.github.javaparser.printer.PrettyPrinterConfiguration +import com.github.javaparser.printer.PrettyPrinterConfiguration.IndentType +import com.twilio.guardrail.Target +import scala.reflect.ClassTag +import scala.util.Try + +object Java { + private[this] def safeParse[T](parser: String => T, s: String)(implicit cls: ClassTag[T]): Target[T] = + Try(parser(s)).toEither.fold(t => Target.raiseError(s"Unable to parse '${t}' to a ${cls.getClass.getSimpleName}: ${t.getMessage}"), Target.pure) + + def safeParseCode(s: String): Target[CompilationUnit] = safeParse(JavaParser.parse, s) + def safeParseSimpleName(s: String): Target[SimpleName] = safeParse(JavaParser.parseSimpleName, s) + def safeParseName(s: String): Target[Name] = safeParse(JavaParser.parseName, s) + def safeParseType(s: String): Target[Type] = safeParse(JavaParser.parseType, s) + def safeParseExpression[T <: Expression](s: String): Target[T] = safeParse(JavaParser.parseExpression, s) + def safeParseParameter(s: String): Target[Parameter] = safeParse(JavaParser.parseParameter, s) + + val printer: PrettyPrinterConfiguration = new PrettyPrinterConfiguration() + .setColumnAlignFirstMethodChain(true) + .setColumnAlignParameters(true) + .setIndentSize(4) + .setIndentType(IndentType.SPACES) + .setOrderImports(true) + .setPrintComments(true) + .setPrintJavadoc(true) + .setTabWidth(4) +} diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Scala.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Scala.scala index 1146051bea..b37b7525e4 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Scala.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Scala.scala @@ -43,8 +43,6 @@ object Scala { q""" object ${Term.Name(staticDefns.className)} { ..${staticDefns.extraImports} - ..${staticDefns.members} - ..${staticDefns.values} ..${staticDefns.definitions} } """ From 7d9ac1257383901e1991e5aa1b048c9d73ca79b3 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Sat, 12 Jan 2019 00:26:39 -0800 Subject: [PATCH 03/86] add initial JavaGenerator (still missing some things though) --- .../guardrail/generators/JavaGenerator.scala | 70 +++++++++---------- .../guardrail/generators/syntax/Java.scala | 12 ++-- 2 files changed, 40 insertions(+), 42 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala index b8ce6121c0..1a156963f3 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala @@ -3,7 +3,7 @@ package com.twilio.guardrail.generators import cats.~> import cats.instances.option._ import cats.syntax.traverse._ -import com.github.javaparser.ast.{ CompilationUnit, ImportDeclaration, PackageDeclaration } +import com.github.javaparser.ast.{ CompilationUnit, ImportDeclaration, Node, PackageDeclaration } import com.github.javaparser.ast.`type`.{ PrimitiveType, Type } import com.github.javaparser.ast.body.{ BodyDeclaration, ClassOrInterfaceDeclaration, Parameter } import com.github.javaparser.ast.expr._ @@ -27,10 +27,10 @@ object JavaGenerator { case LitLong(value) => Target.pure(new LongLiteralExpr(value)) case LitBoolean(value) => Target.pure(new BooleanLiteralExpr(value)) case LiftOptionalType(value) => safeParseType(s"java.util.Optional<${value}>") - case LiftOptionalTerm(value) => safeParseExpression[MethodCallExpr](s"java.util.Optional.ofNullable(${value}") - case EmptyOptionalTerm() => safeParseExpression[MethodCallExpr]("java.util.Optional.empty()") + case LiftOptionalTerm(value) => safeParseExpression[MethodCallExpr](s"java.util.Optional.ofNullable(${value}").map(identity) + case EmptyOptionalTerm() => safeParseExpression[MethodCallExpr]("java.util.Optional.empty()").map(identity) case LiftVectorType(value) => safeParseType(s"java.util.List<${value}>") - case LiftVectorTerm(value) => safeParseExpression[MethodCallExpr](s"java.util.Collections.singletonList(${value})") + case LiftVectorTerm(value) => safeParseExpression[MethodCallExpr](s"java.util.Collections.singletonList(${value})").map(identity) case LiftMapType(value) => safeParseType(s"java.util.Map") case LookupEnumDefaultValue(tpe, defaultValue, values) => { // FIXME: Is there a better way to do this? There's a gap of coverage here @@ -38,9 +38,9 @@ object JavaGenerator { case s: StringLiteralExpr => values .find(_._1 == s.getValue) - .fold(Target.raiseError[Expression](s"Enumeration ${tpe} is not defined for default value ${s.getValue}"))(value => Target.pure(value._3)) + .fold(Target.raiseError[Name](s"Enumeration ${tpe} is not defined for default value ${s.getValue}"))(value => Target.pure(value._3)) case _ => - Target.raiseError[Expression](s"Enumeration ${tpe} somehow has a default value that isn't a string") + Target.raiseError(s"Enumeration ${tpe} somehow has a default value that isn't a string") } } case EmbedArray(tpe) => @@ -62,12 +62,14 @@ object JavaGenerator { } case ParseType(tpe) => safeParseType(tpe) - .fold({ err => - println(s"Warning: Unparsable x-java-type: ${tpe} ${err}") - None - }, Option.apply) + .map(Option.apply) + .recover { + case err => + println(s"Warning: Unparsable x-java-type: ${tpe} ${err}") + None + } case ParseTypeName(tpe) => - Option(tpe).map(_.trim).filterNot(_.isEmpty).map(safeParseType).sequence + Option(tpe).map(_.trim).filterNot(_.isEmpty).map(safeParseName).sequence case PureTermName(tpe) => Option(tpe).map(_.trim).filterNot(_.isEmpty).map(safeParseName).getOrElse(Target.raiseError("A structure's name is empty")) @@ -86,10 +88,8 @@ object JavaGenerator { Target.pure(a.equals(b)) case ExtractTypeName(tpe) => - Target.pure(tpe match { - case x: Name => Option(x) - case _ => Option.empty - }) + safeParseName(tpe.asString).map(Option.apply) + case ExtractTermName(term) => Target.pure(term.getIdentifier) @@ -110,7 +110,7 @@ object JavaGenerator { case FallbackType(tpe, format) => safeParseType(tpe) case WidenTypeName(tpe) => safeParseType(tpe.asString) - case WidenTermSelect(value) => Target.pure(value) // FIXME: what is a TermSelect??? + case WidenTermSelect(value) => Target.pure(value) case RenderImplicits(pkgPath, pkgName, frameworkImports, jsonImports, customImports) => // FIXME @@ -126,14 +126,15 @@ object JavaGenerator { case WriteProtocolDefinition(outputPath, pkgName, definitions, dtoComponents, imports, elem) => elem match { - case EnumDefinition(_, _, _, cls, staticDecls) => + case EnumDefinition(_, _, _, cls, staticDefns) => val clsCopy = cls.clone() buildPkgDecl(pkgName).map { pkgDecl => val cu = new CompilationUnit() cu.setPackageDeclaration(pkgDecl) - imports.map(cu.addImport) + imports.foreach(cu.addImport) + staticDefns.extraImports.foreach(cu.addImport) val clsCopy = cls.clone() - staticDecls.foreach(clsCopy.addMember) + staticDefns.definitions.foreach(clsCopy.addMember) cu.addType(clsCopy) ( List( @@ -146,13 +147,14 @@ object JavaGenerator { ) } - case ClassDefinition(_, _, cls, staticDecls, _) => + case ClassDefinition(_, _, cls, staticDefns, _) => buildPkgDecl(pkgName).map { pkgDecl => val cu = new CompilationUnit() cu.setPackageDeclaration(pkgDecl) - imports.map(cu.addImport) + imports.foreach(cu.addImport) + staticDefns.extraImports.foreach(cu.addImport) val clsCopy = cls.clone() - staticDecls.foreach(clsCopy.addMember) + staticDefns.definitions.foreach(clsCopy.addMember) cu.addType(clsCopy) ( List( @@ -165,13 +167,14 @@ object JavaGenerator { ) } - case ADT(name, tpe, trt, staticDecls) => + case ADT(name, tpe, trt, staticDefns) => buildPkgDecl(pkgName).map { pkgDecl => val cu = new CompilationUnit() cu.setPackageDeclaration(pkgDecl) - imports.map(cu.addImport) + imports.foreach(cu.addImport) + staticDefns.extraImports.foreach(cu.addImport) val trtCopy = trt.clone() - staticDecls.foreach(trtCopy.addMember) + staticDefns.definitions.foreach(trtCopy.addMember) cu.addType(trtCopy) ( List( @@ -185,14 +188,14 @@ object JavaGenerator { } case RandomType(_, _) => - (List.empty, List.empty) + Target.pure((List.empty, List.empty)) } case WriteClient(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, - Client(pkg, clientName, imports, staticDecls, client, responseDefinitions)) => + Client(pkg, clientName, imports, staticDefns, client, responseDefinitions)) => for { pkgDecl <- buildPkgDecl(pkgName ++ pkg) implicitsImport <- safeParseName((pkgName ++ List("Implicits", "*")).mkString(".")).map(name => new ImportDeclaration(name, false, true)) @@ -208,17 +211,12 @@ object JavaGenerator { cu.addImport(frameworkImplicitsImport) cu.addImport(dtoComponentsImport) val clientCopy = client.head.merge.clone() // FIXME: WriteClient needs to be altered to return `NonEmptyList[WriteTree]` to accommodate Java not being able to put multiple classes in the same file. Scala just jams them all together, but this could be improved over there as well. - staticDecls.foreach(clientCopy.addMember) + staticDefns.definitions.foreach(clientCopy.addMember) responseDefinitions.foreach(clientCopy.addMember) cu.addType(clientCopy) - ( - List( - WriteTree( - resolveFile(pkgPath)(pkg :+ s"${clientName}.java"), - cu.toString(printer).getBytes(StandardCharsets.UTF_8) - ) - ), - List.empty[Statement] + WriteTree( + resolveFile(pkgPath)(pkg :+ s"${clientName}.java"), + cu.toString(printer).getBytes(StandardCharsets.UTF_8) ) } case WriteServer(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, Server(pkg, extraImports, src)) => diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index 56606a40b7..b962cb3345 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -15,12 +15,12 @@ object Java { private[this] def safeParse[T](parser: String => T, s: String)(implicit cls: ClassTag[T]): Target[T] = Try(parser(s)).toEither.fold(t => Target.raiseError(s"Unable to parse '${t}' to a ${cls.getClass.getSimpleName}: ${t.getMessage}"), Target.pure) - def safeParseCode(s: String): Target[CompilationUnit] = safeParse(JavaParser.parse, s) - def safeParseSimpleName(s: String): Target[SimpleName] = safeParse(JavaParser.parseSimpleName, s) - def safeParseName(s: String): Target[Name] = safeParse(JavaParser.parseName, s) - def safeParseType(s: String): Target[Type] = safeParse(JavaParser.parseType, s) - def safeParseExpression[T <: Expression](s: String): Target[T] = safeParse(JavaParser.parseExpression, s) - def safeParseParameter(s: String): Target[Parameter] = safeParse(JavaParser.parseParameter, s) + def safeParseCode(s: String): Target[CompilationUnit] = safeParse(JavaParser.parse, s) + def safeParseSimpleName(s: String): Target[SimpleName] = safeParse(JavaParser.parseSimpleName, s) + def safeParseName(s: String): Target[Name] = safeParse(JavaParser.parseName, s) + def safeParseType(s: String): Target[Type] = safeParse(JavaParser.parseType, s) + def safeParseExpression[T <: Expression](s: String)(implicit cls: ClassTag[T]): Target[T] = safeParse[T](JavaParser.parseExpression, s) + def safeParseParameter(s: String): Target[Parameter] = safeParse(JavaParser.parseParameter, s) val printer: PrettyPrinterConfiguration = new PrettyPrinterConfiguration() .setColumnAlignFirstMethodChain(true) From 068c4cf23be2fa527721515307608a564802a22b Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Thu, 24 Jan 2019 23:08:14 -0800 Subject: [PATCH 04/86] Adding stubs for java interpreter --- .../main/scala/com/twilio/guardrail/CLI.scala | 13 ++++++++- .../guardrail/generators/Java/AkkaHttp.scala | 29 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala index 06dc28fd80..93b3b8a9c2 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala @@ -7,7 +7,7 @@ import cats.~> import com.twilio.guardrail.core.CoreTermInterp import com.twilio.guardrail.terms.CoreTerm import com.twilio.swagger.core.{ LogLevel, LogLevels, StructuredLogger } -import com.twilio.guardrail.languages.{ LA, ScalaLanguage } +import com.twilio.guardrail.languages.{ LA, JavaLanguage, ScalaLanguage } import scala.io.AnsiColor @@ -134,8 +134,10 @@ object CLICommon { trait CLICommon { val scalaInterpreter: CoreTerm[ScalaLanguage, ?] ~> CoreTarget + val javaInterpreter: CoreTerm[JavaLanguage, ?] ~> CoreTarget val handleLanguage: PartialFunction[String, Array[String] => Unit] = { + case "java" => CLICommon.run(_)(javaInterpreter) case "scala" => CLICommon.run(_)(scalaInterpreter) } @@ -147,6 +149,7 @@ trait CLICommon { object CLI extends CLICommon { import com.twilio.guardrail.generators.{ AkkaHttp, Endpoints, Http4s } + import com.twilio.guardrail.generators.Java import scala.meta._ val scalaInterpreter = CoreTermInterp[ScalaLanguage]( "akka-http", { @@ -157,4 +160,12 @@ object CLI extends CLICommon { _.parse[Importer].toEither.bimap(err => UnparseableArgument("import", err.toString), importer => Import(List(importer))) } ) + + val javaInterpreter = CoreTermInterp[JavaLanguage]( + "akka-http", { + case "akka-http" => Java.AkkaHttp + }, { + _ => Left(UnparseableArgument("import", "FIXME: Java imports are not supported at this time")) + } + ) } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala new file mode 100644 index 0000000000..c6919ed56c --- /dev/null +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala @@ -0,0 +1,29 @@ +package com.twilio.guardrail +package generators +package Java + +import com.twilio.guardrail.languages.JavaLanguage +import cats.~> + +object AkkaHttp extends (CodegenApplication[JavaLanguage, ?] ~> Target) { + /* + val interpDefinitionPM: DefinitionPM[JavaLanguage, ?] ~> Target = ProtocolSupportTermInterp or ModelProtocolTermInterp + val interpDefinitionPME: DefinitionPME[JavaLanguage, ?] ~> Target = EnumProtocolTermInterp or interpDefinitionPM + val interpDefinitionPMEA: DefinitionPMEA[JavaLanguage, ?] ~> Target = ArrayProtocolTermInterp or interpDefinitionPME + val interpDefinitionPMEAP: DefinitionPMEAP[JavaLanguage, ?] ~> Target = PolyProtocolTermInterp or interpDefinitionPMEA + + val interpModel: ModelInterpreters[JavaLanguage, ?] ~> Target = interpDefinitionPMEAP + + val interpFrameworkC: FrameworkC[JavaLanguage, ?] ~> Target = ClientTermInterp or interpModel + val interpFrameworkCS: FrameworkCS[JavaLanguage, ?] ~> Target = ServerTermInterp or interpFrameworkC + val interpFrameworkCSF: FrameworkCSF[JavaLanguage, ?] ~> Target = FrameworkInterp or interpFrameworkCS + + val interpFramework: ClientServerTerms[JavaLanguage, ?] ~> Target = interpFrameworkCSF + + val parser: Parser[JavaLanguage, ?] ~> Target = SwaggerInterp or interpFramework + + val codegenApplication: CodegenApplication[JavaLanguage, ?] ~> Target = ScalaInterp or parser + */ + + def apply[T](x: CodegenApplication[JavaLanguage, T]): Target[T] = Target.raiseError(x.toString()) +} From 84eec2b00cb1b2fe5df976e9f5ecf9406974eaaf Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Thu, 24 Jan 2019 23:34:41 -0800 Subject: [PATCH 05/86] Adding runJavaExample --- build.sbt | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 199c3a2235..944409c427 100644 --- a/build.sbt +++ b/build.sbt @@ -44,8 +44,28 @@ val exampleCases: List[(java.io.File, String, Boolean, List[String])] = List( (sampleResource("pathological-parameters.yaml"), "pathological", false, List.empty) ) -val exampleArgs: List[List[String]] = exampleCases - .foldLeft(List.empty[List[String]])({ +val exampleJavaArgs: List[List[String]] = exampleCases + .foldLeft(List[List[String]](List("java")))({ + case (acc, (path, prefix, tracing, extra)) => + acc ++ (for { + kind <- List("client", "server") + frameworkPair <- List( + ("akka-http", "akkaHttp"), + ) + (frameworkName, frameworkPackage) = frameworkPair + tracingFlag = if (tracing) Option("--tracing") else Option.empty[String] + } yield + ( + List(s"--${kind}") ++ + List("--specPath", path.toString()) ++ + List("--outputPath", s"modules/sample/src/main/java/generated") ++ + List("--packageName", s"${prefix}.${kind}.${frameworkPackage}") ++ + List("--framework", frameworkName) + ) ++ tracingFlag ++ extra) + }) + +val exampleScalaArgs: List[List[String]] = exampleCases + .foldLeft(List[List[String]](List("scala")))({ case (acc, (path, prefix, tracing, extra)) => acc ++ (for { frameworkSuite <- List( @@ -66,12 +86,20 @@ val exampleArgs: List[List[String]] = exampleCases ) ++ tracingFlag ++ extra) }) +lazy val runJavaExample: TaskKey[Unit] = taskKey[Unit]("Run scala generator with example args") +fullRunTask( + runJavaExample, + Test, + "com.twilio.guardrail.CLI", + exampleJavaArgs.flatten.filter(_.nonEmpty): _* +) + lazy val runScalaExample: TaskKey[Unit] = taskKey[Unit]("Run scala generator with example args") fullRunTask( runScalaExample, Test, "com.twilio.guardrail.CLI", - exampleArgs.flatten.filter(_.nonEmpty): _* + exampleScalaArgs.flatten.filter(_.nonEmpty): _* ) artifact in (Compile, assembly) := { From 87d04ec7e7a5376e2f6384b3ebedca3e4aa8ae63 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Thu, 24 Jan 2019 23:34:52 -0800 Subject: [PATCH 06/86] Parsing imports --- .../src/main/scala/com/twilio/guardrail/CLI.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala index 93b3b8a9c2..45631aac5d 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala @@ -164,8 +164,14 @@ object CLI extends CLICommon { val javaInterpreter = CoreTermInterp[JavaLanguage]( "akka-http", { case "akka-http" => Java.AkkaHttp - }, { - _ => Left(UnparseableArgument("import", "FIXME: Java imports are not supported at this time")) + }, { str => + import com.github.javaparser.JavaParser + import scala.collection.JavaConverters._ + import scala.util.Try + (for { + imports <- Try(JavaParser.parse(s"import ${str};")).toOption.toRight(s"Unable to parse ${str} as an import") + stat <- imports.getImports().asScala.headOption.toRight(s"${str} was not an import") + } yield stat).leftMap(UnparseableArgument("import", _)) } ) } From 459bf98769dc0fcafc4d1d7d7752f76404c3b7d4 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Mon, 18 Feb 2019 14:36:59 -0800 Subject: [PATCH 07/86] Better logging for java syntax --- .../guardrail/generators/syntax/Java.scala | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index b962cb3345..c01850eeb5 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -1,5 +1,6 @@ package com.twilio.guardrail.generators.syntax +import cats.implicits._ import com.github.javaparser.JavaParser import com.github.javaparser.ast.CompilationUnit import com.github.javaparser.ast.`type`.Type @@ -12,15 +13,18 @@ import scala.reflect.ClassTag import scala.util.Try object Java { - private[this] def safeParse[T](parser: String => T, s: String)(implicit cls: ClassTag[T]): Target[T] = - Try(parser(s)).toEither.fold(t => Target.raiseError(s"Unable to parse '${t}' to a ${cls.getClass.getSimpleName}: ${t.getMessage}"), Target.pure) + private[this] def safeParse[T](log: String)(parser: String => T, s: String)(implicit cls: ClassTag[T]): Target[T] = { + Target.log.debug(log)(s) >> ( + Try(parser(s)).toEither.fold(t => Target.raiseError(s"Unable to parse '${s}' to a ${cls.runtimeClass.getName}: ${t.getMessage}"), Target.pure) + ) + } - def safeParseCode(s: String): Target[CompilationUnit] = safeParse(JavaParser.parse, s) - def safeParseSimpleName(s: String): Target[SimpleName] = safeParse(JavaParser.parseSimpleName, s) - def safeParseName(s: String): Target[Name] = safeParse(JavaParser.parseName, s) - def safeParseType(s: String): Target[Type] = safeParse(JavaParser.parseType, s) - def safeParseExpression[T <: Expression](s: String)(implicit cls: ClassTag[T]): Target[T] = safeParse[T](JavaParser.parseExpression, s) - def safeParseParameter(s: String): Target[Parameter] = safeParse(JavaParser.parseParameter, s) + def safeParseCode(s: String): Target[CompilationUnit] = safeParse("safeParseCode")(JavaParser.parse, s) + def safeParseSimpleName(s: String): Target[SimpleName] = safeParse("safeParseSimpleName")(JavaParser.parseSimpleName, s) + def safeParseName(s: String): Target[Name] = safeParse("safeParseName")(JavaParser.parseName, s) + def safeParseType(s: String): Target[Type] = safeParse("safeParseType")(JavaParser.parseType, s) + def safeParseExpression[T <: Expression](s: String)(implicit cls: ClassTag[T]): Target[T] = safeParse[T]("safeParseExpression")(JavaParser.parseExpression, s) + def safeParseParameter(s: String): Target[Parameter] = safeParse("safeParseParameter")(JavaParser.parseParameter, s) val printer: PrettyPrinterConfiguration = new PrettyPrinterConfiguration() .setColumnAlignFirstMethodChain(true) From 57d1e868e2f49ab26ecd7db1703292b262b19317 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Mon, 18 Feb 2019 20:25:05 -0800 Subject: [PATCH 08/86] Switching away from primitive types --- .../twilio/guardrail/generators/JavaGenerator.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala index 1a156963f3..1a774e22e2 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala @@ -99,13 +99,13 @@ object JavaGenerator { case DateType() => safeParseType("java.time.LocalDate") case DateTimeType() => safeParseType("java.time.OffsetDateTime") case StringType(format) => format.fold(safeParseType("String"))(safeParseType) - case FloatType() => Target.pure(PrimitiveType.floatType) - case DoubleType() => Target.pure(PrimitiveType.doubleType) + case FloatType() => safeParseType("Float") + case DoubleType() => safeParseType("Double") case NumberType(format) => safeParseType("java.math.BigDecimal") - case IntType() => Target.pure(PrimitiveType.intType) - case LongType() => Target.pure(PrimitiveType.longType) + case IntType() => safeParseType("Int") + case LongType() => safeParseType("Long") case IntegerType(format) => safeParseType("java.math.BigInteger") - case BooleanType(format) => Target.pure(PrimitiveType.booleanType) + case BooleanType(format) => safeParseType("Boolean") case ArrayType(format) => safeParseType("java.util.List") case FallbackType(tpe, format) => safeParseType(tpe) From f4a3d0a149bc14952679c434b87d57ebb1e67693 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Mon, 18 Feb 2019 20:53:23 -0800 Subject: [PATCH 09/86] Enable --debug --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 944409c427..f5d42297de 100644 --- a/build.sbt +++ b/build.sbt @@ -45,7 +45,7 @@ val exampleCases: List[(java.io.File, String, Boolean, List[String])] = List( ) val exampleJavaArgs: List[List[String]] = exampleCases - .foldLeft(List[List[String]](List("java")))({ + .foldLeft(List[List[String]](List("java", "--debug")))({ case (acc, (path, prefix, tracing, extra)) => acc ++ (for { kind <- List("client", "server") From f11bd7050462c85590d93993cb42c1234559f4c3 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Mon, 18 Feb 2019 23:27:39 -0800 Subject: [PATCH 10/86] Wiring in JavaInterp --- .../guardrail/generators/Java/AkkaHttp.scala | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala index c6919ed56c..4a3ef9f73c 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala @@ -5,6 +5,8 @@ package Java import com.twilio.guardrail.languages.JavaLanguage import cats.~> +import JavaGenerator.JavaInterp + object AkkaHttp extends (CodegenApplication[JavaLanguage, ?] ~> Target) { /* val interpDefinitionPM: DefinitionPM[JavaLanguage, ?] ~> Target = ProtocolSupportTermInterp or ModelProtocolTermInterp @@ -19,11 +21,15 @@ object AkkaHttp extends (CodegenApplication[JavaLanguage, ?] ~> Target) { val interpFrameworkCSF: FrameworkCSF[JavaLanguage, ?] ~> Target = FrameworkInterp or interpFrameworkCS val interpFramework: ClientServerTerms[JavaLanguage, ?] ~> Target = interpFrameworkCSF + */ - val parser: Parser[JavaLanguage, ?] ~> Target = SwaggerInterp or interpFramework + val interpFramework = new (ClientServerTerms[JavaLanguage, ?] ~> Target) { + def apply[T](term: ClientServerTerms[JavaLanguage, T]): Target[T] = Target.raiseError(s"interpFramework: ${term.toString()}") + } - val codegenApplication: CodegenApplication[JavaLanguage, ?] ~> Target = ScalaInterp or parser - */ + val parser: Parser[JavaLanguage, ?] ~> Target = SwaggerGenerator[JavaLanguage] or interpFramework + + val codegenApplication: CodegenApplication[JavaLanguage, ?] ~> Target = JavaInterp or parser - def apply[T](x: CodegenApplication[JavaLanguage, T]): Target[T] = Target.raiseError(x.toString()) + def apply[T](x: CodegenApplication[JavaLanguage, T]): Target[T] = codegenApplication.apply(x) } From 25d171310126098127597dd0c26df8ef26c5acc5 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Mon, 18 Feb 2019 23:41:03 -0800 Subject: [PATCH 11/86] Fleshing out Jackson stub --- .../guardrail/generators/Java/AkkaHttp.scala | 20 ++-- .../generators/Java/JacksonGenerator.scala | 91 +++++++++++++++++++ 2 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala index 4a3ef9f73c..49770efc2d 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala @@ -3,12 +3,25 @@ package generators package Java import com.twilio.guardrail.languages.JavaLanguage +import com.twilio.guardrail.protocol.terms.client.ClientTerm +import com.twilio.guardrail.protocol.terms.server.ServerTerm +import com.twilio.guardrail.terms.framework.FrameworkTerm import cats.~> +import JacksonProtocolGenerator._ import JavaGenerator.JavaInterp object AkkaHttp extends (CodegenApplication[JavaLanguage, ?] ~> Target) { - /* + object ClientTermInterp extends (ClientTerm[JavaLanguage, ?] ~> Target) { + def apply[T](term: ClientTerm[JavaLanguage, T]): Target[T] = Target.raiseError(s"interpFramework: ${term.toString()}") + } + object FrameworkInterp extends (FrameworkTerm[JavaLanguage, ?] ~> Target) { + def apply[T](term: FrameworkTerm[JavaLanguage, T]): Target[T] = Target.raiseError(s"interpFramework: ${term.toString()}") + } + object ServerTermInterp extends (ServerTerm[JavaLanguage, ?] ~> Target) { + def apply[T](term: ServerTerm[JavaLanguage, T]): Target[T] = Target.raiseError(s"interpFramework: ${term.toString()}") + } + val interpDefinitionPM: DefinitionPM[JavaLanguage, ?] ~> Target = ProtocolSupportTermInterp or ModelProtocolTermInterp val interpDefinitionPME: DefinitionPME[JavaLanguage, ?] ~> Target = EnumProtocolTermInterp or interpDefinitionPM val interpDefinitionPMEA: DefinitionPMEA[JavaLanguage, ?] ~> Target = ArrayProtocolTermInterp or interpDefinitionPME @@ -21,11 +34,6 @@ object AkkaHttp extends (CodegenApplication[JavaLanguage, ?] ~> Target) { val interpFrameworkCSF: FrameworkCSF[JavaLanguage, ?] ~> Target = FrameworkInterp or interpFrameworkCS val interpFramework: ClientServerTerms[JavaLanguage, ?] ~> Target = interpFrameworkCSF - */ - - val interpFramework = new (ClientServerTerms[JavaLanguage, ?] ~> Target) { - def apply[T](term: ClientServerTerms[JavaLanguage, T]): Target[T] = Target.raiseError(s"interpFramework: ${term.toString()}") - } val parser: Parser[JavaLanguage, ?] ~> Target = SwaggerGenerator[JavaLanguage] or interpFramework diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala new file mode 100644 index 0000000000..b10c6866f4 --- /dev/null +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -0,0 +1,91 @@ +package com.twilio.guardrail +package generators +package Java + +import _root_.io.swagger.v3.oas.models.media._ +import cats.implicits._ +import cats.~> +import cats.data.NonEmptyList +import com.twilio.guardrail.extract.{ Default, ScalaEmptyIsNull, ScalaType } +import com.twilio.guardrail.shims._ +import com.twilio.guardrail.terms +import java.util.Locale +import com.twilio.guardrail.languages.{ LA, JavaLanguage } +import com.twilio.guardrail.protocol.terms.protocol._ +import scala.collection.JavaConverters._ +import scala.meta._ + +object JacksonProtocolGenerator { + import ProtocolGenerator._ + + object EnumProtocolTermInterp extends (EnumProtocolTerm[JavaLanguage, ?] ~> Target) { + def apply[T](term: EnumProtocolTerm[JavaLanguage, T]): Target[T] = term match { + case ExtractEnum(swagger) => + Target.raiseError(s"ExtractEnum($swagger)") + case RenderMembers(clsName, elems) => + Target.raiseError(s"RenderMembers($clsName, $elems)") + case EncodeEnum(clsName) => + Target.raiseError(s"EncodeEnum($clsName)") + case DecodeEnum(clsName) => + Target.raiseError(s"DecodeEnum($clsName)") + case RenderClass(clsName, tpe) => + Target.raiseError(s"RenderClass($clsName, $tpe)") + case RenderStaticDefns(clsName, members, accessors, encoder, decoder) => + Target.raiseError(s"RenderStaticDefns($clsName, $members, $accessors, $encoder, $decoder)") + case BuildAccessor(clsName, termName) => + Target.raiseError(s"BuildAccessor($clsName, $termName)") + } + } + + object ModelProtocolTermInterp extends (ModelProtocolTerm[JavaLanguage, ?] ~> Target) { + def apply[T](term: ModelProtocolTerm[JavaLanguage, T]): Target[T] = term match { + case ExtractProperties(swagger) => + Target.raiseError(s"ExtractProperties($swagger)") + case TransformProperty(clsName, name, property, meta, needCamelSnakeConversion, concreteTypes, isRequired) => + Target.raiseError(s"TransformProperty($clsName, $name, $property, $meta, $needCamelSnakeConversion, $concreteTypes, $isRequired)") + case RenderDTOClass(clsName, selfTerms, parents) => + Target.raiseError(s"RenderDTOClass($clsName, $selfTerms, $parents)") + case EncodeModel(clsName, needCamelSnakeConversion, selfParams, parents) => + Target.raiseError(s"EncodeModel($clsName, $needCamelSnakeConversion, $selfParams, $parents)") + case DecodeModel(clsName, needCamelSnakeConversion, selfParams, parents) => + Target.raiseError(s"DecodeModel($clsName, $needCamelSnakeConversion, $selfParams, $parents)") + case RenderDTOStaticDefns(clsName, deps, encoder, decoder) => + Target.raiseError(s"RenderDTOStaticDefns($clsName, $deps, $encoder, $decoder)") + } + } + + object ArrayProtocolTermInterp extends (ArrayProtocolTerm[JavaLanguage, ?] ~> Target) { + def apply[T](term: ArrayProtocolTerm[JavaLanguage, T]): Target[T] = term match { + case ExtractArrayType(arr, concreteTypes) => + Target.raiseError(s"ExtractArrayType($arr, $concreteTypes)") + } + } + + object ProtocolSupportTermInterp extends (ProtocolSupportTerm[JavaLanguage, ?] ~> Target) { + def apply[T](term: ProtocolSupportTerm[JavaLanguage, T]): Target[T] = term match { + case ExtractConcreteTypes(definitions) => + Target.raiseError(s"ExtractConcreteTypes($definitions)") + case ProtocolImports() => + Target.raiseError(s"ProtocolImports()") + case PackageObjectImports() => + Target.raiseError(s"PackageObjectImports()") + case PackageObjectContents() => + Target.raiseError(s"PackageObjectContents()") + } + } + + object PolyProtocolTermInterp extends (PolyProtocolTerm[JavaLanguage, ?] ~> Target) { + override def apply[A](fa: PolyProtocolTerm[JavaLanguage, A]): Target[A] = fa match { + case ExtractSuperClass(swagger, definitions) => + Target.raiseError(s"ExtractSuperClass($swagger, $definitions)") + case RenderADTStaticDefns(clsName, discriminator, encoder, decoder) => + Target.raiseError(s"RenderADTStaticDefns($clsName, $discriminator, $encoder, $decoder)") + case DecodeADT(clsName, children) => + Target.raiseError(s"DecodeADT($clsName, $children)") + case EncodeADT(clsName, children) => + Target.raiseError(s"EncodeADT($clsName, $children)") + case RenderSealedTrait(className, terms, discriminator, parents) => + Target.raiseError(s"RenderSealedTrait($className, $terms, $discriminator, $parents)") + } + } +} From a14fe7b1726c4472875a1ed4492e417059aa6106 Mon Sep 17 00:00:00 2001 From: Devon Stewart Date: Fri, 1 Mar 2019 03:30:49 -0800 Subject: [PATCH 12/86] Further work on Jackson stub --- .../generators/Java/JacksonGenerator.scala | 151 ++++++++++++++++-- .../guardrail/generators/syntax/Java.scala | 19 ++- modules/sample/src/main/resources/alias.yaml | 2 + 3 files changed, 161 insertions(+), 11 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala index b10c6866f4..b637452ea8 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -3,25 +3,44 @@ package generators package Java import _root_.io.swagger.v3.oas.models.media._ +import cats.data.NonEmptyList import cats.implicits._ import cats.~> -import cats.data.NonEmptyList +import com.github.javaparser.ast.`type`.{ ClassOrInterfaceType, Type } import com.twilio.guardrail.extract.{ Default, ScalaEmptyIsNull, ScalaType } +import com.twilio.guardrail.generators.syntax.Java._ +import com.twilio.guardrail.languages.{ LA, JavaLanguage } +import com.twilio.guardrail.protocol.terms.protocol._ import com.twilio.guardrail.shims._ import com.twilio.guardrail.terms import java.util.Locale -import com.twilio.guardrail.languages.{ LA, JavaLanguage } -import com.twilio.guardrail.protocol.terms.protocol._ import scala.collection.JavaConverters._ -import scala.meta._ +import com.github.javaparser.JavaParser +import com.github.javaparser.ast.{ CompilationUnit, Node, NodeList } +import com.github.javaparser.ast.stmt.{ BlockStmt, ExpressionStmt } +import com.github.javaparser.ast.Modifier.{ PRIVATE, PUBLIC, STATIC } +import com.github.javaparser.ast.expr.{ AssignExpr, BooleanLiteralExpr, DoubleLiteralExpr, Expression, FieldAccessExpr, IntegerLiteralExpr, LiteralExpr, LongLiteralExpr, NameExpr, SimpleName, StringLiteralExpr, ThisExpr } object JacksonProtocolGenerator { import ProtocolGenerator._ + def lookupTypeName(tpeName: String, concreteTypes: List[PropMeta[JavaLanguage]])(f: Type => Target[Type]): Option[Target[Type]] = + concreteTypes + .find(_.clsName == tpeName) + .map(_.tpe) + .map(f) + + object EnumProtocolTermInterp extends (EnumProtocolTerm[JavaLanguage, ?] ~> Target) { def apply[T](term: EnumProtocolTerm[JavaLanguage, T]): Target[T] = term match { case ExtractEnum(swagger) => - Target.raiseError(s"ExtractEnum($swagger)") + val enumEntries: Option[List[String]] = swagger match { + case x: StringSchema => + Option[java.util.List[String]](x.getEnum()).map(_.asScala.toList) + case x => + Option[java.util.List[_]](x.getEnum()).map(_.asScala.toList.map(_.toString())) + } + Target.pure(Either.fromOption(enumEntries, "Model has no enumerations")) case RenderMembers(clsName, elems) => Target.raiseError(s"RenderMembers($clsName, $elems)") case EncodeEnum(clsName) => @@ -40,11 +59,115 @@ object JacksonProtocolGenerator { object ModelProtocolTermInterp extends (ModelProtocolTerm[JavaLanguage, ?] ~> Target) { def apply[T](term: ModelProtocolTerm[JavaLanguage, T]): Target[T] = term match { case ExtractProperties(swagger) => - Target.raiseError(s"ExtractProperties($swagger)") + (swagger match { + case m: ObjectSchema => Target.pure(Option(m.getProperties)) + case comp: ComposedSchema => + Target.pure(Option(comp.getAllOf()).toList.flatMap(_.asScala.toList).lastOption.flatMap(prop => Option(prop.getProperties))) + case comp: Schema[_] if Option(comp.get$ref).isDefined => + Target.error(s"Attempted to extractProperties for a ${comp.getClass()}, unsure what to do here") + case _ => Target.pure(None) + }).map(_.map(_.asScala.toList).toList.flatten) + case TransformProperty(clsName, name, property, meta, needCamelSnakeConversion, concreteTypes, isRequired) => - Target.raiseError(s"TransformProperty($clsName, $name, $property, $meta, $needCamelSnakeConversion, $concreteTypes, $isRequired)") + def toCamelCase(s: String): String = + "[_\\.]([a-z])".r.replaceAllIn(s, m => m.group(1).toUpperCase(Locale.US)) + + for { + _ <- Target.log.debug("definitions", "circe", "modelProtocolTerm")(s"Generated ProtocolParameter(${term}, ${name}, ...)") + + argName = if (needCamelSnakeConversion) toCamelCase(name) else name + + defaultValue <- ((property match { + case _: MapSchema => + Option(safeParseExpression[LiteralExpr]("new java.util.Map<>()").map(x => x: Expression)) + case _: ArraySchema => + Option(safeParseExpression[LiteralExpr]("new java.util.List<>()").map(x => x: Expression)) + case p: BooleanSchema => + Default(p).extract[Boolean].map(x => Target.pure(new BooleanLiteralExpr(x))) + case p: NumberSchema if p.getFormat == "double" => + Default(p).extract[Double].map(x => Target.pure(new DoubleLiteralExpr(x))) + case p: NumberSchema if p.getFormat == "float" => + Default(p).extract[Float].map(x => Target.pure(new DoubleLiteralExpr(x))) + case p: IntegerSchema if p.getFormat == "int32" => + Default(p).extract[Int].map(x => Target.pure(new IntegerLiteralExpr(x))) + case p: IntegerSchema if p.getFormat == "int64" => + Default(p).extract[Long].map(x => Target.pure(new LongLiteralExpr(x))) + case p: StringSchema => + Default(p).extract[String].map(safeParseExpression[LiteralExpr](_).map(x => x: Expression)) + case _ => + None + }): Option[Target[Expression]]).sequence + + readOnlyKey = Option(name).filter(_ => Option(property.getReadOnly).contains(true)) + emptyToNull = (property match { + case d: DateSchema => ScalaEmptyIsNull(d) + case dt: DateTimeSchema => ScalaEmptyIsNull(dt) + case s: StringSchema => ScalaEmptyIsNull(s) + case _ => None + }).getOrElse(EmptyIsEmpty) + + tpeClassDep <- meta match { + case SwaggerUtil.Resolved(declType, classDep, _) => + Target.pure((declType, classDep)) + case SwaggerUtil.Deferred(tpeName) => + val tpe = concreteTypes.find(_.clsName == tpeName).map(x => Target.pure(x.tpe)).getOrElse { + println(s"Unable to find definition for ${tpeName}, just inlining") + safeParseType(tpeName) + } + tpe.map((_, Option.empty)) + case SwaggerUtil.DeferredArray(tpeName) => + safeParseType(s"java.util.List<${tpeName}>").map((_, Option.empty)) + case SwaggerUtil.DeferredMap(tpeName) => + safeParseType(s"java.util.List<${tpeName}>").map((_, Option.empty)) + } + (tpe, classDep) = tpeClassDep + + _declDefaultPair <- Option(isRequired) + .filterNot(_ == false) + .fold[Target[(Type, Option[Expression])]]( + (safeParseType(s"java.util.Optional<${tpe}>"), Option(defaultValue.fold[Target[Expression]](safeParseExpression[Expression](s"java.util.Optional.empty()"))(t => safeParseExpression[Expression](s"java.util.Optional.of($t)"))).sequence).mapN((_, _)) + )(Function.const(Target.pure((tpe, defaultValue))) _) + (finalDeclType, finalDefaultValue) = _declDefaultPair + term <- safeParseParameter(s"${finalDeclType} ${argName}") // FIXME: How do we deal with default values? .copy(default = finalDefaultValue) + dep = classDep.filterNot(_.value == clsName) // Filter out our own class name + } yield ProtocolParameter[JavaLanguage](term, name, dep, readOnlyKey, emptyToNull) + case RenderDTOClass(clsName, selfTerms, parents) => - Target.raiseError(s"RenderDTOClass($clsName, $selfTerms, $parents)") + val discriminators = parents.flatMap(_.discriminators) + val parenOpt = parents.headOption + val terms = (parents.reverse.flatMap(_.params.map(_.term)) ++ selfTerms).filterNot( + param => discriminators.contains(param.getName().getId()) + ) + + val compilationUnit = new CompilationUnit() + val dtoClass = compilationUnit.addClass(clsName).setPublic(true) + + parenOpt.foreach({ parent => + val directParent = new ClassOrInterfaceType(null, new SimpleName(parent.clsName), null, null) + val otherParents = parent.interfaces.map({ a => new ClassOrInterfaceType(null, new SimpleName(a), null, null) }) + dtoClass.setExtendedTypes( + new NodeList((directParent +: otherParents): _*) + ) + }) + + terms.foreach( term => + dtoClass.addField(term.getType(), term.getNameAsString(), PRIVATE) + ) + + val primaryConstructor = dtoClass.addConstructor(PUBLIC) + primaryConstructor.setParameters(new NodeList(terms: _*)) + primaryConstructor.setBody( + new BlockStmt( + new NodeList( + terms.map( term => + new ExpressionStmt(new AssignExpr(new FieldAccessExpr(new ThisExpr, term.getNameAsString()), new NameExpr(term.getName()), AssignExpr.Operator.ASSIGN)) + ): _* + ) + ) + ) + + Target.raiseError(dtoClass.toString()) + case EncodeModel(clsName, needCamelSnakeConversion, selfParams, parents) => Target.raiseError(s"EncodeModel($clsName, $needCamelSnakeConversion, $selfParams, $parents)") case DecodeModel(clsName, needCamelSnakeConversion, selfParams, parents) => @@ -57,7 +180,17 @@ object JacksonProtocolGenerator { object ArrayProtocolTermInterp extends (ArrayProtocolTerm[JavaLanguage, ?] ~> Target) { def apply[T](term: ArrayProtocolTerm[JavaLanguage, T]): Target[T] = term match { case ExtractArrayType(arr, concreteTypes) => - Target.raiseError(s"ExtractArrayType($arr, $concreteTypes)") + for { + result <- arr match { + case SwaggerUtil.Resolved(tpe, dep, default) => Target.pure(tpe) + case SwaggerUtil.Deferred(tpeName) => + Target.fromOption(lookupTypeName(tpeName, concreteTypes)(Target.pure(_)), s"Unresolved reference ${tpeName}").flatten + case SwaggerUtil.DeferredArray(tpeName) => + Target.fromOption(lookupTypeName(tpeName, concreteTypes)(tpe => safeParseType(s"Array<${tpe}>")), s"Unresolved reference ${tpeName}").flatten + case SwaggerUtil.DeferredMap(tpeName) => + Target.fromOption(lookupTypeName(tpeName, concreteTypes)(tpe => safeParseType(s"Array>")), s"Unresolved reference ${tpeName}").flatten + } + } yield result } } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index c01850eeb5..d5406b078a 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -2,7 +2,7 @@ package com.twilio.guardrail.generators.syntax import cats.implicits._ import com.github.javaparser.JavaParser -import com.github.javaparser.ast.CompilationUnit +import com.github.javaparser.ast.{ CompilationUnit, Node } import com.github.javaparser.ast.`type`.Type import com.github.javaparser.ast.body.Parameter import com.github.javaparser.ast.expr.{ Expression, Name, SimpleName } @@ -23,7 +23,7 @@ object Java { def safeParseSimpleName(s: String): Target[SimpleName] = safeParse("safeParseSimpleName")(JavaParser.parseSimpleName, s) def safeParseName(s: String): Target[Name] = safeParse("safeParseName")(JavaParser.parseName, s) def safeParseType(s: String): Target[Type] = safeParse("safeParseType")(JavaParser.parseType, s) - def safeParseExpression[T <: Expression](s: String)(implicit cls: ClassTag[T]): Target[T] = safeParse[T]("safeParseExpression")(JavaParser.parseExpression, s) + def safeParseExpression[T <: Expression](s: String)(implicit cls: ClassTag[T]): Target[T] = safeParse[T]("safeParseExpression")(JavaParser.parseExpression[T], s) def safeParseParameter(s: String): Target[Parameter] = safeParse("safeParseParameter")(JavaParser.parseParameter, s) val printer: PrettyPrinterConfiguration = new PrettyPrinterConfiguration() @@ -35,4 +35,19 @@ object Java { .setPrintComments(true) .setPrintJavadoc(true) .setTabWidth(4) + +/* + implicit class PrintStructure(value: Node) { + def toAST: String = { + @scala.annotation.tailrec + def walk(chunks: List[(String, Int, List[Node], String)]) = { + chunks.flatMap { case (pre, level, nodes, post) => + + } + } + + walk(List(("", 0, List(value), ""))) + } + } +*/ } diff --git a/modules/sample/src/main/resources/alias.yaml b/modules/sample/src/main/resources/alias.yaml index 5c5082262a..872eed38ee 100644 --- a/modules/sample/src/main/resources/alias.yaml +++ b/modules/sample/src/main/resources/alias.yaml @@ -37,6 +37,8 @@ definitions: $ref: '#/definitions/defArrayLong' propRef: type: object + required: + - arrayArray properties: param: $ref: "#/definitions/defLong" From 5e1893a891ff9aed10abae8ac8f0e9d43db32a3b Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Wed, 27 Feb 2019 02:44:44 -0800 Subject: [PATCH 13/86] Provide ProtocolParameter to RenderDTOClass Previously we just supplied the L#MethodParameter, which loses some information, like the original Swagger property name, and the default value (if any), which Java/Jackson needs. --- .../main/scala/com/twilio/guardrail/ProtocolGenerator.scala | 3 +-- .../twilio/guardrail/generators/CirceProtocolGenerator.scala | 4 ++-- .../guardrail/protocol/terms/protocol/ModelProtocolTerm.scala | 2 +- .../protocol/terms/protocol/ModelProtocolTerms.scala | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala index 2f0cea4d2f..9caf47992e 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala @@ -256,8 +256,7 @@ object ProtocolGenerator { val isRequired = requiredFields.contains(name) SwaggerUtil.propMeta[L, F](prop).flatMap(transformProperty(clsName, needCamelSnakeConversion, concreteTypes)(name, prop, _, isRequired)) }) - terms = params.map(_.term) - defn <- renderDTOClass(clsName, terms, parents) + defn <- renderDTOClass(clsName, params, parents) deps = params.flatMap(_.dep) encoder <- encodeModel(clsName, needCamelSnakeConversion, params, parents) decoder <- decodeModel(clsName, needCamelSnakeConversion, params, parents) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala index 17057631e5..3e56c933f4 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala @@ -170,10 +170,10 @@ object CirceProtocolGenerator { dep = classDep.filterNot(_.value == clsName) // Filter out our own class name } yield ProtocolParameter[ScalaLanguage](term, name, dep, readOnlyKey, emptyToNull) - case RenderDTOClass(clsName, selfTerms, parents) => + case RenderDTOClass(clsName, selfParams, parents) => val discriminators = parents.flatMap(_.discriminators) val parenOpt = parents.headOption - val terms = (parents.reverse.flatMap(_.params.map(_.term)) ++ selfTerms).filterNot( + val terms = (parents.reverse.flatMap(_.params.map(_.term)) ++ selfParams.map(_.term)).filterNot( param => discriminators.contains(param.name.value) ) val code = parenOpt diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerm.scala index e4cb759b62..689a240a6f 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerm.scala @@ -15,7 +15,7 @@ case class TransformProperty[L <: LA](clsName: String, concreteTypes: List[PropMeta[L]], isRequired: Boolean) extends ModelProtocolTerm[L, ProtocolParameter[L]] -case class RenderDTOClass[L <: LA](clsName: String, terms: List[L#MethodParameter], parents: List[SuperClass[L]] = Nil) +case class RenderDTOClass[L <: LA](clsName: String, params: List[ProtocolParameter[L]], parents: List[SuperClass[L]] = Nil) extends ModelProtocolTerm[L, L#ClassDefinition] case class EncodeModel[L <: LA](clsName: String, needCamelSnakeConversion: Boolean, params: List[ProtocolParameter[L]], parents: List[SuperClass[L]] = Nil) extends ModelProtocolTerm[L, L#ValueDefinition] diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerms.scala index 4032828ba4..d658c4d068 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerms.scala @@ -17,7 +17,7 @@ class ModelProtocolTerms[L <: LA, F[_]](implicit I: InjectK[ModelProtocolTerm[L, isRequired: Boolean ): Free[F, ProtocolParameter[L]] = Free.inject[ModelProtocolTerm[L, ?], F](TransformProperty[L](clsName, name, prop, meta, needCamelSnakeConversion, concreteTypes, isRequired)) - def renderDTOClass(clsName: String, terms: List[L#MethodParameter], parents: List[SuperClass[L]] = Nil): Free[F, L#ClassDefinition] = + def renderDTOClass(clsName: String, terms: List[ProtocolParameter[L]], parents: List[SuperClass[L]] = Nil): Free[F, L#ClassDefinition] = Free.inject[ModelProtocolTerm[L, ?], F](RenderDTOClass[L](clsName, terms, parents)) def encodeModel(clsName: String, needCamelSnakeConversion: Boolean, From 873ac4c890d69bd231bb630dff77eeccc26a2c8f Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Wed, 27 Feb 2019 02:58:26 -0800 Subject: [PATCH 14/86] Move toCamelCase and toPascalCase to a shared RichString implicit --- .../twilio/guardrail/ProtocolGenerator.scala | 14 ++--------- .../generators/CirceProtocolGenerator.scala | 6 ++--- .../guardrail/generators/ScalaParameter.scala | 10 ++------ .../guardrail/generators/syntax/package.scala | 25 +++++++++++++++++++ .../src/test/scala/core/issues/Issue145.scala | 6 ++--- .../test/scala/tests/core/BacktickTest.scala | 6 ++--- 6 files changed, 37 insertions(+), 30 deletions(-) create mode 100644 modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/package.scala diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala index 9caf47992e..62656ca5e4 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala @@ -5,6 +5,7 @@ import _root_.io.swagger.v3.oas.models.media.{ ArraySchema, ComposedSchema, Inte import cats.free.Free import cats.implicits._ import com.twilio.guardrail.extract.ScalaType +import com.twilio.guardrail.generators.syntax.RichString import com.twilio.guardrail.languages.LA import com.twilio.guardrail.protocol.terms.protocol._ import com.twilio.guardrail.shims._ @@ -65,21 +66,10 @@ object ProtocolGenerator { import E._ import Sc._ - val toPascalRegexes = List( - "[\\._-]([a-z])".r, // dotted, snake, or dashed case - "\\s+([a-zA-Z])".r, // spaces - "^([a-z])".r // initial letter - ) - - def toPascalCase(s: String): String = - toPascalRegexes.foldLeft(s)( - (accum, regex) => regex.replaceAllIn(accum, m => m.group(1).toUpperCase(Locale.US)) - ) - def validProg(enum: List[String], tpe: L#Type): Free[F, EnumDefinition[L]] = for { elems <- enum.traverse { elem => - val termName = toPascalCase(elem) + val termName = elem.toPascalCase for { valueTerm <- pureTermName(termName) accessor <- buildAccessor(clsName, termName) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala index 3e56c933f4..75c5ca8dd7 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala @@ -6,6 +6,7 @@ import cats.implicits._ import cats.~> import cats.data.NonEmptyList import com.twilio.guardrail.extract.{ Default, ScalaEmptyIsNull, ScalaType } +import com.twilio.guardrail.generators.syntax.RichString import com.twilio.guardrail.shims._ import com.twilio.guardrail.terms import java.util.Locale @@ -109,13 +110,10 @@ object CirceProtocolGenerator { }).map(_.map(_.asScala.toList).toList.flatten) case TransformProperty(clsName, name, property, meta, needCamelSnakeConversion, concreteTypes, isRequired) => - def toCamelCase(s: String): String = - "[_\\.]([a-z])".r.replaceAllIn(s, m => m.group(1).toUpperCase(Locale.US)) - for { _ <- Target.log.debug("definitions", "circe", "modelProtocolTerm")(s"Generated ProtocolParameter(${term}, ${name}, ...)") - argName = if (needCamelSnakeConversion) toCamelCase(name) else name + argName = if (needCamelSnakeConversion) name.toCamelCase else name defaultValue = property match { case _: MapSchema => diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaParameter.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaParameter.scala index 5a019f45d2..6c85d51793 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaParameter.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaParameter.scala @@ -4,6 +4,7 @@ package generators import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.parameters._ import com.twilio.guardrail.extract.{ Default, ScalaFileHashAlgorithm, ScalaType } +import com.twilio.guardrail.generators.syntax.RichString import com.twilio.guardrail.languages.LA import com.twilio.guardrail.languages.ScalaLanguage import com.twilio.guardrail.shims._ @@ -54,13 +55,6 @@ object ScalaParameter { import Sc._ import Sw._ - def toCamelCase(s: String): String = { - val fromSnakeOrDashed = - "[_-]([a-z])".r.replaceAllIn(s, m => m.group(1).toUpperCase(Locale.US)) - "^([A-Z])".r - .replaceAllIn(fromSnakeOrDashed, m => m.group(1).toLowerCase(Locale.US)) - } - def paramMeta(param: Parameter): Free[F, SwaggerUtil.ResolvedType[L]] = { def getDefault[U <: Parameter: Default.GetDefault](p: U): Free[F, Option[L#Term]] = Option(p.getSchema.getType) @@ -169,7 +163,7 @@ object ScalaParameter { name <- getParameterName(parameter) - paramName <- pureTermName(toCamelCase(name)) + paramName <- pureTermName(name.toCamelCase) param <- pureMethodParameter(paramName, declType, defaultValue) ftpe <- fileType(None) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/package.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/package.scala new file mode 100644 index 0000000000..a3b87973cb --- /dev/null +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/package.scala @@ -0,0 +1,25 @@ +package com.twilio.guardrail.generators + +import java.util.Locale + +package object syntax { + private val toPascalRegexes = List( + "[\\._-]([a-z])".r, // dotted, snake, or dashed case + "\\s+([a-zA-Z])".r, // spaces + "^([a-z])".r // initial letter + ) + + implicit class RichString(val s: String) extends AnyVal { + def toPascalCase: String = + toPascalRegexes.foldLeft(s)( + (accum, regex) => regex.replaceAllIn(accum, m => m.group(1).toUpperCase(Locale.US)) + ) + + def toCamelCase: String = { + val fromSnakeOrDashed = + "[_-]([a-z])".r.replaceAllIn(s, m => m.group(1).toUpperCase(Locale.US)) + "^([A-Z])".r + .replaceAllIn(fromSnakeOrDashed, m => m.group(1).toLowerCase(Locale.US)) + } + } +} diff --git a/modules/codegen/src/test/scala/core/issues/Issue145.scala b/modules/codegen/src/test/scala/core/issues/Issue145.scala index 84f7411dbc..cc121b526a 100644 --- a/modules/codegen/src/test/scala/core/issues/Issue145.scala +++ b/modules/codegen/src/test/scala/core/issues/Issue145.scala @@ -52,15 +52,15 @@ class Issue145 extends FunSpec with Matchers with SwaggerSpecRunner { object Pet { implicit val encodePet = { val readOnlyKeys = Set[String]() - Encoder.forProduct3("name", "underscore_name", "dash-name")((o: Pet) => (o.name, o.underscoreName, o.`dash-name`)).mapJsonObject(_.filterKeys(key => !(readOnlyKeys contains key))) + Encoder.forProduct3("name", "underscore_name", "dash-name")((o: Pet) => (o.name, o.underscoreName, o.dashName)).mapJsonObject(_.filterKeys(key => !(readOnlyKeys contains key))) } implicit val decodePet = new Decoder[Pet] { final def apply(c: HCursor): Decoder.Result[Pet] = for ( name <- c.downField("name").withFocus(j => j.asString.fold(j)(s => if (s.isEmpty) Json.Null else j)).as[Option[CustomThing]]; underscoreName <- c.downField("underscore_name").withFocus(j => j.asString.fold(j)(s => if (s.isEmpty) Json.Null else j)).as[Option[CustomThing]]; - `dash-name` <- c.downField("dash-name").withFocus(j => j.asString.fold(j)(s => if (s.isEmpty) Json.Null else j)).as[Option[CustomThing]] - ) yield Pet(name, underscoreName, `dash-name`) + dashName <- c.downField("dash-name").withFocus(j => j.asString.fold(j)(s => if (s.isEmpty) Json.Null else j)).as[Option[CustomThing]] + ) yield Pet(name, underscoreName, dashName) } }""".toString() } diff --git a/modules/codegen/src/test/scala/tests/core/BacktickTest.scala b/modules/codegen/src/test/scala/tests/core/BacktickTest.scala index 7b97b32ee5..e6c666ce91 100644 --- a/modules/codegen/src/test/scala/tests/core/BacktickTest.scala +++ b/modules/codegen/src/test/scala/tests/core/BacktickTest.scala @@ -146,13 +146,13 @@ class BacktickTest extends FunSuite with Matchers with SwaggerSpecRunner { val cmp = companionForStaticDefns(staticDefns) val definition = q""" - case class `dashy-class`(`dashy-param`: Option[Long] = None) + case class `dashy-class`(dashyParam: Option[Long] = None) """ val companion = q""" object `dashy-class` { implicit val `encodedashy-class` = { val readOnlyKeys = Set[String]() - Encoder.forProduct1("dashy-param")((o: `dashy-class`) => o.`dashy-param`).mapJsonObject(_.filterKeys(key => !(readOnlyKeys contains key))) + Encoder.forProduct1("dashy-param")((o: `dashy-class`) => o.dashyParam).mapJsonObject(_.filterKeys(key => !(readOnlyKeys contains key))) } implicit val `decodedashy-class` = Decoder.forProduct1("dashy-param")(`dashy-class`.apply _) } @@ -160,7 +160,7 @@ class BacktickTest extends FunSuite with Matchers with SwaggerSpecRunner { cls.structure should equal(definition.structure) cls.toString should include("case class `dashy-class`") - cls.toString should include("`dashy-param`") + cls.toString should include("dashyParam") cls.toString shouldNot include("``") cmp.structure should equal(companion.structure) From 8365fadace4ba17f99a89669c020bbe5abd5c061 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Wed, 27 Feb 2019 04:33:04 -0800 Subject: [PATCH 15/86] Make DTO EncodeModel and DecodeModel optional --- .../guardrail/generators/CirceProtocolGenerator.scala | 10 +++++----- .../protocol/terms/protocol/ModelProtocolTerm.scala | 6 +++--- .../protocol/terms/protocol/ModelProtocolTerms.scala | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala index 75c5ca8dd7..9b3187c287 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala @@ -230,12 +230,12 @@ object CirceProtocolGenerator { } """ } - Target.pure(q""" + Target.pure(Some(q""" implicit val ${suffixClsName("encode", clsName)} = { val readOnlyKeys = Set[String](..${readOnlyKeys.map(Lit.String(_))}) $encVal.mapJsonObject(_.filterKeys(key => !(readOnlyKeys contains key))) } - """) + """)) case DecodeModel(clsName, needCamelSnakeConversion, selfParams, parents) => val discriminators = parents.flatMap(_.discriminators) @@ -285,9 +285,9 @@ object CirceProtocolGenerator { } """ } - Target.pure(q""" + Target.pure(Some(q""" implicit val ${suffixClsName("decode", clsName)} = $decVal - """) + """)) case RenderDTOStaticDefns(clsName, deps, encoder, decoder) => val extraImports: List[Import] = deps.map { term => @@ -297,7 +297,7 @@ object CirceProtocolGenerator { StaticDefns[ScalaLanguage]( className = clsName, extraImports = extraImports, - definitions = List(encoder, decoder) + definitions = List(encoder, decoder).flatten ) ) } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerm.scala index 689a240a6f..ee898981c4 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerm.scala @@ -18,8 +18,8 @@ case class TransformProperty[L <: LA](clsName: String, case class RenderDTOClass[L <: LA](clsName: String, params: List[ProtocolParameter[L]], parents: List[SuperClass[L]] = Nil) extends ModelProtocolTerm[L, L#ClassDefinition] case class EncodeModel[L <: LA](clsName: String, needCamelSnakeConversion: Boolean, params: List[ProtocolParameter[L]], parents: List[SuperClass[L]] = Nil) - extends ModelProtocolTerm[L, L#ValueDefinition] + extends ModelProtocolTerm[L, Option[L#ValueDefinition]] case class DecodeModel[L <: LA](clsName: String, needCamelSnakeConversion: Boolean, params: List[ProtocolParameter[L]], parents: List[SuperClass[L]] = Nil) - extends ModelProtocolTerm[L, L#ValueDefinition] -case class RenderDTOStaticDefns[L <: LA](clsName: String, deps: List[L#TermName], encoder: L#ValueDefinition, decoder: L#ValueDefinition) + extends ModelProtocolTerm[L, Option[L#ValueDefinition]] +case class RenderDTOStaticDefns[L <: LA](clsName: String, deps: List[L#TermName], encoder: Option[L#ValueDefinition], decoder: Option[L#ValueDefinition]) extends ModelProtocolTerm[L, StaticDefns[L]] diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerms.scala index d658c4d068..7a23c4deb4 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerms.scala @@ -22,14 +22,14 @@ class ModelProtocolTerms[L <: LA, F[_]](implicit I: InjectK[ModelProtocolTerm[L, def encodeModel(clsName: String, needCamelSnakeConversion: Boolean, params: List[ProtocolParameter[L]], - parents: List[SuperClass[L]] = Nil): Free[F, L#ValueDefinition] = + parents: List[SuperClass[L]] = Nil): Free[F, Option[L#ValueDefinition]] = Free.inject[ModelProtocolTerm[L, ?], F](EncodeModel[L](clsName, needCamelSnakeConversion, params, parents)) def decodeModel(clsName: String, needCamelSnakeConversion: Boolean, params: List[ProtocolParameter[L]], - parents: List[SuperClass[L]] = Nil): Free[F, L#ValueDefinition] = + parents: List[SuperClass[L]] = Nil): Free[F, Option[L#ValueDefinition]] = Free.inject[ModelProtocolTerm[L, ?], F](DecodeModel[L](clsName, needCamelSnakeConversion, params, parents)) - def renderDTOStaticDefns(clsName: String, deps: List[L#TermName], encoder: L#ValueDefinition, decoder: L#ValueDefinition): Free[F, StaticDefns[L]] = + def renderDTOStaticDefns(clsName: String, deps: List[L#TermName], encoder: Option[L#ValueDefinition], decoder: Option[L#ValueDefinition]): Free[F, StaticDefns[L]] = Free.inject[ModelProtocolTerm[L, ?], F](RenderDTOStaticDefns[L](clsName, deps, encoder, decoder)) } object ModelProtocolTerms { From 91538a4ea6077db5438f09e281ad7c54e28a9b67 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Wed, 27 Feb 2019 04:45:12 -0800 Subject: [PATCH 16/86] Make enum RenderMembers, EncodeEnum, and DecodeEnum optional --- .../com/twilio/guardrail/ProtocolGenerator.scala | 7 +++---- .../generators/CirceProtocolGenerator.scala | 16 ++++++++-------- .../terms/protocol/EnumProtocolTerm.scala | 12 ++++++------ .../terms/protocol/EnumProtocolTerms.scala | 12 ++++++------ 4 files changed, 23 insertions(+), 24 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala index 62656ca5e4..0f97cc3ada 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala @@ -1,19 +1,18 @@ package com.twilio.guardrail import _root_.io.swagger.v3.oas.models._ -import _root_.io.swagger.v3.oas.models.media.{ ArraySchema, ComposedSchema, IntegerSchema, ObjectSchema, Schema, StringSchema } +import _root_.io.swagger.v3.oas.models.media._ import cats.free.Free import cats.implicits._ import com.twilio.guardrail.extract.ScalaType import com.twilio.guardrail.generators.syntax.RichString import com.twilio.guardrail.languages.LA import com.twilio.guardrail.protocol.terms.protocol._ -import com.twilio.guardrail.shims._ import com.twilio.guardrail.terms.framework.FrameworkTerms -import com.twilio.guardrail.terms.{ ScalaTerms, SwaggerTerms } +import com.twilio.guardrail.terms.{ScalaTerms, SwaggerTerms} import java.util.Locale import scala.collection.JavaConverters._ -import scala.language.{ higherKinds, postfixOps, reflectiveCalls } +import scala.language.{higherKinds, postfixOps, reflectiveCalls} case class ProtocolDefinitions[L <: LA](elems: List[StrictProtocolElems[L]], protocolImports: List[L#Import], diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala index 9b3187c287..0d1aca00ea 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala @@ -38,7 +38,7 @@ object CirceProtocolGenerator { Target.pure(Either.fromOption(enumEntries, "Model has no enumerations")) case RenderMembers(clsName, elems) => - Target.pure(q""" + Target.pure(Some(q""" object members { ..${elems .map({ @@ -47,21 +47,21 @@ object CirceProtocolGenerator { }) .to[List]} } - """) + """)) case EncodeEnum(clsName) => - Target.pure(q""" + Target.pure(Some(q""" implicit val ${suffixClsName("encode", clsName)}: Encoder[${Type.Name(clsName)}] = Encoder[String].contramap(_.value) - """) + """)) case DecodeEnum(clsName) => - Target.pure(q""" + Target.pure(Some(q""" implicit val ${suffixClsName("decode", clsName)}: Decoder[${Type.Name(clsName)}] = Decoder[String].emap(value => parse(value).toRight(${Term.Interpolate(Term.Name("s"), List(Lit.String(""), Lit.String(s" not a member of ${clsName}")), List(Term.Name("value")))})) - """) + """)) case RenderClass(clsName, tpe) => Target.pure(q""" @@ -85,9 +85,9 @@ object CirceProtocolGenerator { StaticDefns[ScalaLanguage]( className = clsName, extraImports = List.empty[Import], - definitions = List(members) ++ + definitions = members.to[List] ++ terms ++ - List(values, encoder, decoder) ++ + List(Some(values), encoder, decoder).flatten ++ implicits ++ List(q"def parse(value: String): Option[${Type.Name(clsName)}] = values.find(_.value == value)") ) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerm.scala index fc7202fb77..0fbaa2223a 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerm.scala @@ -6,14 +6,14 @@ import com.twilio.guardrail.languages.LA sealed trait EnumProtocolTerm[L <: LA, T] case class ExtractEnum[L <: LA](swagger: Schema[_]) extends EnumProtocolTerm[L, Either[String, List[String]]] -case class RenderMembers[L <: LA](clsName: String, elems: List[(String, L#TermName, L#TermSelect)]) extends EnumProtocolTerm[L, L#ObjectDefinition] -case class EncodeEnum[L <: LA](clsName: String) extends EnumProtocolTerm[L, L#ValueDefinition] -case class DecodeEnum[L <: LA](clsName: String) extends EnumProtocolTerm[L, L#ValueDefinition] +case class RenderMembers[L <: LA](clsName: String, elems: List[(String, L#TermName, L#TermSelect)]) extends EnumProtocolTerm[L, Option[L#ObjectDefinition]] +case class EncodeEnum[L <: LA](clsName: String) extends EnumProtocolTerm[L, Option[L#ValueDefinition]] +case class DecodeEnum[L <: LA](clsName: String) extends EnumProtocolTerm[L, Option[L#ValueDefinition]] case class RenderClass[L <: LA](clsName: String, tpe: L#Type) extends EnumProtocolTerm[L, L#ClassDefinition] case class RenderStaticDefns[L <: LA](clsName: String, - members: L#ObjectDefinition, + members: Option[L#ObjectDefinition], accessors: List[L#TermName], - encoder: L#ValueDefinition, - decoder: L#ValueDefinition) + encoder: Option[L#ValueDefinition], + decoder: Option[L#ValueDefinition]) extends EnumProtocolTerm[L, StaticDefns[L]] case class BuildAccessor[L <: LA](clsName: String, termName: String) extends EnumProtocolTerm[L, L#TermSelect] diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerms.scala index bcbabadc45..02bb325259 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerms.scala @@ -9,19 +9,19 @@ import com.twilio.guardrail.languages.LA class EnumProtocolTerms[L <: LA, F[_]](implicit I: InjectK[EnumProtocolTerm[L, ?], F]) { def extractEnum(swagger: Schema[_]): Free[F, Either[String, List[String]]] = Free.inject[EnumProtocolTerm[L, ?], F](ExtractEnum[L](swagger)) - def renderMembers(clsName: String, elems: List[(String, L#TermName, L#TermSelect)]): Free[F, L#ObjectDefinition] = + def renderMembers(clsName: String, elems: List[(String, L#TermName, L#TermSelect)]): Free[F, Option[L#ObjectDefinition]] = Free.inject[EnumProtocolTerm[L, ?], F](RenderMembers[L](clsName, elems)) - def encodeEnum(clsName: String): Free[F, L#ValueDefinition] = + def encodeEnum(clsName: String): Free[F, Option[L#ValueDefinition]] = Free.inject[EnumProtocolTerm[L, ?], F](EncodeEnum[L](clsName)) - def decodeEnum(clsName: String): Free[F, L#ValueDefinition] = + def decodeEnum(clsName: String): Free[F, Option[L#ValueDefinition]] = Free.inject[EnumProtocolTerm[L, ?], F](DecodeEnum[L](clsName)) def renderClass(clsName: String, tpe: L#Type): Free[F, L#ClassDefinition] = Free.inject[EnumProtocolTerm[L, ?], F](RenderClass[L](clsName, tpe)) def renderStaticDefns(clsName: String, - members: L#ObjectDefinition, + members: Option[L#ObjectDefinition], accessors: List[L#TermName], - encoder: L#ValueDefinition, - decoder: L#ValueDefinition): Free[F, StaticDefns[L]] = + encoder: Option[L#ValueDefinition], + decoder: Option[L#ValueDefinition]): Free[F, StaticDefns[L]] = Free.inject[EnumProtocolTerm[L, ?], F](RenderStaticDefns[L](clsName, members, accessors, encoder, decoder)) def buildAccessor(clsName: String, termName: String): Free[F, L#TermSelect] = Free.inject[EnumProtocolTerm[L, ?], F](BuildAccessor[L](clsName, termName)) From 45b25a21d4ed0e50a0db878107976e7ef28b9261 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Wed, 27 Feb 2019 04:54:32 -0800 Subject: [PATCH 17/86] Pass elems to enum RenderClass --- .../main/scala/com/twilio/guardrail/ProtocolGenerator.scala | 2 +- .../twilio/guardrail/generators/CirceProtocolGenerator.scala | 2 +- .../guardrail/protocol/terms/protocol/EnumProtocolTerm.scala | 5 ++++- .../protocol/terms/protocol/EnumProtocolTerms.scala | 4 ++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala index 0f97cc3ada..3c0fcbde9b 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala @@ -79,7 +79,7 @@ object ProtocolGenerator { encoder <- encodeEnum(clsName) decoder <- decodeEnum(clsName) - defn <- renderClass(clsName, tpe) + defn <- renderClass(clsName, tpe, elems) staticDefns <- renderStaticDefns(clsName, members, pascalValues, encoder, decoder) classType <- pureTypeName(clsName) } yield EnumDefinition[L](clsName, classType, elems, defn, staticDefns) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala index 0d1aca00ea..4d79f90800 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala @@ -63,7 +63,7 @@ object CirceProtocolGenerator { List(Term.Name("value")))})) """)) - case RenderClass(clsName, tpe) => + case RenderClass(clsName, tpe, _) => Target.pure(q""" sealed abstract class ${Type.Name(clsName)}(val value: ${tpe}) { override def toString: String = value.toString diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerm.scala index 0fbaa2223a..688107579c 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerm.scala @@ -9,7 +9,10 @@ case class ExtractEnum[L <: LA](swagger: Schema[_]) case class RenderMembers[L <: LA](clsName: String, elems: List[(String, L#TermName, L#TermSelect)]) extends EnumProtocolTerm[L, Option[L#ObjectDefinition]] case class EncodeEnum[L <: LA](clsName: String) extends EnumProtocolTerm[L, Option[L#ValueDefinition]] case class DecodeEnum[L <: LA](clsName: String) extends EnumProtocolTerm[L, Option[L#ValueDefinition]] -case class RenderClass[L <: LA](clsName: String, tpe: L#Type) extends EnumProtocolTerm[L, L#ClassDefinition] +case class RenderClass[L <: LA](clsName: String, + tpe: L#Type, + elems: List[(String, L#TermName, L#TermSelect)]) + extends EnumProtocolTerm[L, L#ClassDefinition] case class RenderStaticDefns[L <: LA](clsName: String, members: Option[L#ObjectDefinition], accessors: List[L#TermName], diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerms.scala index 02bb325259..67cc54202b 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerms.scala @@ -15,8 +15,8 @@ class EnumProtocolTerms[L <: LA, F[_]](implicit I: InjectK[EnumProtocolTerm[L, ? Free.inject[EnumProtocolTerm[L, ?], F](EncodeEnum[L](clsName)) def decodeEnum(clsName: String): Free[F, Option[L#ValueDefinition]] = Free.inject[EnumProtocolTerm[L, ?], F](DecodeEnum[L](clsName)) - def renderClass(clsName: String, tpe: L#Type): Free[F, L#ClassDefinition] = - Free.inject[EnumProtocolTerm[L, ?], F](RenderClass[L](clsName, tpe)) + def renderClass(clsName: String, tpe: L#Type, elems: List[(String, L#TermName, L#TermSelect)]): Free[F, L#ClassDefinition] = + Free.inject[EnumProtocolTerm[L, ?], F](RenderClass[L](clsName, tpe, elems)) def renderStaticDefns(clsName: String, members: Option[L#ObjectDefinition], accessors: List[L#TermName], From 9feaf6595f9e90fb84f7423dfd53fc5afb340d4c Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Thu, 28 Feb 2019 17:26:43 -0800 Subject: [PATCH 18/86] Make PolyProtocolTerm EncodeADT and DecodeADT optional --- .../generators/CirceProtocolGenerator.scala | 10 +++++----- .../protocol/terms/protocol/PolyProtocolTerm.scala | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala index 4d79f90800..60ed23e905 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala @@ -388,11 +388,11 @@ object CirceProtocolGenerator { StaticDefns[ScalaLanguage]( className = clsName, extraImports = List.empty[Import], - definitions = List[Defn]( - q"val discriminator: String = ${Lit.String(discriminator)}", + definitions = List[Option[Defn]]( + Some(q"val discriminator: String = ${Lit.String(discriminator)}"), encoder, decoder - ) + ).flatten ) ) @@ -409,7 +409,7 @@ object CirceProtocolGenerator { Left(DecodingFailure("Unknown value " ++ tpe ++ ${Lit.String(s" (valid: ${children.mkString(", ")})")}, discriminatorCursor.history)) } })""" - Target.pure(code) + Target.pure(Some(code)) case EncodeADT(clsName, children) => val childrenCases = children.map( @@ -419,7 +419,7 @@ object CirceProtocolGenerator { q"""implicit val encoder: Encoder[${Type.Name(clsName)}] = Encoder.instance { ..case $childrenCases }""" - Target.pure(code) + Target.pure(Some(code)) case RenderSealedTrait(className, terms, discriminator, parents) => for { diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/PolyProtocolTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/PolyProtocolTerm.scala index 5298e13dc7..946f3bcbac 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/PolyProtocolTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/PolyProtocolTerm.scala @@ -17,11 +17,11 @@ case class ExtractSuperClass[L <: LA](swagger: ComposedSchema, definitions: List case class RenderSealedTrait[L <: LA](className: String, terms: List[L#MethodParameter], discriminator: String, parents: List[SuperClass[L]] = Nil) extends PolyProtocolTerm[L, L#Trait] -case class EncodeADT[L <: LA](clsName: String, children: List[String] = Nil) extends PolyProtocolTerm[L, L#ValueDefinition] +case class EncodeADT[L <: LA](clsName: String, children: List[String] = Nil) extends PolyProtocolTerm[L, Option[L#ValueDefinition]] -case class DecodeADT[L <: LA](clsName: String, children: List[String] = Nil) extends PolyProtocolTerm[L, L#ValueDefinition] +case class DecodeADT[L <: LA](clsName: String, children: List[String] = Nil) extends PolyProtocolTerm[L, Option[L#ValueDefinition]] -case class RenderADTStaticDefns[L <: LA](clsName: String, discriminator: String, encoder: L#ValueDefinition, decoder: L#ValueDefinition) +case class RenderADTStaticDefns[L <: LA](clsName: String, discriminator: String, encoder: Option[L#ValueDefinition], decoder: Option[L#ValueDefinition]) extends PolyProtocolTerm[L, StaticDefns[L]] class PolyProtocolTerms[L <: LA, F[_]](implicit I: InjectK[PolyProtocolTerm[L, ?], F]) { @@ -35,17 +35,17 @@ class PolyProtocolTerms[L <: LA, F[_]](implicit I: InjectK[PolyProtocolTerm[L, ? ): Free[F, L#Trait] = Free.inject[PolyProtocolTerm[L, ?], F](RenderSealedTrait(className, terms, discriminator, parents)) - def encodeADT(clsName: String, children: List[String] = Nil): Free[F, L#ValueDefinition] = + def encodeADT(clsName: String, children: List[String] = Nil): Free[F, Option[L#ValueDefinition]] = Free.inject[PolyProtocolTerm[L, ?], F](EncodeADT(clsName, children)) - def decodeADT(clsName: String, children: List[String] = Nil): Free[F, L#ValueDefinition] = + def decodeADT(clsName: String, children: List[String] = Nil): Free[F, Option[L#ValueDefinition]] = Free.inject[PolyProtocolTerm[L, ?], F](DecodeADT(clsName, children)) def renderADTStaticDefns( clsName: String, discriminator: String, - encoder: L#ValueDefinition, - decoder: L#ValueDefinition + encoder: Option[L#ValueDefinition], + decoder: Option[L#ValueDefinition] ): Free[F, StaticDefns[L]] = Free.inject[PolyProtocolTerm[L, ?], F](RenderADTStaticDefns(clsName, discriminator, encoder, decoder)) From 0137818688309ce887cf4b22941f94511eec1aa8 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Fri, 1 Mar 2019 04:11:55 -0800 Subject: [PATCH 19/86] Pass full ProtocolParameter list to RenderSealedTrait for poly proto --- .../scala/com/twilio/guardrail/ProtocolGenerator.scala | 3 +-- .../guardrail/generators/CirceProtocolGenerator.scala | 5 +++-- .../protocol/terms/protocol/PolyProtocolTerm.scala | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala index 3c0fcbde9b..093417573b 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala @@ -154,8 +154,7 @@ object ProtocolGenerator { .propMeta[L, F](prop) .flatMap(transformProperty(hierarchy.name, needCamelSnakeConversion, concreteTypes)(name, prop, _, isRequired)) }) - terms = params.map(_.term) - definition <- renderSealedTrait(hierarchy.name, terms, discriminator, parents) + definition <- renderSealedTrait(hierarchy.name, params, discriminator, parents) encoder <- encodeADT(hierarchy.name, children) decoder <- decodeADT(hierarchy.name, children) staticDefns <- renderADTStaticDefns(hierarchy.name, discriminator, encoder, decoder) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala index 60ed23e905..0234b7e934 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala @@ -421,10 +421,11 @@ object CirceProtocolGenerator { }""" Target.pure(Some(code)) - case RenderSealedTrait(className, terms, discriminator, parents) => + case RenderSealedTrait(className, params, discriminator, parents) => for { testTerms <- ( - terms + params + .map(_.term) .filter(_.name.value != discriminator) .traverse { t => for { diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/PolyProtocolTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/PolyProtocolTerm.scala index 946f3bcbac..8551a805c4 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/PolyProtocolTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/PolyProtocolTerm.scala @@ -2,9 +2,9 @@ package com.twilio.guardrail.protocol.terms.protocol import cats.InjectK import cats.free.Free -import com.twilio.guardrail.{ StaticDefns, SuperClass } +import com.twilio.guardrail.{ProtocolParameter, StaticDefns, SuperClass} import com.twilio.guardrail.languages.LA -import io.swagger.v3.oas.models.media.{ ComposedSchema, Schema } +import io.swagger.v3.oas.models.media.{ComposedSchema, Schema} /** * Protocol for Polymorphic models @@ -14,7 +14,7 @@ sealed trait PolyProtocolTerm[L <: LA, T] case class ExtractSuperClass[L <: LA](swagger: ComposedSchema, definitions: List[(String, Schema[_])]) extends PolyProtocolTerm[L, List[(String, Schema[_], List[Schema[_]])]] -case class RenderSealedTrait[L <: LA](className: String, terms: List[L#MethodParameter], discriminator: String, parents: List[SuperClass[L]] = Nil) +case class RenderSealedTrait[L <: LA](className: String, params: List[ProtocolParameter[L]], discriminator: String, parents: List[SuperClass[L]] = Nil) extends PolyProtocolTerm[L, L#Trait] case class EncodeADT[L <: LA](clsName: String, children: List[String] = Nil) extends PolyProtocolTerm[L, Option[L#ValueDefinition]] @@ -29,11 +29,11 @@ class PolyProtocolTerms[L <: LA, F[_]](implicit I: InjectK[PolyProtocolTerm[L, ? Free.inject[PolyProtocolTerm[L, ?], F](ExtractSuperClass(swagger, definitions)) def renderSealedTrait( className: String, - terms: List[L#MethodParameter], + params: List[ProtocolParameter[L]], discriminator: String, parents: List[SuperClass[L]] = Nil ): Free[F, L#Trait] = - Free.inject[PolyProtocolTerm[L, ?], F](RenderSealedTrait(className, terms, discriminator, parents)) + Free.inject[PolyProtocolTerm[L, ?], F](RenderSealedTrait(className, params, discriminator, parents)) def encodeADT(clsName: String, children: List[String] = Nil): Free[F, Option[L#ValueDefinition]] = Free.inject[PolyProtocolTerm[L, ?], F](EncodeADT(clsName, children)) From 696bb7531809556dc0163fc29d078badd9be3532 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Fri, 1 Mar 2019 05:13:34 -0800 Subject: [PATCH 20/86] Provide list of children to poly RenderSealedTrait --- .../scala/com/twilio/guardrail/ProtocolGenerator.scala | 2 +- .../guardrail/generators/CirceProtocolGenerator.scala | 2 +- .../protocol/terms/protocol/PolyProtocolTerm.scala | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala index 093417573b..a2d90f3858 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala @@ -154,7 +154,7 @@ object ProtocolGenerator { .propMeta[L, F](prop) .flatMap(transformProperty(hierarchy.name, needCamelSnakeConversion, concreteTypes)(name, prop, _, isRequired)) }) - definition <- renderSealedTrait(hierarchy.name, params, discriminator, parents) + definition <- renderSealedTrait(hierarchy.name, params, discriminator, parents, children) encoder <- encodeADT(hierarchy.name, children) decoder <- decodeADT(hierarchy.name, children) staticDefns <- renderADTStaticDefns(hierarchy.name, discriminator, encoder, decoder) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala index 0234b7e934..6299a3b5d7 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala @@ -421,7 +421,7 @@ object CirceProtocolGenerator { }""" Target.pure(Some(code)) - case RenderSealedTrait(className, params, discriminator, parents) => + case RenderSealedTrait(className, params, discriminator, parents, _) => for { testTerms <- ( params diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/PolyProtocolTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/PolyProtocolTerm.scala index 8551a805c4..dbaadf71f9 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/PolyProtocolTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/PolyProtocolTerm.scala @@ -14,7 +14,7 @@ sealed trait PolyProtocolTerm[L <: LA, T] case class ExtractSuperClass[L <: LA](swagger: ComposedSchema, definitions: List[(String, Schema[_])]) extends PolyProtocolTerm[L, List[(String, Schema[_], List[Schema[_]])]] -case class RenderSealedTrait[L <: LA](className: String, params: List[ProtocolParameter[L]], discriminator: String, parents: List[SuperClass[L]] = Nil) +case class RenderSealedTrait[L <: LA](className: String, params: List[ProtocolParameter[L]], discriminator: String, parents: List[SuperClass[L]] = Nil, children: List[String] = Nil) extends PolyProtocolTerm[L, L#Trait] case class EncodeADT[L <: LA](clsName: String, children: List[String] = Nil) extends PolyProtocolTerm[L, Option[L#ValueDefinition]] @@ -31,9 +31,10 @@ class PolyProtocolTerms[L <: LA, F[_]](implicit I: InjectK[PolyProtocolTerm[L, ? className: String, params: List[ProtocolParameter[L]], discriminator: String, - parents: List[SuperClass[L]] = Nil + parents: List[SuperClass[L]] = Nil, + children: List[String] = Nil ): Free[F, L#Trait] = - Free.inject[PolyProtocolTerm[L, ?], F](RenderSealedTrait(className, params, discriminator, parents)) + Free.inject[PolyProtocolTerm[L, ?], F](RenderSealedTrait(className, params, discriminator, parents, children)) def encodeADT(clsName: String, children: List[String] = Nil): Free[F, Option[L#ValueDefinition]] = Free.inject[PolyProtocolTerm[L, ?], F](EncodeADT(clsName, children)) From 31818ebe2b76677582b1741ef91acc77b54b0f95 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Fri, 1 Mar 2019 07:20:46 -0800 Subject: [PATCH 21/86] Finish Jackson generator implementation It's almost certainly buggy, but need to implement more things before it's testable. --- .../guardrail/generators/Java/AkkaHttp.scala | 2 +- .../generators/Java/JacksonGenerator.scala | 588 ++++++++++++++---- .../guardrail/generators/syntax/Java.scala | 12 +- .../guardrail/generators/syntax/package.scala | 6 + .../guardrail/languages/JavaLanguage.scala | 8 +- 5 files changed, 493 insertions(+), 123 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala index 49770efc2d..27f859bac1 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala @@ -8,7 +8,7 @@ import com.twilio.guardrail.protocol.terms.server.ServerTerm import com.twilio.guardrail.terms.framework.FrameworkTerm import cats.~> -import JacksonProtocolGenerator._ +import JacksonGenerator._ import JavaGenerator.JavaInterp object AkkaHttp extends (CodegenApplication[JavaLanguage, ?] ~> Target) { diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala index b637452ea8..c31c2dbb26 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -3,33 +3,86 @@ package generators package Java import _root_.io.swagger.v3.oas.models.media._ -import cats.data.NonEmptyList import cats.implicits._ import cats.~> -import com.github.javaparser.ast.`type`.{ ClassOrInterfaceType, Type } -import com.twilio.guardrail.extract.{ Default, ScalaEmptyIsNull, ScalaType } +import com.github.javaparser.ast.`type`.{ClassOrInterfaceType, PrimitiveType, Type} +import com.twilio.guardrail.extract.{Default, ScalaEmptyIsNull} import com.twilio.guardrail.generators.syntax.Java._ -import com.twilio.guardrail.languages.{ LA, JavaLanguage } +import com.twilio.guardrail.generators.syntax.RichString +import com.twilio.guardrail.languages.JavaLanguage import com.twilio.guardrail.protocol.terms.protocol._ -import com.twilio.guardrail.shims._ -import com.twilio.guardrail.terms import java.util.Locale import scala.collection.JavaConverters._ import com.github.javaparser.JavaParser -import com.github.javaparser.ast.{ CompilationUnit, Node, NodeList } -import com.github.javaparser.ast.stmt.{ BlockStmt, ExpressionStmt } -import com.github.javaparser.ast.Modifier.{ PRIVATE, PUBLIC, STATIC } -import com.github.javaparser.ast.expr.{ AssignExpr, BooleanLiteralExpr, DoubleLiteralExpr, Expression, FieldAccessExpr, IntegerLiteralExpr, LiteralExpr, LongLiteralExpr, NameExpr, SimpleName, StringLiteralExpr, ThisExpr } +import com.github.javaparser.ast.{ImportDeclaration,NodeList} +import com.github.javaparser.ast.stmt._ +import com.github.javaparser.ast.Modifier.{ABSTRACT, FINAL, PRIVATE, PUBLIC, STATIC} +import com.github.javaparser.ast.body._ +import com.github.javaparser.ast.expr._ +import java.util +import scala.language.existentials +import scala.util.Try -object JacksonProtocolGenerator { +object JacksonGenerator { import ProtocolGenerator._ - def lookupTypeName(tpeName: String, concreteTypes: List[PropMeta[JavaLanguage]])(f: Type => Target[Type]): Option[Target[Type]] = + private case class ParameterTerm(propertyName: String, + parameterName: String, + fieldType: Type, + parameterType: Type) + + // returns a tuple of (requiredTerms, optionalTerms) + private def sortParams(params: List[ProtocolParameter[JavaLanguage]]): (List[ParameterTerm], List[ParameterTerm]) = { + // TODO: if a required field has a default specified, include it in optionalTerms instead + val (req, opt) = params.partition(_.term.getType match { + case cls: ClassOrInterfaceType => !isOptionalType(cls) + case _ => true + }) + + val requiredTerms = req.map({ param => + val types = param.term.getType match { + case cls: ClassOrInterfaceType => Try(cls.toUnboxedType).getOrElse(cls) + case tpe => tpe + } + ParameterTerm(param.name, param.term.getNameAsString, types, types) + }) + + val optionalTerms = opt.flatMap(param => param.term.getType match { + case cls: ClassOrInterfaceType => cls.getTypeArguments.asScala.flatMap({ typeArgument => + val parameterType = typeArgument.asScala.headOption.map({ + case innerCls: ClassOrInterfaceType => Try(innerCls.toUnboxedType).getOrElse(innerCls) + case tpe => tpe + }) + parameterType.map(pt => ParameterTerm(param.name, param.term.getNameAsString, param.term.getType, pt)) + }) + case _ => None + }) + + (requiredTerms, optionalTerms) + } + + private def addParents(cls: ClassOrInterfaceDeclaration, parentOpt: Option[SuperClass[JavaLanguage]]): Unit = { + parentOpt.foreach({ parent => + val directParent = JavaParser.parseClassOrInterfaceType(parent.clsName) + val otherParents = parent.interfaces.map(JavaParser.parseClassOrInterfaceType) + cls.setExtendedTypes( + new NodeList((directParent +: otherParents): _*) + ) + }) + } + + private def isOptionalType(cls: ClassOrInterfaceType): Boolean = + (cls.getScope.asScala.fold("")(_.asString + ".") + cls.getName.asString) == "java.util.Optional" + + private def lookupTypeName(tpeName: String, concreteTypes: List[PropMeta[JavaLanguage]])(f: Type => Target[Type]): Option[Target[Type]] = concreteTypes .find(_.clsName == tpeName) .map(_.tpe) .map(f) + private val STRING_TYPE = JavaParser.parseClassOrInterfaceType("String") + private val HASH_MAP_TYPE = JavaParser.parseClassOrInterfaceType("java.util.HashMap") + private val ARRAY_LIST_TYPE = JavaParser.parseClassOrInterfaceType("java.util.ArrayList") object EnumProtocolTermInterp extends (EnumProtocolTerm[JavaLanguage, ?] ~> Target) { def apply[T](term: EnumProtocolTerm[JavaLanguage, T]): Target[T] = term match { @@ -41,18 +94,115 @@ object JacksonProtocolGenerator { Option[java.util.List[_]](x.getEnum()).map(_.asScala.toList.map(_.toString())) } Target.pure(Either.fromOption(enumEntries, "Model has no enumerations")) + case RenderMembers(clsName, elems) => - Target.raiseError(s"RenderMembers($clsName, $elems)") + Target.pure(None) + case EncodeEnum(clsName) => - Target.raiseError(s"EncodeEnum($clsName)") + Target.pure(None) + case DecodeEnum(clsName) => - Target.raiseError(s"DecodeEnum($clsName)") - case RenderClass(clsName, tpe) => - Target.raiseError(s"RenderClass($clsName, $tpe)") + Target.pure(None) + + case RenderClass(clsName, tpe, elems) => + val enumType = JavaParser.parseType(clsName) + + val enumDefns = elems.map { + case (value, termName, _) => new EnumConstantDeclaration( + new NodeList(), new SimpleName(termName.getIdentifier.toSnakeCase.toUpperCase(Locale.US)), + new NodeList(new StringLiteralExpr(value)), + new NodeList() + ) + } + + val nameField = new FieldDeclaration( + util.EnumSet.of(PRIVATE, FINAL), + new VariableDeclarator(STRING_TYPE, "name") + ) + + val constructor = new ConstructorDeclaration(util.EnumSet.of(PRIVATE), clsName) + constructor.addParameter(new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("name"))) + constructor.setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new AssignExpr( + new FieldAccessExpr(new ThisExpr, "name"), + new NameExpr("name"), + AssignExpr.Operator.ASSIGN + )) + ))) + + val getNameMethod = new MethodDeclaration( + util.EnumSet.of(PUBLIC), + STRING_TYPE, + "getName" + ) + getNameMethod.addMarkerAnnotation("JsonValue") + getNameMethod.setBody(new BlockStmt(new NodeList( + new ReturnStmt(new FieldAccessExpr(new ThisExpr, "name")) + ))) + + val parseMethod = new MethodDeclaration( + util.EnumSet.of(PUBLIC, STATIC), + enumType, + "parse" + ) + parseMethod.addMarkerAnnotation("JsonCreator") + parseMethod.addParameter(new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("name"))) + parseMethod.setBody(new BlockStmt(new NodeList( + new ForEachStmt( + new VariableDeclarationExpr(new VariableDeclarator(enumType, "value"), FINAL), + new MethodCallExpr("values"), + new BlockStmt(new NodeList( + new IfStmt( + new MethodCallExpr("value.name.equals", new NameExpr("name")), + new ReturnStmt(new NameExpr("value")), + null + ) + )) + ), + new ThrowStmt(new ObjectCreationExpr( + null, + JavaParser.parseClassOrInterfaceType("IllegalArgumentException"), + new NodeList( + new BinaryExpr( + new BinaryExpr(new StringLiteralExpr("Name '"), new NameExpr("name"), BinaryExpr.Operator.PLUS), + new StringLiteralExpr(s"' is not valid for enum '${clsName}'"), + BinaryExpr.Operator.PLUS + ) + ) + )) + ))) + + + val enumClass = new EnumDeclaration( + util.EnumSet.of(PUBLIC), + new NodeList(), + new SimpleName(clsName), + new NodeList(), + new NodeList(enumDefns: _*), + new NodeList( + nameField, + constructor, + getNameMethod, + parseMethod + ) + ) + + Target.pure(enumClass) + case RenderStaticDefns(clsName, members, accessors, encoder, decoder) => - Target.raiseError(s"RenderStaticDefns($clsName, $members, $accessors, $encoder, $decoder)") + for { + extraImports <- List( + "com.fasterxml.jackson.annotations.JsonCreator", + "com.fasterxml.jackson.annotations.JsonValue" + ).map(safeParseRawImport).sequence + } yield StaticDefns[JavaLanguage]( + className = clsName, + extraImports = extraImports, + definitions = List.empty + ) + case BuildAccessor(clsName, termName) => - Target.raiseError(s"BuildAccessor($clsName, $termName)") + Target.pure(new Name(s"${clsName}.${termName}")) } } @@ -64,116 +214,231 @@ object JacksonProtocolGenerator { case comp: ComposedSchema => Target.pure(Option(comp.getAllOf()).toList.flatMap(_.asScala.toList).lastOption.flatMap(prop => Option(prop.getProperties))) case comp: Schema[_] if Option(comp.get$ref).isDefined => - Target.error(s"Attempted to extractProperties for a ${comp.getClass()}, unsure what to do here") + Target.raiseError(s"Attempted to extractProperties for a ${comp.getClass()}, unsure what to do here") case _ => Target.pure(None) }).map(_.map(_.asScala.toList).toList.flatten) case TransformProperty(clsName, name, property, meta, needCamelSnakeConversion, concreteTypes, isRequired) => - def toCamelCase(s: String): String = - "[_\\.]([a-z])".r.replaceAllIn(s, m => m.group(1).toUpperCase(Locale.US)) + Target.log.function("transformProperty") { + for { + defaultValue <- ((property match { + case _: MapSchema => + Option(Target.pure(new ObjectCreationExpr(null, HASH_MAP_TYPE, new NodeList())).map(x => x: Expression)) + case _: ArraySchema => + Option(Target.pure(new ObjectCreationExpr(null, ARRAY_LIST_TYPE, new NodeList())).map(x => x: Expression)) + case p: BooleanSchema => + Default(p).extract[Boolean].map(x => Target.pure(new BooleanLiteralExpr(x))) + case p: NumberSchema if p.getFormat == "double" => + Default(p).extract[Double].map(x => Target.pure(new DoubleLiteralExpr(x))) + case p: NumberSchema if p.getFormat == "float" => + Default(p).extract[Float].map(x => Target.pure(new DoubleLiteralExpr(x))) + case p: IntegerSchema if p.getFormat == "int32" => + Default(p).extract[Int].map(x => Target.pure(new IntegerLiteralExpr(x))) + case p: IntegerSchema if p.getFormat == "int64" => + Default(p).extract[Long].map(x => Target.pure(new LongLiteralExpr(x))) + case p: StringSchema => + Default(p).extract[String].map(x => Target.fromOption(Try(new StringLiteralExpr(x)).toOption, s"Default string literal for '${p.getTitle}' is null")) + case _ => + None + }): Option[Target[Expression]]).sequence - for { - _ <- Target.log.debug("definitions", "circe", "modelProtocolTerm")(s"Generated ProtocolParameter(${term}, ${name}, ...)") - - argName = if (needCamelSnakeConversion) toCamelCase(name) else name - - defaultValue <- ((property match { - case _: MapSchema => - Option(safeParseExpression[LiteralExpr]("new java.util.Map<>()").map(x => x: Expression)) - case _: ArraySchema => - Option(safeParseExpression[LiteralExpr]("new java.util.List<>()").map(x => x: Expression)) - case p: BooleanSchema => - Default(p).extract[Boolean].map(x => Target.pure(new BooleanLiteralExpr(x))) - case p: NumberSchema if p.getFormat == "double" => - Default(p).extract[Double].map(x => Target.pure(new DoubleLiteralExpr(x))) - case p: NumberSchema if p.getFormat == "float" => - Default(p).extract[Float].map(x => Target.pure(new DoubleLiteralExpr(x))) - case p: IntegerSchema if p.getFormat == "int32" => - Default(p).extract[Int].map(x => Target.pure(new IntegerLiteralExpr(x))) - case p: IntegerSchema if p.getFormat == "int64" => - Default(p).extract[Long].map(x => Target.pure(new LongLiteralExpr(x))) - case p: StringSchema => - Default(p).extract[String].map(safeParseExpression[LiteralExpr](_).map(x => x: Expression)) - case _ => - None - }): Option[Target[Expression]]).sequence - - readOnlyKey = Option(name).filter(_ => Option(property.getReadOnly).contains(true)) - emptyToNull = (property match { - case d: DateSchema => ScalaEmptyIsNull(d) - case dt: DateTimeSchema => ScalaEmptyIsNull(dt) - case s: StringSchema => ScalaEmptyIsNull(s) - case _ => None - }).getOrElse(EmptyIsEmpty) - - tpeClassDep <- meta match { - case SwaggerUtil.Resolved(declType, classDep, _) => - Target.pure((declType, classDep)) - case SwaggerUtil.Deferred(tpeName) => - val tpe = concreteTypes.find(_.clsName == tpeName).map(x => Target.pure(x.tpe)).getOrElse { - println(s"Unable to find definition for ${tpeName}, just inlining") - safeParseType(tpeName) - } - tpe.map((_, Option.empty)) - case SwaggerUtil.DeferredArray(tpeName) => - safeParseType(s"java.util.List<${tpeName}>").map((_, Option.empty)) - case SwaggerUtil.DeferredMap(tpeName) => - safeParseType(s"java.util.List<${tpeName}>").map((_, Option.empty)) - } - (tpe, classDep) = tpeClassDep - - _declDefaultPair <- Option(isRequired) - .filterNot(_ == false) - .fold[Target[(Type, Option[Expression])]]( - (safeParseType(s"java.util.Optional<${tpe}>"), Option(defaultValue.fold[Target[Expression]](safeParseExpression[Expression](s"java.util.Optional.empty()"))(t => safeParseExpression[Expression](s"java.util.Optional.of($t)"))).sequence).mapN((_, _)) - )(Function.const(Target.pure((tpe, defaultValue))) _) - (finalDeclType, finalDefaultValue) = _declDefaultPair - term <- safeParseParameter(s"${finalDeclType} ${argName}") // FIXME: How do we deal with default values? .copy(default = finalDefaultValue) - dep = classDep.filterNot(_.value == clsName) // Filter out our own class name - } yield ProtocolParameter[JavaLanguage](term, name, dep, readOnlyKey, emptyToNull) - - case RenderDTOClass(clsName, selfTerms, parents) => + readOnlyKey = Option(name).filter(_ => Option(property.getReadOnly).contains(true)) + emptyToNull = (property match { + case d: DateSchema => ScalaEmptyIsNull(d) + case dt: DateTimeSchema => ScalaEmptyIsNull(dt) + case s: StringSchema => ScalaEmptyIsNull(s) + case _ => None + }).getOrElse(EmptyIsEmpty) + + tpeClassDep <- meta match { + case SwaggerUtil.Resolved(declType, classDep, _) => + Target.pure((declType, classDep)) + case SwaggerUtil.Deferred(tpeName) => + val tpe = concreteTypes.find(_.clsName == tpeName).map(x => Target.pure(x.tpe)).getOrElse { + println(s"Unable to find definition for ${tpeName}, just inlining") + safeParseType(tpeName) + } + tpe.map((_, Option.empty)) + case SwaggerUtil.DeferredArray(tpeName) => + safeParseType(s"java.util.List<${tpeName}>").map((_, Option.empty)) + case SwaggerUtil.DeferredMap(tpeName) => + safeParseType(s"java.util.List<${tpeName}>").map((_, Option.empty)) + } + (tpe, classDep) = tpeClassDep + + argName = if (needCamelSnakeConversion) name.toCamelCase else name + _declDefaultPair <- Option(isRequired) + .filterNot(_ == false) + .fold[Target[(Type, Option[Expression])]]( + (safeParseType(s"java.util.Optional<${tpe}>"), Option(defaultValue.fold[Target[Expression]](safeParseExpression[Expression](s"java.util.Optional.empty()"))(t => safeParseExpression[Expression](s"java.util.Optional.of($t)"))).sequence).mapN((_, _)) + )(Function.const(Target.pure((tpe, defaultValue))) _) + (finalDeclType, finalDefaultValue) = _declDefaultPair + term <- safeParseParameter(s"${finalDeclType} ${argName}") // FIXME: How do we deal with default values? .copy(default = finalDefaultValue) + dep = classDep.filterNot(_.value == clsName) // Filter out our own class name + } yield ProtocolParameter[JavaLanguage](term, name, dep, readOnlyKey, emptyToNull) + } + + case RenderDTOClass(clsName, selfParams, parents) => val discriminators = parents.flatMap(_.discriminators) - val parenOpt = parents.headOption - val terms = (parents.reverse.flatMap(_.params.map(_.term)) ++ selfTerms).filterNot( - param => discriminators.contains(param.getName().getId()) + val parentOpt = parents.headOption + val params = (parents.reverse.flatMap(_.params) ++ selfParams).filterNot( + param => discriminators.contains(param.term.getName().getId()) ) + val (requiredTerms, optionalTerms) = sortParams(params) + val terms = requiredTerms ++ optionalTerms - val compilationUnit = new CompilationUnit() - val dtoClass = compilationUnit.addClass(clsName).setPublic(true) + val dtoClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, clsName) + dtoClass.addAnnotation(new NormalAnnotationExpr( + new Name("JsonDeserialize"), + new NodeList(new MemberValuePair( + "builder", + new ClassExpr(JavaParser.parseClassOrInterfaceType(s"${clsName}.Builder")) + )) + )) + dtoClass.addAnnotation(new NormalAnnotationExpr( + new Name("JsonIgnoreProperties"), + new NodeList(new MemberValuePair( + "ignoreUnknown", + new BooleanLiteralExpr(true) + )) + )) - parenOpt.foreach({ parent => - val directParent = new ClassOrInterfaceType(null, new SimpleName(parent.clsName), null, null) - val otherParents = parent.interfaces.map({ a => new ClassOrInterfaceType(null, new SimpleName(a), null, null) }) - dtoClass.setExtendedTypes( - new NodeList((directParent +: otherParents): _*) - ) + addParents(dtoClass, parentOpt) + + discriminators.foreach({ discriminator => + val field = dtoClass.addFieldWithInitializer(STRING_TYPE, discriminator, new StringLiteralExpr(clsName), PRIVATE, FINAL) + field.addAnnotation(new NormalAnnotationExpr( + new Name("JsonProperty"), + new NodeList( + new MemberValuePair("value", new StringLiteralExpr(discriminator)), + new MemberValuePair("access", new FieldAccessExpr(new NameExpr("JsonProperty.Access"), "READ_ONLY")) + ) + )) }) - terms.foreach( term => - dtoClass.addField(term.getType(), term.getNameAsString(), PRIVATE) - ) + terms.foreach({ case ParameterTerm(propertyName, parameterName, fieldType, _) => + val field: FieldDeclaration = dtoClass.addField(fieldType, parameterName, PRIVATE, FINAL) + field.addSingleMemberAnnotation("JsonProperty", new StringLiteralExpr(propertyName)) + }) - val primaryConstructor = dtoClass.addConstructor(PUBLIC) - primaryConstructor.setParameters(new NodeList(terms: _*)) + val primaryConstructor = dtoClass.addConstructor(PRIVATE) + primaryConstructor.addMarkerAnnotation("JsonCreator") // FIXME: is this needed? + primaryConstructor.addParameter(new Parameter(util.EnumSet.of(FINAL), JavaParser.parseClassOrInterfaceType("Builder"), new SimpleName("builder"))); primaryConstructor.setBody( new BlockStmt( new NodeList( - terms.map( term => - new ExpressionStmt(new AssignExpr(new FieldAccessExpr(new ThisExpr, term.getNameAsString()), new NameExpr(term.getName()), AssignExpr.Operator.ASSIGN)) - ): _* + terms.map({ case ParameterTerm(_, parameterName, fieldType, _) => + new ExpressionStmt(new AssignExpr( + new FieldAccessExpr(new ThisExpr, parameterName), + fieldType match { + case _: PrimitiveType => new FieldAccessExpr(new NameExpr("builder"), parameterName) + case _ => new MethodCallExpr("requireNonNull", new FieldAccessExpr(new NameExpr("builder"), parameterName)) + }, + AssignExpr.Operator.ASSIGN + )) + }): _* ) ) ) - Target.raiseError(dtoClass.toString()) + // TODO: handle emptyToNull in the return for the getters + terms.foreach({ case ParameterTerm(_, parameterName, fieldType, _) => + val method = dtoClass.addMethod(s"get${parameterName.capitalize}", PUBLIC) + method.setType(fieldType) + method.setBody(new BlockStmt(new NodeList( + new ReturnStmt(new FieldAccessExpr(new ThisExpr, parameterName)) + ))) + }) + + val builderMethod = dtoClass.addMethod("builder", PUBLIC, STATIC) + builderMethod.setType("Builder") + builderMethod.setParameters(new NodeList( + requiredTerms.map({ case ParameterTerm(_, parameterName, _, parameterType) => + new Parameter(util.EnumSet.of(FINAL), parameterType, new SimpleName(parameterName)) + }): _* + )) + builderMethod.setBody(new BlockStmt(new NodeList( + new ReturnStmt( + new ObjectCreationExpr( + null, + JavaParser.parseClassOrInterfaceType("Builder"), + new NodeList(requiredTerms.map({ + case ParameterTerm(_, parameterName, _, _) => new NameExpr(parameterName) + }): _*) + ) + ) + ))) + + val builderClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, STATIC), false, "Builder") + + requiredTerms.foreach({ case ParameterTerm(_, parameterName, fieldType, _) => + builderClass.addField(fieldType, parameterName, PRIVATE, FINAL) + }) + optionalTerms.foreach({ case ParameterTerm(_, parameterName, fieldType, _) => + // TODO: initialize with default if one is specified + builderClass.addFieldWithInitializer(fieldType, parameterName, new MethodCallExpr("java.util.Optional.empty"), PRIVATE) + }) + + val builderConstructor = builderClass.addConstructor(PRIVATE) + builderConstructor.setParameters(new NodeList( + requiredTerms.map({ case ParameterTerm(_, parameterName, _, parameterType) => + new Parameter(util.EnumSet.of(FINAL), parameterType, new SimpleName(parameterName)) + }): _* + )) + builderConstructor.setBody(new BlockStmt(new NodeList( + requiredTerms.map({ case ParameterTerm(_, parameterName, fieldType, _) => + new ExpressionStmt( + new AssignExpr( + new FieldAccessExpr(new ThisExpr, parameterName), + fieldType match { + case _: PrimitiveType => new NameExpr(parameterName) + case _ => new MethodCallExpr("requireNonNull", new NameExpr(parameterName)) + }, + AssignExpr.Operator.ASSIGN + ) + ) + }): _* + ))) + + // TODO: leave out with${name}() if readOnlyKey? + optionalTerms.foreach({ case ParameterTerm(_, parameterName, _, parameterType) => + val setter = builderClass.addMethod(s"with${parameterName.capitalize}", PUBLIC) + setter.setType("Builder") + setter.addAndGetParameter(new Parameter(util.EnumSet.of(FINAL), parameterType, new SimpleName(parameterName))) + setter.setBody(new BlockStmt(new NodeList( + new ExpressionStmt( + new AssignExpr( + new FieldAccessExpr(new ThisExpr, parameterName), + new MethodCallExpr("java.util.Optional.of", new NameExpr(parameterName)), + AssignExpr.Operator.ASSIGN + ) + ), + new ReturnStmt(new ThisExpr) + ))) + }) + + val buildMethod = builderClass.addMethod("build", PUBLIC) + buildMethod.setType(clsName) + buildMethod.setBody(new BlockStmt(new NodeList( + new ReturnStmt(new ObjectCreationExpr( + null, + JavaParser.parseClassOrInterfaceType(clsName), + new NodeList(new ThisExpr) + )) + ))) + + dtoClass.addMember(builderClass) + + Target.pure(dtoClass) case EncodeModel(clsName, needCamelSnakeConversion, selfParams, parents) => - Target.raiseError(s"EncodeModel($clsName, $needCamelSnakeConversion, $selfParams, $parents)") + Target.pure(None) + case DecodeModel(clsName, needCamelSnakeConversion, selfParams, parents) => - Target.raiseError(s"DecodeModel($clsName, $needCamelSnakeConversion, $selfParams, $parents)") + Target.pure(None) + case RenderDTOStaticDefns(clsName, deps, encoder, decoder) => - Target.raiseError(s"RenderDTOStaticDefns($clsName, $deps, $encoder, $decoder)") + Target.pure(StaticDefns(clsName, List.empty, List.empty)) } } @@ -197,28 +462,119 @@ object JacksonProtocolGenerator { object ProtocolSupportTermInterp extends (ProtocolSupportTerm[JavaLanguage, ?] ~> Target) { def apply[T](term: ProtocolSupportTerm[JavaLanguage, T]): Target[T] = term match { case ExtractConcreteTypes(definitions) => - Target.raiseError(s"ExtractConcreteTypes($definitions)") + definitions.fold[Target[List[PropMeta[JavaLanguage]]]](Target.raiseError, Target.pure) + case ProtocolImports() => - Target.raiseError(s"ProtocolImports()") + (List( + "com.fasterxml.jackson.annotation.JsonCreator", + "com.fasterxml.jackson.annotation.JsonDeserialize", + "com.fasterxml.jackson.annotation.JsonIgnoreProperties", + "com.fasterxml.jackson.annotation.JsonProperty" + ).map(safeParseRawImport) ++ List( + "java.lang.Objects.requireNonNull" + ).map(safeParseRawStaticImport)).sequence + case PackageObjectImports() => - Target.raiseError(s"PackageObjectImports()") + Target.pure(List.empty) + case PackageObjectContents() => - Target.raiseError(s"PackageObjectContents()") + Target.pure(List.empty) } } object PolyProtocolTermInterp extends (PolyProtocolTerm[JavaLanguage, ?] ~> Target) { override def apply[A](fa: PolyProtocolTerm[JavaLanguage, A]): Target[A] = fa match { case ExtractSuperClass(swagger, definitions) => - Target.raiseError(s"ExtractSuperClass($swagger, $definitions)") + def allParents(model: Schema[_]): List[(String, Schema[_], List[Schema[_]])] = + model match { + case elem: ComposedSchema => + Option(elem.getAllOf).map(_.asScala.toList).getOrElse(List.empty) match { + case head :: tail => + definitions + .collectFirst({ + case (clsName, e) if Option(head.get$ref).exists(_.endsWith(s"/$clsName")) => + (clsName, e, tail.toList) :: allParents(e) + }) + .getOrElse(List.empty) + case _ => List.empty + } + case _ => List.empty + } + + Target.pure(allParents(swagger)) + case RenderADTStaticDefns(clsName, discriminator, encoder, decoder) => - Target.raiseError(s"RenderADTStaticDefns($clsName, $discriminator, $encoder, $decoder)") + for { + extraImports <- List( + "com.fasterxml.jackson.annotations.JsonIgnoreProperties", + "com.fasterxml.jackson.annotations.JsonSubTypes", + "com.fasterxml.jackson.annotations.JsonTypeInfo" + ).map(safeParseRawImport).sequence + } yield StaticDefns[JavaLanguage]( + clsName, + extraImports, + List.empty + ) + case DecodeADT(clsName, children) => - Target.raiseError(s"DecodeADT($clsName, $children)") + Target.pure(None) + case EncodeADT(clsName, children) => - Target.raiseError(s"EncodeADT($clsName, $children)") - case RenderSealedTrait(className, terms, discriminator, parents) => - Target.raiseError(s"RenderSealedTrait($className, $terms, $discriminator, $parents)") + Target.pure(None) + + case RenderSealedTrait(className, selfParams, discriminator, parents, children) => + val parentOpt = parents.headOption + val params = (parents.reverse.flatMap(_.params) ++ selfParams).filterNot(_.term.getName.getId == discriminator) + val (requiredTerms, optionalTerms) = sortParams(params) + val terms = requiredTerms ++ optionalTerms + + val abstractClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, ABSTRACT), false, className) + abstractClass.addAnnotation(new NormalAnnotationExpr( + new Name("JsonIgnoreProperties"), + new NodeList(new MemberValuePair( + "ignoreUnknown", + new BooleanLiteralExpr(true) + )) + )) + abstractClass.addAnnotation(new NormalAnnotationExpr( + new Name("JsonTypeInfo"), + new NodeList( + new MemberValuePair( + "use", + new FieldAccessExpr(new NameExpr("JsonTypeInfo.Id"), "NAME") + ), + new MemberValuePair( + "include", + new FieldAccessExpr(new NameExpr("JsonTypeInfo.As"), "PROPERTY") + ), + new MemberValuePair( + "property", + new StringLiteralExpr(discriminator) + ) + ) + )) + abstractClass.addSingleMemberAnnotation( + "JsonSubTypes", + new ArrayInitializerExpr(new NodeList( + children.map(child => new NormalAnnotationExpr( + new Name("JsonSubTypes.Type"), + new NodeList( + new MemberValuePair("name", new StringLiteralExpr(child)), + new MemberValuePair("value", new ClassExpr(JavaParser.parseType(child))) + ) + )): _* + )) + ) + + addParents(abstractClass, parentOpt) + + terms.foreach({ case ParameterTerm(_, parameterName, fieldType, _) => + val method: MethodDeclaration = abstractClass.addMethod(s"get${parameterName.capitalize}", PUBLIC, ABSTRACT) + method.setType(fieldType) + method.setBody(null) + }) + + Target.pure(abstractClass) } } } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index d5406b078a..0ed505ff25 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -2,17 +2,22 @@ package com.twilio.guardrail.generators.syntax import cats.implicits._ import com.github.javaparser.JavaParser -import com.github.javaparser.ast.{ CompilationUnit, Node } +import com.github.javaparser.ast.{CompilationUnit, ImportDeclaration} import com.github.javaparser.ast.`type`.Type import com.github.javaparser.ast.body.Parameter -import com.github.javaparser.ast.expr.{ Expression, Name, SimpleName } +import com.github.javaparser.ast.expr.{Expression, Name, SimpleName} import com.github.javaparser.printer.PrettyPrinterConfiguration import com.github.javaparser.printer.PrettyPrinterConfiguration.IndentType import com.twilio.guardrail.Target +import java.util.Optional import scala.reflect.ClassTag import scala.util.Try object Java { + implicit class RichJavaOptional[T](val o: Optional[T]) extends AnyVal { + def asScala: Option[T] = if (o.isPresent) Option(o.get) else None + } + private[this] def safeParse[T](log: String)(parser: String => T, s: String)(implicit cls: ClassTag[T]): Target[T] = { Target.log.debug(log)(s) >> ( Try(parser(s)).toEither.fold(t => Target.raiseError(s"Unable to parse '${s}' to a ${cls.runtimeClass.getName}: ${t.getMessage}"), Target.pure) @@ -25,6 +30,9 @@ object Java { def safeParseType(s: String): Target[Type] = safeParse("safeParseType")(JavaParser.parseType, s) def safeParseExpression[T <: Expression](s: String)(implicit cls: ClassTag[T]): Target[T] = safeParse[T]("safeParseExpression")(JavaParser.parseExpression[T], s) def safeParseParameter(s: String): Target[Parameter] = safeParse("safeParseParameter")(JavaParser.parseParameter, s) + def safeParseImport(s: String): Target[ImportDeclaration] = safeParse("safeParseImport")(JavaParser.parseImport, s) + def safeParseRawImport(s: String): Target[ImportDeclaration] = safeParse("safeParseRawImport")(JavaParser.parseImport, s"import ${s};") + def safeParseRawStaticImport(s: String): Target[ImportDeclaration] = safeParse("safeParseStaticImport")(JavaParser.parseImport, s"import static ${s};") val printer: PrettyPrinterConfiguration = new PrettyPrinterConfiguration() .setColumnAlignFirstMethodChain(true) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/package.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/package.scala index a3b87973cb..deae48013f 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/package.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/package.scala @@ -21,5 +21,11 @@ package object syntax { "^([A-Z])".r .replaceAllIn(fromSnakeOrDashed, m => m.group(1).toLowerCase(Locale.US)) } + + def toSnakeCase: String = { + val noPascal = "^[A-Z]".r.replaceAllIn(s, _.group(0).toLowerCase(Locale.US)) + val fromCamel = "[A-Z]".r.replaceAllIn(noPascal, "_" + _.group(0)) + fromCamel.replaceAllLiterally("-", "_") + } } } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/languages/JavaLanguage.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/languages/JavaLanguage.scala index b1c10b3e1a..7707208c9f 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/languages/JavaLanguage.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/languages/JavaLanguage.scala @@ -17,15 +17,15 @@ class JavaLanguage extends LanguageAbstraction { // Definitions type Definition = com.github.javaparser.ast.body.BodyDeclaration[_] - type AbstractClass = Nothing - type ClassDefinition = com.github.javaparser.ast.body.ClassOrInterfaceDeclaration + type AbstractClass = com.github.javaparser.ast.body.ClassOrInterfaceDeclaration + type ClassDefinition = com.github.javaparser.ast.body.TypeDeclaration[_] type InterfaceDefinition = com.github.javaparser.ast.body.ClassOrInterfaceDeclaration type ObjectDefinition = Nothing type Trait = com.github.javaparser.ast.body.ClassOrInterfaceDeclaration // Functions - type InstanceMethod = Nothing - type StaticMethod = Nothing + type InstanceMethod = com.github.javaparser.ast.body.MethodDeclaration + type StaticMethod = com.github.javaparser.ast.body.MethodDeclaration // Values type ValueDefinition = com.github.javaparser.ast.body.VariableDeclarator From 83cbece3bf79eff2f2e7e2b949871228d8297ddf Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Fri, 1 Mar 2019 07:23:54 -0800 Subject: [PATCH 22/86] Make framework implicits and package objects optional --- .../scala/com/twilio/guardrail/Common.scala | 18 +++++++++--------- .../generators/AkkaHttpGenerator.scala | 2 +- .../generators/EndpointsGenerator.scala | 4 ++-- .../guardrail/generators/Http4sGenerator.scala | 11 +---------- .../guardrail/generators/JavaGenerator.scala | 12 +++++------- .../guardrail/generators/ScalaGenerator.scala | 4 ++-- .../com/twilio/guardrail/terms/ScalaTerm.scala | 7 +++---- .../twilio/guardrail/terms/ScalaTerms.scala | 4 ++-- .../terms/framework/FrameworkTerm.scala | 2 +- .../terms/framework/FrameworkTerms.scala | 2 +- 10 files changed, 27 insertions(+), 39 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala index f4083c4acb..28d5d4d0cb 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala @@ -6,13 +6,13 @@ import cats.free.Free import cats.implicits._ import cats.~> import com.twilio.guardrail.languages.LA -import com.twilio.guardrail.protocol.terms.protocol.{ ArrayProtocolTerms, EnumProtocolTerms, ModelProtocolTerms, PolyProtocolTerms, ProtocolSupportTerms } +import com.twilio.guardrail.protocol.terms.protocol.{ArrayProtocolTerms, EnumProtocolTerms, ModelProtocolTerms, PolyProtocolTerms, ProtocolSupportTerms} import com.twilio.guardrail.terms.framework.FrameworkTerms import com.twilio.guardrail.protocol.terms.client.ClientTerms import com.twilio.guardrail.protocol.terms.server.ServerTerms import com.twilio.guardrail.shims._ -import com.twilio.guardrail.terms.{ CoreTerms, ScalaTerms, SwaggerTerms } -import java.nio.file.{ Path, Paths } +import com.twilio.guardrail.terms.{CoreTerms, ScalaTerms, SwaggerTerms} +import java.nio.file.{Path, Paths} import java.util.Locale import scala.collection.JavaConverters._ import scala.io.AnsiColor @@ -114,23 +114,23 @@ object Common { ) frameworkImports <- getFrameworkImports(context.tracing) - _frameworkImplicits <- getFrameworkImplicits() - (frameworkImplicitName, frameworkImplicits) = _frameworkImplicits + frameworkImplicits <- getFrameworkImplicits() + frameworkImplicitName = frameworkImplicits.map(_._1) files <- (clients.traverse(writeClient(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, _)), servers.traverse(writeServer(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, _))).mapN(_ ++ _) implicits <- renderImplicits(pkgPath, pkgName, frameworkImports, protocolImports, customImports) - frameworkImplicitsFile <- renderFrameworkImplicits(pkgPath, pkgName, frameworkImports, protocolImports, frameworkImplicits, frameworkImplicitName) + frameworkImplicitsFile <- frameworkImplicits.fold(Free.pure[F, Option[WriteTree]](None))({ case (name, defn) => renderFrameworkImplicits(pkgPath, pkgName, frameworkImports, protocolImports, defn, name).map(Option.apply) }) } yield ( protocolDefinitions ++ List(packageObject) ++ files ++ List( - implicits, - frameworkImplicitsFile - ) + implicits + ) ++ + frameworkImplicitsFile.toList ).toList } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpGenerator.scala index 7947850a54..5f20863623 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpGenerator.scala @@ -165,7 +165,7 @@ object AkkaHttpGenerator { implicit def UnitUnmarshaller(implicit mat: Materializer): Unmarshaller[Multipart.FormData.BodyPart, Unit] = StaticUnmarshaller(()) } """ - Target.pure((q"AkkaHttpImplicits", defn)) + Target.pure(Some((q"AkkaHttpImplicits", defn))) } case LookupStatusCode(key) => diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsGenerator.scala index d010053e35..9a99371de9 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsGenerator.scala @@ -34,7 +34,7 @@ object EndpointsGenerator { q"import org.scalajs.dom.raw.XMLHttpRequest" ) ) - case GetFrameworkImplicits() => Target.pure((q"EndpointsImplicits", q""" + case GetFrameworkImplicits() => Target.pure(Some((q"EndpointsImplicits", q""" object EndpointsImplicits { case class UnknownStatusException(xhr: XMLHttpRequest) extends Exception implicit class FaithfulFutureExtensions[T](value: Future[T]) { @@ -115,7 +115,7 @@ object EndpointsGenerator { } } } - """)) + """))) case LookupStatusCode(key) => key match { case "100" => Target.pure((100, q"Continue")) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sGenerator.scala index 1f7b147c2e..7203aff8b8 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sGenerator.scala @@ -1,18 +1,9 @@ package com.twilio.guardrail package generators -import _root_.io.swagger.v3.oas.models._ import cats.arrow.FunctionK -import cats.data.NonEmptyList -import cats.instances.all._ -import cats.syntax.flatMap._ -import cats.syntax.functor._ -import cats.syntax.traverse._ -import com.twilio.guardrail.extract.ScalaPackage import com.twilio.guardrail.languages.ScalaLanguage import com.twilio.guardrail.terms.framework._ -import java.util.Locale -import scala.collection.JavaConverters._ import scala.meta._ object Http4sGenerator { @@ -76,7 +67,7 @@ object Http4sGenerator { } } """ - (q"Http4sImplicits", defn) + Some((q"Http4sImplicits", defn)) }) case LookupStatusCode(key) => diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala index 1a774e22e2..73facaa7d7 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala @@ -117,12 +117,10 @@ object JavaGenerator { Target.pure(WriteTree(pkgPath.resolve("Implicits.java"), new Array[Byte](0))) case RenderFrameworkImplicits(pkgPath, pkgName, frameworkImports, jsonImports, frameworkImplicits, frameworkImplicitName) => - // FIXME - Target.pure(WriteTree(pkgPath.resolve(s"FrameworkImplicits.java"), new Array[Byte](0))) + Target.raiseError("Java does not support Framework Implicits") case WritePackageObject(dtoPackagePath, dtoComponents, customImports, packageObjectImports, protocolImports, packageObjectContents, extraTypes) => - // FIXME - Target.pure(WriteTree(dtoPackagePath.resolve("Package.java"), new Array[Byte](0))) + Target.raiseError("Java does not support Package Objects") case WriteProtocolDefinition(outputPath, pkgName, definitions, dtoComponents, imports, elem) => elem match { @@ -199,8 +197,8 @@ object JavaGenerator { for { pkgDecl <- buildPkgDecl(pkgName ++ pkg) implicitsImport <- safeParseName((pkgName ++ List("Implicits", "*")).mkString(".")).map(name => new ImportDeclaration(name, false, true)) - frameworkImplicitsImport <- safeParseName((pkgName ++ List(frameworkImplicitName.getIdentifier, "*")).mkString(".")) - .map(name => new ImportDeclaration(name, false, true)) + //frameworkImplicitsImport <- safeParseName((pkgName ++ List(frameworkImplicitName.getIdentifier, "*")).mkString(".")) + // .map(name => new ImportDeclaration(name, false, true)) dtoComponentsImport <- safeParseName((dtoComponents :+ "*").mkString(".")).map(name => new ImportDeclaration(name, false, true)) } yield { val cu = new CompilationUnit() @@ -208,7 +206,7 @@ object JavaGenerator { imports.map(cu.addImport) customImports.map(cu.addImport) cu.addImport(implicitsImport) - cu.addImport(frameworkImplicitsImport) + //cu.addImport(frameworkImplicitsImport) cu.addImport(dtoComponentsImport) val clientCopy = client.head.merge.clone() // FIXME: WriteClient needs to be altered to return `NonEmptyList[WriteTree]` to accommodate Java not being able to put multiple classes in the same file. Scala just jams them all together, but this could be improved over there as well. staticDefns.definitions.foreach(clientCopy.addMember) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala index 5c9a64a5d0..8bb1a76492 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala @@ -318,7 +318,7 @@ object ScalaGenerator { source""" package ${buildPkgTerm(pkgName ++ pkg)} import ${buildPkgTerm(List("_root_") ++ pkgName ++ List("Implicits"))}._ - import ${buildPkgTerm(List("_root_") ++ pkgName)}.${frameworkImplicitName}._ + ..${frameworkImplicitName.map(name => q"import ${buildPkgTerm(List("_root_") ++ pkgName)}.${name}._")} import ${buildPkgTerm(List("_root_") ++ dtoComponents)}._ ..${customImports}; ..${imports}; @@ -336,7 +336,7 @@ object ScalaGenerator { package ${buildPkgTerm((pkgName ++ pkg.toList))} ..${extraImports} import ${buildPkgTerm(List("_root_") ++ pkgName ++ List("Implicits"))}._ - import ${buildPkgTerm(List("_root_") ++ pkgName)}.${frameworkImplicitName}._ + ..${frameworkImplicitName.map(name => q"import ${buildPkgTerm(List("_root_") ++ pkgName)}.${name}._")} import ${buildPkgTerm(List("_root_") ++ dtoComponents)}._ ..${customImports} ..$src diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala index d9aed766b9..235cf5a489 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala @@ -1,9 +1,8 @@ package com.twilio.guardrail package terms -import cats.data.NonEmptyList -import com.twilio.guardrail.languages.LA import com.twilio.guardrail.SwaggerUtil.LazyResolvedType +import com.twilio.guardrail.languages.LA import java.nio.file.Path sealed trait ScalaTerm[L <: LA, T] @@ -85,14 +84,14 @@ case class WriteProtocolDefinition[L <: LA](outputPath: Path, case class WriteClient[L <: LA](pkgPath: Path, pkgName: List[String], customImports: List[L#Import], - frameworkImplicitName: L#TermName, + frameworkImplicitName: Option[L#TermName], dtoComponents: List[String], client: Client[L]) extends ScalaTerm[L, WriteTree] case class WriteServer[L <: LA](pkgPath: Path, pkgName: List[String], customImports: List[L#Import], - frameworkImplicitName: L#TermName, + frameworkImplicitName: Option[L#TermName], dtoComponents: List[String], server: Server[L]) extends ScalaTerm[L, WriteTree] diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala index 5d11a9b70d..c254f42d0b 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala @@ -92,14 +92,14 @@ class ScalaTerms[L <: LA, F[_]](implicit I: InjectK[ScalaTerm[L, ?], F]) { def writeClient(pkgPath: Path, pkgName: List[String], customImports: List[L#Import], - frameworkImplicitName: L#TermName, + frameworkImplicitName: Option[L#TermName], dtoComponents: List[String], client: Client[L]): Free[F, WriteTree] = Free.inject[ScalaTerm[L, ?], F](WriteClient(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, client)) def writeServer(pkgPath: Path, pkgName: List[String], customImports: List[L#Import], - frameworkImplicitName: L#TermName, + frameworkImplicitName: Option[L#TermName], dtoComponents: List[String], server: Server[L]): Free[F, WriteTree] = Free.inject[ScalaTerm[L, ?], F](WriteServer(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, server)) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/framework/FrameworkTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/framework/FrameworkTerm.scala index 273acee573..225afaf3c5 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/framework/FrameworkTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/framework/FrameworkTerm.scala @@ -5,7 +5,7 @@ import com.twilio.guardrail.languages.LA sealed trait FrameworkTerm[L <: LA, T] case class GetFrameworkImports[L <: LA](tracing: Boolean) extends FrameworkTerm[L, List[L#Import]] -case class GetFrameworkImplicits[L <: LA]() extends FrameworkTerm[L, (L#TermName, L#ObjectDefinition)] +case class GetFrameworkImplicits[L <: LA]() extends FrameworkTerm[L, Option[(L#TermName, L#ObjectDefinition)]] case class LookupStatusCode[L <: LA](key: String) extends FrameworkTerm[L, (Int, L#TermName)] case class FileType[L <: LA](format: Option[String]) extends FrameworkTerm[L, L#Type] case class ObjectType[L <: LA](format: Option[String]) extends FrameworkTerm[L, L#Type] diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/framework/FrameworkTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/framework/FrameworkTerms.scala index 9a2892e90b..f38ede3d24 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/framework/FrameworkTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/framework/FrameworkTerms.scala @@ -8,7 +8,7 @@ import com.twilio.guardrail.languages.LA class FrameworkTerms[L <: LA, F[_]](implicit I: InjectK[FrameworkTerm[L, ?], F]) { def getFrameworkImports(tracing: Boolean): Free[F, List[L#Import]] = Free.inject[FrameworkTerm[L, ?], F](GetFrameworkImports[L](tracing)) - def getFrameworkImplicits(): Free[F, (L#TermName, L#ObjectDefinition)] = + def getFrameworkImplicits(): Free[F, Option[(L#TermName, L#ObjectDefinition)]] = Free.inject[FrameworkTerm[L, ?], F](GetFrameworkImplicits[L]()) def lookupStatusCode(key: String): Free[F, (Int, L#TermName)] = Free.inject[FrameworkTerm[L, ?], F](LookupStatusCode(key)) From a03af72f1b99eac5caffa7b7513dc2c664bea033 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Fri, 1 Mar 2019 09:05:26 -0800 Subject: [PATCH 23/86] Various fixes to type parsing in JavaGenerator --- .../generators/Java/JacksonGenerator.scala | 1 - .../guardrail/generators/JavaGenerator.scala | 55 +++++++++++++------ .../guardrail/generators/syntax/Java.scala | 5 +- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala index c31c2dbb26..e14296adee 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -80,7 +80,6 @@ object JacksonGenerator { .map(_.tpe) .map(f) - private val STRING_TYPE = JavaParser.parseClassOrInterfaceType("String") private val HASH_MAP_TYPE = JavaParser.parseClassOrInterfaceType("java.util.HashMap") private val ARRAY_LIST_TYPE = JavaParser.parseClassOrInterfaceType("java.util.ArrayList") diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala index 73facaa7d7..b393221de4 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala @@ -3,9 +3,9 @@ package com.twilio.guardrail.generators import cats.~> import cats.instances.option._ import cats.syntax.traverse._ -import com.github.javaparser.ast.{ CompilationUnit, ImportDeclaration, Node, PackageDeclaration } -import com.github.javaparser.ast.`type`.{ PrimitiveType, Type } -import com.github.javaparser.ast.body.{ BodyDeclaration, ClassOrInterfaceDeclaration, Parameter } +import com.github.javaparser.ast._ +import com.github.javaparser.ast.`type`.{ClassOrInterfaceType, PrimitiveType, Type, VoidType, ArrayType => AstArrayType} +import com.github.javaparser.ast.body.Parameter import com.github.javaparser.ast.expr._ import com.github.javaparser.ast.stmt.Statement import com.twilio.guardrail._ @@ -14,11 +14,26 @@ import com.twilio.guardrail.generators.syntax.Java._ import com.twilio.guardrail.languages.JavaLanguage import com.twilio.guardrail.terms._ import java.nio.charset.StandardCharsets +import java.util object JavaGenerator { object JavaInterp extends (ScalaTerm[JavaLanguage, ?] ~> Target) { def buildPkgDecl(parts: List[String]): Target[PackageDeclaration] = safeParseName(parts.mkString(".")).map(new PackageDeclaration(_)) + def buildMethodCall(name: String, arg: Option[Node] = None): Target[Node] = arg match { + case Some(expr: Expression) => Target.pure(new MethodCallExpr(name, expr)) + case None => Target.pure(new MethodCallExpr(name)) + case other => Target.raiseError(s"Need expression to call '${name}' but got a ${other.getClass.getName} instead") + } + + def resolveReferenceTypeName(tpe: Type): Target[String] = tpe match { + case a: AstArrayType if a.getComponentType.isPrimitiveType => resolveReferenceTypeName(new AstArrayType(a.getComponentType.asPrimitiveType().toBoxedType)) + case a: AstArrayType => Target.pure(a.asString) + case ci: ClassOrInterfaceType => Target.pure(ci.getNameAsString) + case p: PrimitiveType => Target.pure(p.toBoxedType.getNameAsString) + case _: VoidType => Target.pure("Void") + } + def apply[T](term: ScalaTerm[JavaLanguage, T]): Target[T] = term match { case LitString(value) => Target.pure(new StringLiteralExpr(value)) case LitFloat(value) => Target.pure(new DoubleLiteralExpr(value)) @@ -26,12 +41,12 @@ object JavaGenerator { case LitInt(value) => Target.pure(new IntegerLiteralExpr(value)) case LitLong(value) => Target.pure(new LongLiteralExpr(value)) case LitBoolean(value) => Target.pure(new BooleanLiteralExpr(value)) - case LiftOptionalType(value) => safeParseType(s"java.util.Optional<${value}>") - case LiftOptionalTerm(value) => safeParseExpression[MethodCallExpr](s"java.util.Optional.ofNullable(${value}").map(identity) - case EmptyOptionalTerm() => safeParseExpression[MethodCallExpr]("java.util.Optional.empty()").map(identity) - case LiftVectorType(value) => safeParseType(s"java.util.List<${value}>") - case LiftVectorTerm(value) => safeParseExpression[MethodCallExpr](s"java.util.Collections.singletonList(${value})").map(identity) - case LiftMapType(value) => safeParseType(s"java.util.Map") + case LiftOptionalType(value) => safeParseClassOrInterfaceType(s"java.util.Optional").map(_.setTypeArguments(new NodeList(value))) + case LiftOptionalTerm(value) => buildMethodCall("java.util.Optional.ofNullable", Some(value)) + case EmptyOptionalTerm() => buildMethodCall("java.util.Optional.empty") + case LiftVectorType(value) => safeParseClassOrInterfaceType("java.util.List").map(_.setTypeArguments(new NodeList(value))) + case LiftVectorTerm(value) => buildMethodCall("java.util.Collections.singletonList", Some(value)) + case LiftMapType(value) => safeParseClassOrInterfaceType("java.util.Map").map(_.setTypeArguments(STRING_TYPE, value)) case LookupEnumDefaultValue(tpe, defaultValue, values) => { // FIXME: Is there a better way to do this? There's a gap of coverage here defaultValue match { @@ -77,9 +92,9 @@ object JavaGenerator { case PureTypeName(tpe) => Option(tpe).map(_.trim).filterNot(_.isEmpty).map(safeParseName).getOrElse(Target.raiseError("A structure's name is empty")) - case PureMethodParameter(name, tpe, default) => + case PureMethodParameter(nameStr, tpe, default) => // FIXME: java methods do not support default param values -- what should we do here? - safeParseParameter(s"final ${tpe} ${name}") + safeParseSimpleName(nameStr.asString).map(name => new Parameter(util.EnumSet.of(Modifier.FINAL), tpe, name)) case TypeNamesEqual(a, b) => Target.pure(a.asString == b.asString) @@ -91,22 +106,30 @@ object JavaGenerator { safeParseName(tpe.asString).map(Option.apply) case ExtractTermName(term) => - Target.pure(term.getIdentifier) + Target.pure(term.asString) case AlterMethodParameterName(param, name) => - safeParseSimpleName(name.asString).map(new Parameter(param.getType, _)) + safeParseSimpleName(name.asString).map(new Parameter( + param.getTokenRange.orElse(null), + param.getModifiers, + param.getAnnotations, + param.getType, + param.isVarArgs, + param.getVarArgsAnnotations, + _ + )) case DateType() => safeParseType("java.time.LocalDate") case DateTimeType() => safeParseType("java.time.OffsetDateTime") - case StringType(format) => format.fold(safeParseType("String"))(safeParseType) + case StringType(format) => format.fold(Target.pure[Type](STRING_TYPE))(safeParseType) case FloatType() => safeParseType("Float") case DoubleType() => safeParseType("Double") case NumberType(format) => safeParseType("java.math.BigDecimal") - case IntType() => safeParseType("Int") + case IntType() => safeParseType("Integer") case LongType() => safeParseType("Long") case IntegerType(format) => safeParseType("java.math.BigInteger") case BooleanType(format) => safeParseType("Boolean") - case ArrayType(format) => safeParseType("java.util.List") + case ArrayType(format) => safeParseClassOrInterfaceType("java.util.List").map(_.setTypeArguments(new NodeList[Type](STRING_TYPE))) case FallbackType(tpe, format) => safeParseType(tpe) case WidenTypeName(tpe) => safeParseType(tpe.asString) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index 0ed505ff25..d010ad6b24 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -3,7 +3,7 @@ package com.twilio.guardrail.generators.syntax import cats.implicits._ import com.github.javaparser.JavaParser import com.github.javaparser.ast.{CompilationUnit, ImportDeclaration} -import com.github.javaparser.ast.`type`.Type +import com.github.javaparser.ast.`type`.{ClassOrInterfaceType, Type} import com.github.javaparser.ast.body.Parameter import com.github.javaparser.ast.expr.{Expression, Name, SimpleName} import com.github.javaparser.printer.PrettyPrinterConfiguration @@ -28,12 +28,15 @@ object Java { def safeParseSimpleName(s: String): Target[SimpleName] = safeParse("safeParseSimpleName")(JavaParser.parseSimpleName, s) def safeParseName(s: String): Target[Name] = safeParse("safeParseName")(JavaParser.parseName, s) def safeParseType(s: String): Target[Type] = safeParse("safeParseType")(JavaParser.parseType, s) + def safeParseClassOrInterfaceType(s: String): Target[ClassOrInterfaceType] = safeParse("safeParseClassOrInterfaceType")(JavaParser.parseClassOrInterfaceType, s) def safeParseExpression[T <: Expression](s: String)(implicit cls: ClassTag[T]): Target[T] = safeParse[T]("safeParseExpression")(JavaParser.parseExpression[T], s) def safeParseParameter(s: String): Target[Parameter] = safeParse("safeParseParameter")(JavaParser.parseParameter, s) def safeParseImport(s: String): Target[ImportDeclaration] = safeParse("safeParseImport")(JavaParser.parseImport, s) def safeParseRawImport(s: String): Target[ImportDeclaration] = safeParse("safeParseRawImport")(JavaParser.parseImport, s"import ${s};") def safeParseRawStaticImport(s: String): Target[ImportDeclaration] = safeParse("safeParseStaticImport")(JavaParser.parseImport, s"import static ${s};") + val STRING_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("String") + val printer: PrettyPrinterConfiguration = new PrettyPrinterConfiguration() .setColumnAlignFirstMethodChain(true) .setColumnAlignParameters(true) From 9378a09cb827d532f4b01e57cbcf736945232052 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Fri, 1 Mar 2019 09:06:15 -0800 Subject: [PATCH 24/86] Make implicits and package objects optional in the framework interpreter --- .../main/scala/com/twilio/guardrail/Common.scala | 6 ++---- .../twilio/guardrail/generators/JavaGenerator.scala | 13 ++++++------- .../guardrail/generators/ScalaGenerator.scala | 6 +++--- .../com/twilio/guardrail/terms/ScalaTerm.scala | 4 ++-- .../com/twilio/guardrail/terms/ScalaTerms.scala | 4 ++-- 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala index 28d5d4d0cb..7b45a0a031 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala @@ -125,11 +125,9 @@ object Common { } yield ( protocolDefinitions ++ - List(packageObject) ++ + packageObject.toList ++ files ++ - List( - implicits - ) ++ + implicits.toList ++ frameworkImplicitsFile.toList ).toList } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala index b393221de4..28c4c3fccd 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala @@ -136,14 +136,13 @@ object JavaGenerator { case WidenTermSelect(value) => Target.pure(value) case RenderImplicits(pkgPath, pkgName, frameworkImports, jsonImports, customImports) => - // FIXME - Target.pure(WriteTree(pkgPath.resolve("Implicits.java"), new Array[Byte](0))) + Target.pure(None) case RenderFrameworkImplicits(pkgPath, pkgName, frameworkImports, jsonImports, frameworkImplicits, frameworkImplicitName) => Target.raiseError("Java does not support Framework Implicits") case WritePackageObject(dtoPackagePath, dtoComponents, customImports, packageObjectImports, protocolImports, packageObjectContents, extraTypes) => - Target.raiseError("Java does not support Package Objects") + Target.pure(None) case WriteProtocolDefinition(outputPath, pkgName, definitions, dtoComponents, imports, elem) => elem match { @@ -219,18 +218,18 @@ object JavaGenerator { Client(pkg, clientName, imports, staticDefns, client, responseDefinitions)) => for { pkgDecl <- buildPkgDecl(pkgName ++ pkg) - implicitsImport <- safeParseName((pkgName ++ List("Implicits", "*")).mkString(".")).map(name => new ImportDeclaration(name, false, true)) + //implicitsImport <- safeParseName((pkgName ++ List("Implicits", "*")).mkString(".")).map(name => new ImportDeclaration(name, false, true)) //frameworkImplicitsImport <- safeParseName((pkgName ++ List(frameworkImplicitName.getIdentifier, "*")).mkString(".")) // .map(name => new ImportDeclaration(name, false, true)) - dtoComponentsImport <- safeParseName((dtoComponents :+ "*").mkString(".")).map(name => new ImportDeclaration(name, false, true)) + //dtoComponentsImport <- safeParseName((dtoComponents :+ "*").mkString(".")).map(name => new ImportDeclaration(name, false, true)) } yield { val cu = new CompilationUnit() cu.setPackageDeclaration(pkgDecl) imports.map(cu.addImport) customImports.map(cu.addImport) - cu.addImport(implicitsImport) + //cu.addImport(implicitsImport) //cu.addImport(frameworkImplicitsImport) - cu.addImport(dtoComponentsImport) + cu.addImport((dtoComponents :+ "*").mkString(".")) //dtoComponentsImport) val clientCopy = client.head.merge.clone() // FIXME: WriteClient needs to be altered to return `NonEmptyList[WriteTree]` to accommodate Java not being able to put multiple classes in the same file. Scala just jams them all together, but this could be improved over there as well. staticDefns.definitions.foreach(clientCopy.addMember) responseDefinitions.foreach(clientCopy.addMember) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala index 8bb1a76492..7a33e0ca2b 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala @@ -192,7 +192,7 @@ object ScalaGenerator { } } """ - Target.pure(WriteTree(pkgPath.resolve("Implicits.scala"), implicits.syntax.getBytes(StandardCharsets.UTF_8))) + Target.pure(Some(WriteTree(pkgPath.resolve("Implicits.scala"), implicits.syntax.getBytes(StandardCharsets.UTF_8)))) case RenderFrameworkImplicits(pkgPath, pkgName, frameworkImports, jsonImports, frameworkImplicits, frameworkImplicitName) => val pkg: Term.Ref = @@ -233,7 +233,7 @@ object ScalaGenerator { stat.copy(rhs = q"${companion}.${mirror}") }) - Target.pure( + Target.pure(Some( WriteTree( dtoPackagePath.resolve("package.scala"), source""" @@ -250,7 +250,7 @@ object ScalaGenerator { } """.syntax.getBytes(StandardCharsets.UTF_8) ) - ) + )) case WriteProtocolDefinition(outputPath, pkgName, definitions, dtoComponents, imports, elem) => Target.pure(elem match { case EnumDefinition(_, _, _, cls, staticDefns) => diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala index 235cf5a489..80efac552f 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala @@ -58,7 +58,7 @@ case class RenderImplicits[L <: LA](pkgPath: Path, frameworkImports: List[L#Import], jsonImports: List[L#Import], customImports: List[L#Import]) - extends ScalaTerm[L, WriteTree] + extends ScalaTerm[L, Option[WriteTree]] case class RenderFrameworkImplicits[L <: LA](pkgPath: Path, pkgName: List[String], frameworkImports: List[L#Import], @@ -73,7 +73,7 @@ case class WritePackageObject[L <: LA](dtoPackagePath: Path, protocolImports: List[L#Import], packageObjectContents: List[L#ValueDefinition], extraTypes: List[L#Statement]) - extends ScalaTerm[L, WriteTree] + extends ScalaTerm[L, Option[WriteTree]] case class WriteProtocolDefinition[L <: LA](outputPath: Path, pkgName: List[String], definitions: List[String], diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala index c254f42d0b..2efaa1c861 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala @@ -62,7 +62,7 @@ class ScalaTerms[L <: LA, F[_]](implicit I: InjectK[ScalaTerm[L, ?], F]) { pkgName: List[String], frameworkImports: List[L#Import], jsonImports: List[L#Import], - customImports: List[L#Import]): Free[F, WriteTree] = + customImports: List[L#Import]): Free[F, Option[WriteTree]] = Free.inject[ScalaTerm[L, ?], F](RenderImplicits(pkgPath, pkgName, frameworkImports, jsonImports, customImports)) def renderFrameworkImplicits(pkgPath: Path, pkgName: List[String], @@ -78,7 +78,7 @@ class ScalaTerms[L <: LA, F[_]](implicit I: InjectK[ScalaTerm[L, ?], F]) { packageObjectImports: List[L#Import], protocolImports: List[L#Import], packageObjectContents: List[L#ValueDefinition], - extraTypes: List[L#Statement]): Free[F, WriteTree] = + extraTypes: List[L#Statement]): Free[F, Option[WriteTree]] = Free.inject[ScalaTerm[L, ?], F]( WritePackageObject(dtoPackagePath, dtoComponents, customImports, packageObjectImports, protocolImports, packageObjectContents, extraTypes) ) From d91809d065e05611d79d131e8ee23a4696856d4a Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Fri, 1 Mar 2019 09:07:35 -0800 Subject: [PATCH 25/86] Add stub Dropwizard and AsyncHttpClient generators This is enough to get protocol objects actually writing to files when in client generation mode. No client or server generation yet though. --- build.sbt | 2 +- .../main/scala/com/twilio/guardrail/CLI.scala | 1 + .../Java/AsyncHttpClientClientGenerator.scala | 47 ++++++++ .../generators/Java/Dropwizard.scala | 36 +++++++ .../generators/Java/DropwizardGenerator.scala | 100 ++++++++++++++++++ 5 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala create mode 100644 modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/Dropwizard.scala create mode 100644 modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala diff --git a/build.sbt b/build.sbt index f5d42297de..77686e3615 100644 --- a/build.sbt +++ b/build.sbt @@ -50,7 +50,7 @@ val exampleJavaArgs: List[List[String]] = exampleCases acc ++ (for { kind <- List("client", "server") frameworkPair <- List( - ("akka-http", "akkaHttp"), + ("dropwizard", "dropwizard"), ) (frameworkName, frameworkPackage) = frameworkPair tracingFlag = if (tracing) Option("--tracing") else Option.empty[String] diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala index 45631aac5d..72ed4a0a70 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala @@ -164,6 +164,7 @@ object CLI extends CLICommon { val javaInterpreter = CoreTermInterp[JavaLanguage]( "akka-http", { case "akka-http" => Java.AkkaHttp + case "dropwizard" => Java.Dropwizard }, { str => import com.github.javaparser.JavaParser import scala.collection.JavaConverters._ diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala new file mode 100644 index 0000000000..da42ebf4c2 --- /dev/null +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -0,0 +1,47 @@ +package com.twilio.guardrail.generators.Java + +import cats.data.NonEmptyList +import cats.~> +import com.github.javaparser.JavaParser +import com.github.javaparser.ast.Modifier +import com.github.javaparser.ast.body.{ClassOrInterfaceDeclaration, MethodDeclaration} +import com.twilio.guardrail.languages.JavaLanguage +import com.twilio.guardrail.protocol.terms.client._ +import com.twilio.guardrail.terms.RouteMeta +import com.twilio.guardrail.{RenderedClientOperation, StaticDefns, Target} +import java.util +import scala.util.Random + +object AsyncHttpClientClientGenerator { + object ClientTermInterp extends (ClientTerm[JavaLanguage, ?] ~> Target) { + def apply[T](term: ClientTerm[JavaLanguage, T]): Target[T] = term match { + case GenerateClientOperation(_, _ @RouteMeta(pathStr, httpMethod, operation), methodName, tracing, parameters, responses) => + val dummyMethod = new MethodDeclaration(util.EnumSet.of(Modifier.PUBLIC), JavaParser.parseType("void"), new Random().alphanumeric.take(20).mkString) + Target.pure(RenderedClientOperation[JavaLanguage](dummyMethod, List.empty)) + + case GetImports(tracing) => + Target.pure(List.empty) + + case GetExtraImports(tracing) => + Target.pure(List.empty) + + case ClientClsArgs(tracingName, serverUrls, tracing) => + Target.pure(List.empty) + + case GenerateResponseDefinitions(operationId, responses, protocolElems) => + Target.pure(List.empty) + + case BuildStaticDefns(clientName, tracingName, serverUrls, ctorArgs, tracing) => + Target.pure( + StaticDefns[JavaLanguage]( + className = clientName, + extraImports = List.empty, + definitions = List.empty + ) + ) + + case BuildClient(clientName, tracingName, serverUrls, basePath, ctorArgs, clientCalls, supportDefinitions, tracing) => + Target.pure(NonEmptyList(Right(new ClassOrInterfaceDeclaration(util.EnumSet.of(Modifier.PUBLIC), false, s"${clientName}Client")), Nil)) + } + } +} diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/Dropwizard.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/Dropwizard.scala new file mode 100644 index 0000000000..0b09ac7e97 --- /dev/null +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/Dropwizard.scala @@ -0,0 +1,36 @@ +package com.twilio.guardrail.generators.Java + +import cats.~> +import com.twilio.guardrail.generators.Java.AsyncHttpClientClientGenerator.ClientTermInterp +import com.twilio.guardrail.generators.Java.DropwizardGenerator.FrameworkInterp +import com.twilio.guardrail.generators.Java.JacksonGenerator._ +import com.twilio.guardrail.generators.JavaGenerator.JavaInterp +import com.twilio.guardrail.generators.SwaggerGenerator +import com.twilio.guardrail.languages.JavaLanguage +import com.twilio.guardrail.protocol.terms.server.ServerTerm +import com.twilio.guardrail.{ClientServerTerms, CodegenApplication, DefinitionPM, DefinitionPME, DefinitionPMEA, DefinitionPMEAP, FrameworkC, FrameworkCS, FrameworkCSF, ModelInterpreters, Parser, Target} + +object Dropwizard extends (CodegenApplication[JavaLanguage, ?] ~> Target) { + object ServerTermInterp extends (ServerTerm[JavaLanguage, ?] ~> Target) { + def apply[T](term: ServerTerm[JavaLanguage, T]): Target[T] = Target.raiseError(s"interpServer: ${term.toString()}") + } + + val interpDefinitionPM: DefinitionPM[JavaLanguage, ?] ~> Target = ProtocolSupportTermInterp or ModelProtocolTermInterp + val interpDefinitionPME: DefinitionPME[JavaLanguage, ?] ~> Target = EnumProtocolTermInterp or interpDefinitionPM + val interpDefinitionPMEA: DefinitionPMEA[JavaLanguage, ?] ~> Target = ArrayProtocolTermInterp or interpDefinitionPME + val interpDefinitionPMEAP: DefinitionPMEAP[JavaLanguage, ?] ~> Target = PolyProtocolTermInterp or interpDefinitionPMEA + + val interpModel: ModelInterpreters[JavaLanguage, ?] ~> Target = interpDefinitionPMEAP + + val interpFrameworkC: FrameworkC[JavaLanguage, ?] ~> Target = ClientTermInterp or interpModel + val interpFrameworkCS: FrameworkCS[JavaLanguage, ?] ~> Target = ServerTermInterp or interpFrameworkC + val interpFrameworkCSF: FrameworkCSF[JavaLanguage, ?] ~> Target = FrameworkInterp or interpFrameworkCS + + val interpFramework: ClientServerTerms[JavaLanguage, ?] ~> Target = interpFrameworkCSF + + val parser: Parser[JavaLanguage, ?] ~> Target = SwaggerGenerator[JavaLanguage] or interpFramework + + val codegenApplication: CodegenApplication[JavaLanguage, ?] ~> Target = JavaInterp or parser + + def apply[T](x: CodegenApplication[JavaLanguage, T]): Target[T] = codegenApplication.apply(x) +} diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala new file mode 100644 index 0000000000..e90caadfa0 --- /dev/null +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala @@ -0,0 +1,100 @@ +package com.twilio.guardrail.generators.Java +import cats.~> +import com.github.javaparser.ast.expr.Name +import com.twilio.guardrail.Target +import com.twilio.guardrail.generators.syntax.Java._ +import com.twilio.guardrail.languages.JavaLanguage +import com.twilio.guardrail.terms.framework._ + +object DropwizardGenerator { + object FrameworkInterp extends (FrameworkTerm[JavaLanguage, ?] ~> Target) { + def apply[T](term: FrameworkTerm[JavaLanguage, T]): Target[T] = term match { + case FileType(format) => safeParseType(format.getOrElse("org.asynchttpclient.request.body.multipart.FilePart")) + case ObjectType(format) => safeParseType("com.fasterxml.jackson.databind.JsonNode") + + case GetFrameworkImports(tracing) => + Target.pure(List.empty) + + case GetFrameworkImplicits() => + Target.pure(None) + + case LookupStatusCode(key) => + def parseStatusCode(code: Int, termName: String): Target[(Int, Name)] = + safeParseName(termName).map(name => (code, name)) + + key match { + case "100" => parseStatusCode(100, "Continue") + case "101" => parseStatusCode(101, "SwitchingProtocols") + case "102" => parseStatusCode(102, "Processing") + + case "200" => parseStatusCode(200, "Ok") + case "201" => parseStatusCode(201, "Created") + case "202" => parseStatusCode(202, "Accepted") + case "203" => parseStatusCode(203, "NonAuthoritativeInformation") + case "204" => parseStatusCode(204, "NoContent") + case "205" => parseStatusCode(205, "ResetContent") + case "206" => parseStatusCode(206, "PartialContent") + case "207" => parseStatusCode(207, "MultiStatus") + case "208" => parseStatusCode(208, "AlreadyReported") + case "226" => parseStatusCode(226, "IMUsed") + + case "300" => parseStatusCode(300, "MultipleChoices") + case "301" => parseStatusCode(301, "MovedPermanently") + case "302" => parseStatusCode(302, "Found") + case "303" => parseStatusCode(303, "SeeOther") + case "304" => parseStatusCode(304, "NotModified") + case "305" => parseStatusCode(305, "UseProxy") + case "307" => parseStatusCode(307, "TemporaryRedirect") + case "308" => parseStatusCode(308, "PermanentRedirect") + + case "400" => parseStatusCode(400, "BadRequest") + case "401" => parseStatusCode(401, "Unauthorized") + case "402" => parseStatusCode(402, "PaymentRequired") + case "403" => parseStatusCode(403, "Forbidden") + case "404" => parseStatusCode(404, "NotFound") + case "405" => parseStatusCode(405, "MethodNotAllowed") + case "406" => parseStatusCode(406, "NotAcceptable") + case "407" => parseStatusCode(407, "ProxyAuthenticationRequired") + case "408" => parseStatusCode(408, "RequestTimeout") + case "409" => parseStatusCode(409, "Conflict") + case "410" => parseStatusCode(410, "Gone") + case "411" => parseStatusCode(411, "LengthRequired") + case "412" => parseStatusCode(412, "PreconditionFailed") + case "413" => parseStatusCode(413, "RequestEntityTooLarge") + case "414" => parseStatusCode(414, "RequestUriTooLong") + case "415" => parseStatusCode(415, "UnsupportedMediaType") + case "416" => parseStatusCode(416, "RequestedRangeNotSatisfiable") + case "417" => parseStatusCode(417, "ExpectationFailed") + case "418" => parseStatusCode(418, "ImATeapot") + case "420" => parseStatusCode(420, "EnhanceYourCalm") + case "422" => parseStatusCode(422, "UnprocessableEntity") + case "423" => parseStatusCode(423, "Locked") + case "424" => parseStatusCode(424, "FailedDependency") + case "425" => parseStatusCode(425, "UnorderedCollection") + case "426" => parseStatusCode(426, "UpgradeRequired") + case "428" => parseStatusCode(428, "PreconditionRequired") + case "429" => parseStatusCode(429, "TooManyRequests") + case "431" => parseStatusCode(431, "RequestHeaderFieldsTooLarge") + case "449" => parseStatusCode(449, "RetryWith") + case "450" => parseStatusCode(450, "BlockedByParentalControls") + case "451" => parseStatusCode(451, "UnavailableForLegalReasons") + + case "500" => parseStatusCode(500, "InternalServerError") + case "501" => parseStatusCode(501, "NotImplemented") + case "502" => parseStatusCode(502, "BadGateway") + case "503" => parseStatusCode(503, "ServiceUnavailable") + case "504" => parseStatusCode(504, "GatewayTimeout") + case "505" => parseStatusCode(505, "HTTPVersionNotSupported") + case "506" => parseStatusCode(506, "VariantAlsoNegotiates") + case "507" => parseStatusCode(507, "InsufficientStorage") + case "508" => parseStatusCode(508, "LoopDetected") + case "509" => parseStatusCode(509, "BandwidthLimitExceeded") + case "510" => parseStatusCode(510, "NotExtended") + case "511" => parseStatusCode(511, "NetworkAuthenticationRequired") + case "598" => parseStatusCode(598, "NetworkReadTimeout") + case "599" => parseStatusCode(599, "NetworkConnectTimeout") + case _ => Target.raiseError(s"Unknown HTTP type: ${key}") + } + } + } +} From 2a70ee7d2dee39a212f97fddd165fcbbc5151067 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Sat, 2 Mar 2019 06:44:46 -0800 Subject: [PATCH 26/86] Add GetFrameworkDefintions and RenderFrameworkDefinitions This allows the Java generator (and any other interested parties) to render auxiliary class definitions. --- .../scala/com/twilio/guardrail/Common.scala | 6 +++++- .../generators/AkkaHttpGenerator.scala | 3 +++ .../generators/EndpointsGenerator.scala | 4 ++++ .../generators/Http4sGenerator.scala | 3 +++ .../guardrail/generators/ScalaGenerator.scala | 19 +++++++++++++++++++ .../twilio/guardrail/terms/ScalaTerm.scala | 6 ++++++ .../twilio/guardrail/terms/ScalaTerms.scala | 6 ++++++ .../terms/framework/FrameworkTerm.scala | 1 + .../terms/framework/FrameworkTerms.scala | 2 ++ 9 files changed, 49 insertions(+), 1 deletion(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala index 7b45a0a031..5d4e05f2a9 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala @@ -116,19 +116,23 @@ object Common { frameworkImports <- getFrameworkImports(context.tracing) frameworkImplicits <- getFrameworkImplicits() frameworkImplicitName = frameworkImplicits.map(_._1) + frameworkDefinitions <- getFrameworkDefinitions() files <- (clients.traverse(writeClient(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, _)), servers.traverse(writeServer(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, _))).mapN(_ ++ _) implicits <- renderImplicits(pkgPath, pkgName, frameworkImports, protocolImports, customImports) frameworkImplicitsFile <- frameworkImplicits.fold(Free.pure[F, Option[WriteTree]](None))({ case (name, defn) => renderFrameworkImplicits(pkgPath, pkgName, frameworkImports, protocolImports, defn, name).map(Option.apply) }) + + frameworkDefinitionsFiles <- frameworkDefinitions.traverse({ case (name, defn) => renderFrameworkDefinitions(pkgPath, pkgName, frameworkImports, defn, name) }) } yield ( protocolDefinitions ++ packageObject.toList ++ files ++ implicits.toList ++ - frameworkImplicitsFile.toList + frameworkImplicitsFile.toList ++ + frameworkDefinitionsFiles ).toList } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpGenerator.scala index 5f20863623..66d56e261c 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpGenerator.scala @@ -168,6 +168,9 @@ object AkkaHttpGenerator { Target.pure(Some((q"AkkaHttpImplicits", defn))) } + case GetFrameworkDefinitions() => + Target.pure(List.empty) + case LookupStatusCode(key) => key match { case "100" => Target.pure((100, q"Continue")) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsGenerator.scala index 9a99371de9..c370a79880 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsGenerator.scala @@ -116,6 +116,10 @@ object EndpointsGenerator { } } """))) + + case GetFrameworkDefinitions() => + Target.pure(List.empty) + case LookupStatusCode(key) => key match { case "100" => Target.pure((100, q"Continue")) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sGenerator.scala index 7203aff8b8..3fc6071170 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sGenerator.scala @@ -70,6 +70,9 @@ object Http4sGenerator { Some((q"Http4sImplicits", defn)) }) + case GetFrameworkDefinitions() => + Target.pure(List.empty) + case LookupStatusCode(key) => key match { case "100" => Target.pure((100, q"Continue")) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala index 7a33e0ca2b..effce14044 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala @@ -215,6 +215,25 @@ object ScalaGenerator { """ Target.pure(WriteTree(pkgPath.resolve(s"${frameworkImplicitName.value}.scala"), frameworkImplicitsFile.syntax.getBytes(StandardCharsets.UTF_8))) + case RenderFrameworkDefinitions(pkgPath, pkgName, frameworkImports, frameworkDefinitions, frameworkDefinitionsName) => + val pkg: Term.Ref = + pkgName.map(Term.Name.apply _).reduceLeft(Term.Select.apply _) + val implicitsRef: Term.Ref = + (pkgName.map(Term.Name.apply _) ++ List(q"Implicits")).foldLeft[Term.Ref](q"_root_")(Term.Select.apply _) + val frameworkDefinitionsFile = source""" + package ${pkg} + + ..${frameworkImports} + + import cats.implicits._ + import cats.data.EitherT + + import ${implicitsRef}._ + + ${frameworkDefinitions} + """ + Target.pure(WriteTree(pkgPath.resolve(s"${frameworkDefinitionsName.value}.scala"), frameworkDefinitionsFile.syntax.getBytes(StandardCharsets.UTF_8))) + case WritePackageObject(dtoPackagePath, dtoComponents, customImports, packageObjectImports, protocolImports, packageObjectContents, extraTypes) => val dtoHead :: dtoRest = dtoComponents val dtoPkg = dtoRest.init diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala index 80efac552f..af83e17919 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala @@ -66,6 +66,12 @@ case class RenderFrameworkImplicits[L <: LA](pkgPath: Path, frameworkImplicits: L#ObjectDefinition, frameworkImplicitName: L#TermName) extends ScalaTerm[L, WriteTree] +case class RenderFrameworkDefinitions[L <: LA](pkgPath: Path, + pkgName: List[String], + frameworkImports: List[L#Import], + frameworkDefinitions: L#ClassDefinition, + frameworkDefinitionsName: L#TermName) + extends ScalaTerm[L, WriteTree] case class WritePackageObject[L <: LA](dtoPackagePath: Path, dtoComponents: List[String], customImports: List[L#Import], diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala index 2efaa1c861..df95870de4 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala @@ -71,6 +71,12 @@ class ScalaTerms[L <: LA, F[_]](implicit I: InjectK[ScalaTerm[L, ?], F]) { frameworkImplicits: L#ObjectDefinition, frameworkImplicitName: L#TermName): Free[F, WriteTree] = Free.inject[ScalaTerm[L, ?], F](RenderFrameworkImplicits(pkgPath, pkgName, frameworkImports, jsonImports, frameworkImplicits, frameworkImplicitName)) + def renderFrameworkDefinitions(pkgPath: Path, + pkgName: List[String], + frameworkImports: List[L#Import], + frameworkDefinitions: L#ClassDefinition, + frameworkDefinitionsName: L#TermName): Free[F, WriteTree] = + Free.inject[ScalaTerm[L, ?], F](RenderFrameworkDefinitions(pkgPath, pkgName, frameworkImports, frameworkDefinitions, frameworkDefinitionsName)) def writePackageObject(dtoPackagePath: Path, dtoComponents: List[String], diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/framework/FrameworkTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/framework/FrameworkTerm.scala index 225afaf3c5..628fb07af5 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/framework/FrameworkTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/framework/FrameworkTerm.scala @@ -6,6 +6,7 @@ import com.twilio.guardrail.languages.LA sealed trait FrameworkTerm[L <: LA, T] case class GetFrameworkImports[L <: LA](tracing: Boolean) extends FrameworkTerm[L, List[L#Import]] case class GetFrameworkImplicits[L <: LA]() extends FrameworkTerm[L, Option[(L#TermName, L#ObjectDefinition)]] +case class GetFrameworkDefinitions[L <: LA]() extends FrameworkTerm[L, List[(L#TermName, L#ClassDefinition)]] case class LookupStatusCode[L <: LA](key: String) extends FrameworkTerm[L, (Int, L#TermName)] case class FileType[L <: LA](format: Option[String]) extends FrameworkTerm[L, L#Type] case class ObjectType[L <: LA](format: Option[String]) extends FrameworkTerm[L, L#Type] diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/framework/FrameworkTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/framework/FrameworkTerms.scala index f38ede3d24..152826b37a 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/framework/FrameworkTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/framework/FrameworkTerms.scala @@ -10,6 +10,8 @@ class FrameworkTerms[L <: LA, F[_]](implicit I: InjectK[FrameworkTerm[L, ?], F]) Free.inject[FrameworkTerm[L, ?], F](GetFrameworkImports[L](tracing)) def getFrameworkImplicits(): Free[F, Option[(L#TermName, L#ObjectDefinition)]] = Free.inject[FrameworkTerm[L, ?], F](GetFrameworkImplicits[L]()) + def getFrameworkDefinitions(): Free[F, List[(L#TermName, L#ClassDefinition)]] = + Free.inject[FrameworkTerm[L, ?], F](GetFrameworkDefinitions[L]()) def lookupStatusCode(key: String): Free[F, (Int, L#TermName)] = Free.inject[FrameworkTerm[L, ?], F](LookupStatusCode(key)) def fileType(format: Option[String]): Free[F, L#Type] = From a97963bda3f14fd9b88dc9f1f724d0fa5b1dabe4 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Sat, 2 Mar 2019 06:48:36 -0800 Subject: [PATCH 27/86] Flesh out the AsyncHttpClient generator a bit I get decent output out of this, but there's still more to do to make it actually functional. --- .../com/twilio/guardrail/SwaggerUtil.scala | 54 ++- .../Java/AsyncHttpClientClientGenerator.scala | 394 +++++++++++++++++- .../generators/Java/DropwizardGenerator.scala | 77 +++- .../guardrail/generators/JavaGenerator.scala | 25 +- .../guardrail/generators/syntax/Java.scala | 2 + 5 files changed, 532 insertions(+), 20 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala index 8e824e3193..a281701e7d 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala @@ -1,23 +1,24 @@ package com.twilio.guardrail +import cats.data.EitherT import io.swagger.v3.oas.models._ import io.swagger.v3.oas.models.PathItem._ import io.swagger.v3.oas.models.media._ import io.swagger.v3.oas.models.parameters._ import io.swagger.v3.oas.models.responses._ -import cats.{ FlatMap, Foldable } +import cats.{FlatMap, Foldable} import cats.free.Free import cats.implicits._ -import com.twilio.guardrail.terms.{ ScalaTerms, SwaggerTerms } +import com.github.javaparser.ast.expr._ +import com.twilio.guardrail.terms.{ScalaTerms, SwaggerTerms} import com.twilio.guardrail.terms.framework.FrameworkTerms -import com.twilio.guardrail.extract.{ Default, ScalaType } -import com.twilio.guardrail.generators.{ Responses, ScalaParameter } -import com.twilio.guardrail.languages.LA +import com.twilio.guardrail.extract.{Default, ScalaType} +import com.twilio.guardrail.generators.{Responses, ScalaParameter} +import com.twilio.guardrail.languages.{JavaLanguage, LA, ScalaLanguage} import com.twilio.guardrail.shims._ -import java.util.{ Map => JMap } +import java.util.{Map => JMap} import scala.language.reflectiveCalls import scala.meta._ -import com.twilio.guardrail.languages.ScalaLanguage import com.twilio.guardrail.protocol.terms.protocol.PropMeta import scala.collection.JavaConverters._ @@ -366,6 +367,45 @@ object SwaggerUtil { Resolved[L](ignoredType, None, None) } + // FIXME: this is mostly copy-paste from the scala impl; can almost certainly generic-ify it + object jpaths { + import atto._, Atto._ + + private[this] def lookupName[T](bindingName: String, + pathArgs: List[ScalaParameter[JavaLanguage]])(f: ScalaParameter[JavaLanguage] => atto.Parser[T]): atto.Parser[T] = + pathArgs + .find(_.argName.value == bindingName) + .fold[atto.Parser[T]]( + err(s"Unable to find argument ${bindingName}") + )(param => f(param)) + + private[this] val variable: atto.Parser[String] = char('{') ~> many(notChar('}')) + .map(_.mkString("")) <~ char('}') + + def generateUrlPathParams(path: String, pathArgs: List[ScalaParameter[JavaLanguage]]): Target[Expression] = { + val term: atto.Parser[Expression] = variable.flatMap { binding => + lookupName(binding, pathArgs) { param => + ok(new MethodCallExpr(new NameExpr(param.paramName.asString), "toString")) + } + } + val other: atto.Parser[String] = many1(notChar('{')).map(_.toList.mkString) + val pattern: atto.Parser[List[Either[String, Expression]]] = many(either(term, other).map(_.swap: Either[String, Expression])) + + for { + parts <- pattern + .parseOnly(path) + .either + .fold(Target.raiseError, Target.pure) + result = parts + .map({ + case Left(part) => new StringLiteralExpr(part) + case Right(term) => term + }) + .foldLeft[Expression](new MethodCallExpr(new FieldAccessExpr(new ThisExpr, "baseUrl"), "toString"))({ case (a, b) => new BinaryExpr(a, b, BinaryExpr.Operator.PLUS) }) + } yield result + } + } + object paths { import atto._, Atto._ diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index da42ebf4c2..3bcd5fdb76 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -1,26 +1,134 @@ package com.twilio.guardrail.generators.Java import cats.data.NonEmptyList +import cats.instances.list._ +import cats.syntax.traverse._ import cats.~> import com.github.javaparser.JavaParser -import com.github.javaparser.ast.Modifier -import com.github.javaparser.ast.body.{ClassOrInterfaceDeclaration, MethodDeclaration} +import com.github.javaparser.ast.{ImportDeclaration, NodeList} +import com.github.javaparser.ast.Modifier._ +import com.github.javaparser.ast.`type`.{ClassOrInterfaceType, Type, VoidType} +import com.github.javaparser.ast.body.{ClassOrInterfaceDeclaration, MethodDeclaration, Parameter, VariableDeclarator} +import com.github.javaparser.ast.expr.{MethodCallExpr, NameExpr, _} +import com.github.javaparser.ast.stmt._ +import com.twilio.guardrail.SwaggerUtil.jpaths +import com.twilio.guardrail.generators.Response +import com.twilio.guardrail.generators.syntax.Java._ import com.twilio.guardrail.languages.JavaLanguage import com.twilio.guardrail.protocol.terms.client._ import com.twilio.guardrail.terms.RouteMeta import com.twilio.guardrail.{RenderedClientOperation, StaticDefns, Target} +import java.net.URI import java.util -import scala.util.Random +import java.util.Locale object AsyncHttpClientClientGenerator { + private def completionStageType: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("CompletionStage" ) + private def optionalType: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Optional") + + private val URI_TYPE = JavaParser.parseClassOrInterfaceType("URI") + private val DEFAULT_ASYNC_HTTP_CLIENT_CONFIG_BUILDER_TYPE = JavaParser.parseClassOrInterfaceType("DefaultAsyncHttpClientConfig.Builder") + private val ASYNC_HTTP_CLIENT_TYPE = JavaParser.parseClassOrInterfaceType("AsyncHttpClient") + private val REQUEST_BUILDER_TYPE = JavaParser.parseClassOrInterfaceType("RequestBuilder") + private val RESPONSE_TYPE = JavaParser.parseClassOrInterfaceType("Response") + private val OBJECT_MAPPER_TYPE = JavaParser.parseClassOrInterfaceType("ObjectMapper") + private val BUILDER_TYPE = JavaParser.parseClassOrInterfaceType("Builder") + private val MARSHALLING_EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("MarshallingException") + private val HTTP_ERROR_TYPE = JavaParser.parseClassOrInterfaceType("HttpError") + private val EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("Exception") + object ClientTermInterp extends (ClientTerm[JavaLanguage, ?] ~> Target) { def apply[T](term: ClientTerm[JavaLanguage, T]): Target[T] = term match { - case GenerateClientOperation(_, _ @RouteMeta(pathStr, httpMethod, operation), methodName, tracing, parameters, responses) => - val dummyMethod = new MethodDeclaration(util.EnumSet.of(Modifier.PUBLIC), JavaParser.parseType("void"), new Random().alphanumeric.take(20).mkString) - Target.pure(RenderedClientOperation[JavaLanguage](dummyMethod, List.empty)) + case GenerateClientOperation(_, RouteMeta(pathStr, httpMethod, operation), methodName, tracing, parameters, responses) => + val responseParentName = s"${operation.getOperationId.capitalize}Response" + for { + responseParentType <- safeParseClassOrInterfaceType(responseParentName) + pathExpr <- jpaths.generateUrlPathParams(pathStr, parameters.pathParams) + } yield { + val method = new MethodDeclaration(util.EnumSet.of(PUBLIC), new VoidType, operation.getOperationId) + method.setType(completionStageType.setTypeArguments(responseParentType)) + + val pathParams = parameters.pathParams.map(_.param) + val qsParams = parameters.queryStringParams.map(_.param) + val formParams = parameters.formParams.map(_.param) + val headerParams = parameters.headerParams.map(_.param) + val bodyParams = parameters.bodyParams.map(_.param).toList + (pathParams ++ qsParams ++ formParams ++ headerParams ++ bodyParams).foreach(method.addParameter) + + val httpMethodCallName = s"prepare${httpMethod.toString.toLowerCase(Locale.US).capitalize}" + val httpMethodCallExpr = new MethodCallExpr(new FieldAccessExpr(new ThisExpr, "httpClient"), httpMethodCallName, new NodeList[Expression](new NameExpr("url"))) + + val requestExecuteCall = new MethodCallExpr(new NameExpr("requestBuilder"), "execute") + val requestApplyCall = new MethodCallExpr(requestExecuteCall, "thenApply", new NodeList[Expression]( + new LambdaExpr(new NodeList(new Parameter(RESPONSE_TYPE, "response")), new BlockStmt(new NodeList( + new SwitchStmt(new MethodCallExpr(new NameExpr("response"), "getStatusCode"), new NodeList( + responses.value.map(response => new SwitchEntryStmt(new IntegerLiteralExpr(response.statusCode), new NodeList(response.value match { + case None => new ReturnStmt(new ObjectCreationExpr(null, JavaParser.parseClassOrInterfaceType(s"${responseParentName}.${response.statusCodeName.asString}"), new NodeList())) + case Some((valueType, _)) => new TryStmt( + new BlockStmt(new NodeList( + new ExpressionStmt(new AssignExpr( + new VariableDeclarationExpr(new VariableDeclarator(valueType, "result"), FINAL), + new MethodCallExpr( + new FieldAccessExpr(new ThisExpr, "objectMapper"), + "readValue", + new NodeList[Expression]( + new MethodCallExpr(new NameExpr("response"), "getResponseBodyAsStream"), + new ClassExpr(valueType)) + ) + ), AssignExpr.Operator.ASSIGN)), + new IfStmt( + new BinaryExpr(new NameExpr("result"), new NullLiteralExpr, BinaryExpr.Operator.EQUALS), + new BlockStmt(new NodeList(new ThrowStmt(new ObjectCreationExpr(null, MARSHALLING_EXCEPTION_TYPE, new NodeList(new StringLiteralExpr("Failed to unmarshal response")))))), + new BlockStmt(new NodeList(new ReturnStmt(new ObjectCreationExpr(null, JavaParser.parseClassOrInterfaceType(s"${responseParentName}.${response.statusCodeName.asString}"), new NodeList[Expression](new NameExpr("result")))))) + ) + )), + new NodeList( + new CatchClause( + new Parameter(util.EnumSet.of(FINAL), MARSHALLING_EXCEPTION_TYPE, new SimpleName("e")), + new BlockStmt(new NodeList( + new ThrowStmt(new NameExpr("e")) + )) + ), + new CatchClause( + new Parameter(util.EnumSet.of(FINAL), EXCEPTION_TYPE, new SimpleName("e")), + new BlockStmt(new NodeList( + new ThrowStmt(new ObjectCreationExpr(null, MARSHALLING_EXCEPTION_TYPE, new NodeList(new MethodCallExpr(new NameExpr("e"), "getMessage"), new NameExpr("e")))) + )) + ) + ), + null + ) + }))) :+ new SwitchEntryStmt(null, new NodeList(new ThrowStmt(new ObjectCreationExpr(null, HTTP_ERROR_TYPE, new NodeList(new NameExpr("response")))))): _* + )) + )), true) + )) + + method.setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new AssignExpr(new VariableDeclarationExpr(new VariableDeclarator(STRING_TYPE, "url"), FINAL), pathExpr, AssignExpr.Operator.ASSIGN)), + new ExpressionStmt(new AssignExpr(new VariableDeclarationExpr(new VariableDeclarator(REQUEST_BUILDER_TYPE, "requestBuilder"), FINAL), httpMethodCallExpr, AssignExpr.Operator.ASSIGN)), + new ExpressionStmt(requestApplyCall) + ))) + + RenderedClientOperation[JavaLanguage](method, List.empty) + } case GetImports(tracing) => - Target.pure(List.empty) + (List( + "java.net.URI", + "java.util.Optional", + "java.util.concurrent.CompletionStage", + "java.util.function.Function", + "com.fasterxml.jackson.databind.ObjectMapper", + "com.fasterxml.jackson.datatype.jdk8.Jdk8Module", + "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", + "org.asynchttpclient.AsyncHttpClient", + "org.asynchttpclient.DefaultAsyncHttpClient", + "org.asynchttpclient.DefaultAsyncHttpClientConfig", + "org.asynchttpclient.RequestBuilder", + "org.asynchttpclient.Response" + ).map(safeParseRawImport) ++ List( + "java.util.Objects.requireNonNull" + ).map(safeParseRawStaticImport)).sequence case GetExtraImports(tracing) => Target.pure(List.empty) @@ -29,7 +137,87 @@ object AsyncHttpClientClientGenerator { Target.pure(List.empty) case GenerateResponseDefinitions(operationId, responses, protocolElems) => - Target.pure(List.empty) + val abstractClassName = s"${operationId.capitalize}Response" + val genericTypeParam = JavaParser.parseClassOrInterfaceType("T") + + val responseData = responses.value.map({ case Response(statusCodeName, valueType) => + val responseName: String = statusCodeName.asString + val responseType = JavaParser.parseClassOrInterfaceType(responseName) + val responseLambdaName = s"handle${responseName}" + + val responseInnerClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, STATIC), false, responseName); + responseInnerClass.addExtendedType(abstractClassName) + valueType.foreach({ vt => + val finalValueType: Type = vt match { + case p: ClassOrInterfaceType if p.isBoxedType => p.toUnboxedType + case other => other + } + + responseInnerClass.addField(finalValueType, "value", PRIVATE, FINAL) + + val constructor = responseInnerClass.addConstructor(PUBLIC) + constructor.addParameter(new Parameter(util.EnumSet.of(FINAL), finalValueType, new SimpleName("value"))) + constructor.setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new AssignExpr( + new FieldAccessExpr(new ThisExpr, "value"), + new NameExpr("value"), + AssignExpr.Operator.ASSIGN + )) + ))) + + val getValueMethod = responseInnerClass.addMethod("getValue", PUBLIC) + getValueMethod.setType(finalValueType) + getValueMethod.setBody(new BlockStmt(new NodeList( + new ReturnStmt(new FieldAccessExpr(new ThisExpr, "value")) + ))) + }) + + val functionType = JavaParser.parseClassOrInterfaceType("Function") + functionType.setTypeArguments(responseType, genericTypeParam) + val foldMethodParameter = new Parameter(util.EnumSet.of(FINAL), functionType, new SimpleName(responseLambdaName)) + + val foldMethodBranch = new IfStmt( + new InstanceOfExpr(new ThisExpr, responseType), + new BlockStmt(new NodeList( + new ReturnStmt(new MethodCallExpr( + new NameExpr(responseLambdaName), + "apply", + new NodeList[Expression](new CastExpr(responseType, new ThisExpr)) + )) + )), + null + ) + + (responseInnerClass, foldMethodParameter, foldMethodBranch) + }) + + val abstractResponseClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, STATIC, ABSTRACT), false, abstractClassName) + + val (innerClasses, foldMethodParameters, foldMethodIfBranches) = responseData.unzip3 + + innerClasses.foreach(abstractResponseClass.addMember) + + val foldMethod = abstractResponseClass.addMethod("fold", PUBLIC) + foldMethod.addTypeParameter("T") + foldMethod.setType("T") + foldMethodParameters.foreach(foldMethod.addParameter) + + NonEmptyList.fromList(foldMethodIfBranches).foreach({ nel => + nel.reduceLeft({ (prev, next) => + prev.setElseStmt(next) + next + }) + + nel.last.setElseStmt(new BlockStmt(new NodeList( + new ThrowStmt(new ObjectCreationExpr(null, ASSERTION_ERROR_TYPE, + new NodeList(new StringLiteralExpr("This is a bug in guardrail!"))) + ) + ))) + + foldMethod.setBody(new BlockStmt(new NodeList(nel.head))) + }) + + Target.pure(List(abstractResponseClass)) case BuildStaticDefns(clientName, tracingName, serverUrls, ctorArgs, tracing) => Target.pure( @@ -41,7 +229,195 @@ object AsyncHttpClientClientGenerator { ) case BuildClient(clientName, tracingName, serverUrls, basePath, ctorArgs, clientCalls, supportDefinitions, tracing) => - Target.pure(NonEmptyList(Right(new ClassOrInterfaceDeclaration(util.EnumSet.of(Modifier.PUBLIC), false, s"${clientName}Client")), Nil)) + val clientType = JavaParser.parseClassOrInterfaceType(clientName) + + val builderClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, STATIC), false, "Builder") + val serverUrl = serverUrls.map(_.head).map(uri => new URI(uri.toString + basePath.getOrElse(""))) + + val baseUrlField = builderClass.addField(URI_TYPE, "baseUrl", PRIVATE, FINAL) + val clientNameField = builderClass.addField(STRING_TYPE, "clientName", PRIVATE, FINAL) + + serverUrl.foreach({ serverUrl => + builderClass.addFieldWithInitializer( + URI_TYPE, "DEFAULT_BASE_URL", + new MethodCallExpr( + new NameExpr("URI"), + "create", + new NodeList[Expression](new StringLiteralExpr(serverUrl.toString)) + ), + PRIVATE, STATIC, FINAL + ) + baseUrlField.setFinal(false) + baseUrlField.getVariables.iterator().next().setInitializer(new NameExpr("DEFAULT_BASE_URL")) + }) + + tracingName.foreach({ tracingName => + builderClass.addFieldWithInitializer( + STRING_TYPE, "DEFAULT_CLIENT_NAME", + new StringLiteralExpr(tracingName), + PRIVATE, STATIC, FINAL + ) + clientNameField.setFinal(false) + clientNameField.getVariables.iterator().next().setInitializer(new NameExpr("DEFAULT_CLIENT_NAME")) + }) + + builderClass.addFieldWithInitializer( + optionalType.setTypeArguments(ASYNC_HTTP_CLIENT_TYPE), "httpClient", + new MethodCallExpr(new NameExpr("Optional"), "empty"), + PRIVATE + ) + builderClass.addFieldWithInitializer( + optionalType.setTypeArguments(OBJECT_MAPPER_TYPE), "objectMapper", + new MethodCallExpr(new NameExpr("Optional"), "empty"), + PRIVATE + ) + + val builderConstructor = builderClass.addConstructor(PUBLIC) + def createConstructorParameter(tpe: Type, name: String): Parameter = { + new Parameter(util.EnumSet.of(FINAL), tpe, new SimpleName(name)) + } + def createBuilderConstructorAssignment(name: String): Statement = { + new ExpressionStmt(new AssignExpr( + new FieldAccessExpr(new ThisExpr, name), + new MethodCallExpr("requireNonNull", new NameExpr(name)), AssignExpr.Operator.ASSIGN) + ) + } + (serverUrl, tracingName) match { + case (None, None) => + builderConstructor.setParameters(new NodeList( + createConstructorParameter(URI_TYPE, "baseUrl"), + createConstructorParameter(STRING_TYPE, "clientName") + )) + builderConstructor.setBody(new BlockStmt(new NodeList( + createBuilderConstructorAssignment("baseUrl"), + createBuilderConstructorAssignment("clientName") + ))) + + case (Some(_), None) => + builderConstructor.setParameters(new NodeList( + createConstructorParameter(STRING_TYPE, "clientName") + )) + builderConstructor.setBody(new BlockStmt(new NodeList( + createBuilderConstructorAssignment("clientName") + ))) + + case (None, Some(_)) => + builderConstructor.setParameters(new NodeList( + createConstructorParameter(URI_TYPE, "baseUrl") + )) + builderConstructor.setBody(new BlockStmt(new NodeList( + createBuilderConstructorAssignment("baseUrl") + ))) + + case (Some(_), Some(_)) => // no params + } + + def addSetter(tpe: Type, name: String, initializer: String => Expression): Unit = { + val setter = builderClass.addMethod(s"with${name.capitalize}", PUBLIC) + setter.setType(BUILDER_TYPE) + setter.addParameter(new Parameter(util.EnumSet.of(FINAL), tpe, new SimpleName(name))) + setter.setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new AssignExpr(new FieldAccessExpr(new ThisExpr, name), initializer(name), AssignExpr.Operator.ASSIGN)), + new ReturnStmt(new ThisExpr) + ))) + } + val nonNullInitializer: String => Expression = name => new MethodCallExpr(null, "requireNonNull", new NodeList[Expression](new NameExpr(name))) + def optionalInitializer(valueArg: String => Expression): String => Expression = name => new MethodCallExpr(new NameExpr("Optional"), "of", new NodeList[Expression](valueArg(name))) + if (serverUrl.isDefined) { + addSetter(URI_TYPE, "baseUrl", nonNullInitializer) + } + addSetter(STRING_TYPE, "clientName", nonNullInitializer) + addSetter(ASYNC_HTTP_CLIENT_TYPE, "httpClient", optionalInitializer(new NameExpr(_))) + addSetter(OBJECT_MAPPER_TYPE, "objectMapper", + optionalInitializer(name => new MethodCallExpr("configureObjectMapper", new NameExpr(name)))) + + val buildMethod = builderClass.addMethod("build", PUBLIC) + buildMethod.setType(clientType) + buildMethod.setBody(new BlockStmt(new NodeList( + new ReturnStmt(new ObjectCreationExpr(null, clientType, new NodeList(new ThisExpr))) + ))) + + def addInternalGetter(tpe: Type, name: String, getterCall: Expression): Unit = { + val getter = builderClass.addMethod(s"get${name.capitalize}", PRIVATE) + getter.setType(tpe) + getter.setBody(new BlockStmt(new NodeList( + new ReturnStmt(new MethodCallExpr( + new FieldAccessExpr(new ThisExpr, name), + "orElseGet", + new NodeList[Expression]( + new LambdaExpr(new NodeList(), new ExpressionStmt(getterCall), true) + ) + )) + ))) + } + addInternalGetter(ASYNC_HTTP_CLIENT_TYPE, "httpClient", + new MethodCallExpr("createDefaultHttpClient")) + addInternalGetter(OBJECT_MAPPER_TYPE, "objectMapper", + new MethodCallExpr("configureObjectMapper", new ObjectCreationExpr(null, OBJECT_MAPPER_TYPE, new NodeList()))) + + val createDefaultHttpClientMethod = builderClass.addMethod("createDefaultHttpClient", PRIVATE, STATIC) + createDefaultHttpClientMethod.setType(ASYNC_HTTP_CLIENT_TYPE) + createDefaultHttpClientMethod.setBody(new BlockStmt(new NodeList(new ReturnStmt({ + val configBuilder = new ObjectCreationExpr(null, DEFAULT_ASYNC_HTTP_CLIENT_CONFIG_BUILDER_TYPE, new NodeList()) + val configBuilderChain = List( + ("setMaxRequestRetry", 2), + ("setConnectTimeout", 3000), + ("setRequestTimeout", 10000), + ("setReadTimeout", 3000), + ("setMaxConnections", 1024), + ("setMaxConnectionsPerHost", 1024) + ).foldLeft[Expression](configBuilder)({ case (lastExpr, (name, arg)) => + new MethodCallExpr(lastExpr, name, new NodeList[Expression](new IntegerLiteralExpr(arg))) + }) + new MethodCallExpr(configBuilderChain, "build", new NodeList[Expression]()) + })))) + + val configureObjectMapperMethod = builderClass.addMethod("configureObjectMapper", PRIVATE, STATIC) + configureObjectMapperMethod.setType(OBJECT_MAPPER_TYPE) + configureObjectMapperMethod.addParameter(new Parameter(util.EnumSet.of(FINAL), OBJECT_MAPPER_TYPE, new SimpleName("objectMapper"))) + configureObjectMapperMethod.setBody(new BlockStmt(new NodeList( + List("JavaTimeModule", "Jdk8Module").map(name => + new ExpressionStmt(new MethodCallExpr( + new NameExpr("objectMapper"), + "registerModule", + new NodeList[Expression](new ObjectCreationExpr(null, JavaParser.parseClassOrInterfaceType(name), new NodeList())) + )) + ) :+ + new ReturnStmt(new NameExpr("objectMapper")): _* + ))) + + val clientClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, clientName) + + List( + (URI_TYPE, "baseUrl"), + (STRING_TYPE, "clientName"), + (ASYNC_HTTP_CLIENT_TYPE, "httpClient"), + (OBJECT_MAPPER_TYPE, "objectMapper") + ).foreach({ case (tpe, name) => clientClass.addField(tpe, name, PRIVATE, FINAL) }) + + val constructor = clientClass.addConstructor(PRIVATE) + constructor.addParameter(new Parameter(util.EnumSet.of(FINAL), BUILDER_TYPE, new SimpleName("builder"))) + def newFieldAccessExpr(scope: Expression, name: String): Expression = new FieldAccessExpr(scope, name) + def newMethodCallExpr(scope: Expression, name: String): Expression = new MethodCallExpr(scope, s"get${name.capitalize}") + constructor.setBody(new BlockStmt(new NodeList( + List[(String, (Expression, String) => Expression)]( + ("baseUrl", newFieldAccessExpr), + ("clientName", newFieldAccessExpr), + ("httpClient", newMethodCallExpr), + ("objectMapper", newMethodCallExpr) + ).map({ case (name, value) => + new ExpressionStmt(new AssignExpr( + new FieldAccessExpr(new ThisExpr, name), + value(new NameExpr("builder"), name), + AssignExpr.Operator.ASSIGN + )) + }): _* + ))) + + clientClass.addMember(builderClass) + clientCalls.foreach(clientClass.addMember) + + Target.pure(NonEmptyList(Right(clientClass), Nil)) } } } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala index e90caadfa0..a5817a4b08 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala @@ -1,23 +1,96 @@ package com.twilio.guardrail.generators.Java + +import cats.instances.list._ +import cats.syntax.traverse._ import cats.~> -import com.github.javaparser.ast.expr.Name +import com.github.javaparser.JavaParser +import com.github.javaparser.ast.Modifier.{ABSTRACT, FINAL, PRIVATE, PUBLIC} +import com.github.javaparser.ast.NodeList +import com.github.javaparser.ast.body.{ClassOrInterfaceDeclaration, Parameter} +import com.github.javaparser.ast.expr._ +import com.github.javaparser.ast.stmt.{BlockStmt, ExpressionStmt, ReturnStmt} import com.twilio.guardrail.Target import com.twilio.guardrail.generators.syntax.Java._ import com.twilio.guardrail.languages.JavaLanguage import com.twilio.guardrail.terms.framework._ +import java.util object DropwizardGenerator { + private val RESPONSE_TYPE = JavaParser.parseClassOrInterfaceType("Response") + object FrameworkInterp extends (FrameworkTerm[JavaLanguage, ?] ~> Target) { def apply[T](term: FrameworkTerm[JavaLanguage, T]): Target[T] = term match { case FileType(format) => safeParseType(format.getOrElse("org.asynchttpclient.request.body.multipart.FilePart")) case ObjectType(format) => safeParseType("com.fasterxml.jackson.databind.JsonNode") case GetFrameworkImports(tracing) => - Target.pure(List.empty) + List( + "org.asynchttpclient.Response" + ).map(safeParseRawImport).sequence case GetFrameworkImplicits() => Target.pure(None) + case GetFrameworkDefinitions() => + def addStdConstructors(cls: ClassOrInterfaceDeclaration): Unit = { + val msgConstructor = cls.addConstructor(PUBLIC) + msgConstructor.addParameter(new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("message"))) + msgConstructor.setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new MethodCallExpr("super", new NameExpr("message"))) + ))) + + val msgCauseConstructor = cls.addConstructor(PUBLIC) + msgCauseConstructor.setParameters(new NodeList( + new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("message")), + new Parameter(util.EnumSet.of(FINAL), THROWABLE_TYPE, new SimpleName("cause")) + )) + msgCauseConstructor.setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new MethodCallExpr("super", new NameExpr("message"), new NameExpr("cause"))) + ))) + } + + val clientExceptionClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, ABSTRACT), false, "ClientException") + clientExceptionClass.addExtendedType("RuntimeException") + addStdConstructors(clientExceptionClass) + + val marshallingExceptionClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, "MarshallingException") + marshallingExceptionClass.addExtendedType("ClientException") + addStdConstructors(marshallingExceptionClass) + + val httpErrorClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, "HttpError") + httpErrorClass.addExtendedType("ClientException") + httpErrorClass.addField(RESPONSE_TYPE, "response", PRIVATE, FINAL) + + val responseConstructor = httpErrorClass.addConstructor(PUBLIC) + responseConstructor.addParameter(new Parameter(util.EnumSet.of(FINAL), RESPONSE_TYPE, new SimpleName("response"))) + responseConstructor.setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new MethodCallExpr( + "super", + new BinaryExpr( + new StringLiteralExpr("HTTP server responded with status "), + new MethodCallExpr(new NameExpr("response"), "getStatusCode"), + BinaryExpr.Operator.PLUS + ) + )), + new ExpressionStmt(new AssignExpr( + new FieldAccessExpr(new ThisExpr, "response"), + new NameExpr("response"), + AssignExpr.Operator.ASSIGN + )) + ))) + + val getResponseMethod = httpErrorClass.addMethod("getResponse", PUBLIC) + getResponseMethod.setType(RESPONSE_TYPE) + getResponseMethod.setBody(new BlockStmt(new NodeList( + new ReturnStmt(new FieldAccessExpr(new ThisExpr, "response")) + ))) + + Target.pure(List( + (new Name("ClientException"), clientExceptionClass), + (new Name("MarshallingException"), marshallingExceptionClass), + (new Name("HttpError"), httpErrorClass) + )) + case LookupStatusCode(key) => def parseStatusCode(code: Int, termName: String): Target[(Int, Name)] = safeParseName(termName).map(name => (code, name)) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala index 28c4c3fccd..a9ca0e35e9 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala @@ -141,6 +141,26 @@ object JavaGenerator { case RenderFrameworkImplicits(pkgPath, pkgName, frameworkImports, jsonImports, frameworkImplicits, frameworkImplicitName) => Target.raiseError("Java does not support Framework Implicits") + case RenderFrameworkDefinitions(pkgPath, pkgName, frameworkImports, frameworkDefinitions, frameworkDefinitionsName) => + for { + pkgDecl <- buildPkgDecl(pkgName) + //implicitsImport <- safeParseName((pkgName ++ List("Implicits", "*")).mkString(".")).map(name => new ImportDeclaration(name, false, true)) + //frameworkImplicitsImport <- safeParseName((pkgName ++ List(frameworkImplicitName.getIdentifier, "*")).mkString(".")) + // .map(name => new ImportDeclaration(name, false, true)) + //dtoComponentsImport <- safeParseName((dtoComponents :+ "*").mkString(".")).map(name => new ImportDeclaration(name, false, true)) + } yield { + val cu = new CompilationUnit() + cu.setPackageDeclaration(pkgDecl) + frameworkImports.map(cu.addImport) + //cu.addImport(implicitsImport) + //cu.addImport(frameworkImplicitsImport) + cu.addType(frameworkDefinitions) + WriteTree( + resolveFile(pkgPath)(List(s"${frameworkDefinitionsName.asString}.java")), + cu.toString(printer).getBytes(StandardCharsets.UTF_8) + ) + } + case WritePackageObject(dtoPackagePath, dtoComponents, customImports, packageObjectImports, protocolImports, packageObjectContents, extraTypes) => Target.pure(None) @@ -210,6 +230,7 @@ object JavaGenerator { case RandomType(_, _) => Target.pure((List.empty, List.empty)) } + case WriteClient(pkgPath, pkgName, customImports, @@ -221,7 +242,7 @@ object JavaGenerator { //implicitsImport <- safeParseName((pkgName ++ List("Implicits", "*")).mkString(".")).map(name => new ImportDeclaration(name, false, true)) //frameworkImplicitsImport <- safeParseName((pkgName ++ List(frameworkImplicitName.getIdentifier, "*")).mkString(".")) // .map(name => new ImportDeclaration(name, false, true)) - //dtoComponentsImport <- safeParseName((dtoComponents :+ "*").mkString(".")).map(name => new ImportDeclaration(name, false, true)) + dtoComponentsImport <- safeParseRawImport((dtoComponents :+ "*").mkString(".")) } yield { val cu = new CompilationUnit() cu.setPackageDeclaration(pkgDecl) @@ -229,7 +250,7 @@ object JavaGenerator { customImports.map(cu.addImport) //cu.addImport(implicitsImport) //cu.addImport(frameworkImplicitsImport) - cu.addImport((dtoComponents :+ "*").mkString(".")) //dtoComponentsImport) + cu.addImport(dtoComponentsImport) val clientCopy = client.head.merge.clone() // FIXME: WriteClient needs to be altered to return `NonEmptyList[WriteTree]` to accommodate Java not being able to put multiple classes in the same file. Scala just jams them all together, but this could be improved over there as well. staticDefns.definitions.foreach(clientCopy.addMember) responseDefinitions.foreach(clientCopy.addMember) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index d010ad6b24..ce50036d5b 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -36,6 +36,8 @@ object Java { def safeParseRawStaticImport(s: String): Target[ImportDeclaration] = safeParse("safeParseStaticImport")(JavaParser.parseImport, s"import static ${s};") val STRING_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("String") + val THROWABLE_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Throwable") + val ASSERTION_ERROR_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("AssertionError") val printer: PrettyPrinterConfiguration = new PrettyPrinterConfiguration() .setColumnAlignFirstMethodChain(true) From f99301c9e6a10c8d4caa68e54c364c5c5ee35582 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Mon, 4 Mar 2019 17:36:18 -0800 Subject: [PATCH 28/86] Fix ExtractTypeName in JavaGenerator --- .../Java/AsyncHttpClientClientGenerator.scala | 2 +- .../guardrail/generators/JavaGenerator.scala | 21 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index 3bcd5fdb76..ae15531743 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -73,7 +73,7 @@ object AsyncHttpClientClientGenerator { "readValue", new NodeList[Expression]( new MethodCallExpr(new NameExpr("response"), "getResponseBodyAsStream"), - new ClassExpr(valueType)) + new ClassExpr(valueType) ) ), AssignExpr.Operator.ASSIGN)), new IfStmt( diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala index a9ca0e35e9..fede650e3a 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala @@ -26,14 +26,6 @@ object JavaGenerator { case other => Target.raiseError(s"Need expression to call '${name}' but got a ${other.getClass.getName} instead") } - def resolveReferenceTypeName(tpe: Type): Target[String] = tpe match { - case a: AstArrayType if a.getComponentType.isPrimitiveType => resolveReferenceTypeName(new AstArrayType(a.getComponentType.asPrimitiveType().toBoxedType)) - case a: AstArrayType => Target.pure(a.asString) - case ci: ClassOrInterfaceType => Target.pure(ci.getNameAsString) - case p: PrimitiveType => Target.pure(p.toBoxedType.getNameAsString) - case _: VoidType => Target.pure("Void") - } - def apply[T](term: ScalaTerm[JavaLanguage, T]): Target[T] = term match { case LitString(value) => Target.pure(new StringLiteralExpr(value)) case LitFloat(value) => Target.pure(new DoubleLiteralExpr(value)) @@ -103,7 +95,18 @@ object JavaGenerator { Target.pure(a.equals(b)) case ExtractTypeName(tpe) => - safeParseName(tpe.asString).map(Option.apply) + def extractTypeName(tpe: Type): Target[Name] = tpe match { + case a: AstArrayType if a.getComponentType.isPrimitiveType => extractTypeName(new AstArrayType(a.getComponentType.asPrimitiveType().toBoxedType)) + case a: AstArrayType => safeParseName(a.asString) + case ci: ClassOrInterfaceType => safeParseName(ci.getNameAsString) + case p: PrimitiveType => safeParseName(p.toBoxedType.getNameAsString) + case _: VoidType => safeParseName("Void") + case _ => + println(s"WARN: ExtractTypeName: unhandled type ${tpe.getClass.getName}") + safeParseName("Void") + } + + extractTypeName(tpe).map(Option.apply) case ExtractTermName(term) => Target.pure(term.asString) From 4477bb8816ca93174615e8328076b9601f3bd3bd Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Mon, 4 Mar 2019 17:40:59 -0800 Subject: [PATCH 29/86] Allow WriteServer to return list of WriteTree --- .../codegen/src/main/scala/com/twilio/guardrail/Common.scala | 2 +- .../com/twilio/guardrail/generators/ScalaGenerator.scala | 4 ++-- .../src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala | 2 +- .../main/scala/com/twilio/guardrail/terms/ScalaTerms.scala | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala index 5d4e05f2a9..30a0ea1c57 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala @@ -119,7 +119,7 @@ object Common { frameworkDefinitions <- getFrameworkDefinitions() files <- (clients.traverse(writeClient(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, _)), - servers.traverse(writeServer(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, _))).mapN(_ ++ _) + servers.flatTraverse(writeServer(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, _))).mapN(_ ++ _) implicits <- renderImplicits(pkgPath, pkgName, frameworkImports, protocolImports, customImports) frameworkImplicitsFile <- frameworkImplicits.fold(Free.pure[F, Option[WriteTree]](None))({ case (name, defn) => renderFrameworkImplicits(pkgPath, pkgName, frameworkImports, protocolImports, defn, name).map(Option.apply) }) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala index effce14044..49c7244aff 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala @@ -349,7 +349,7 @@ object ScalaGenerator { ) case WriteServer(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, Server(pkg, extraImports, src)) => Target.pure( - WriteTree( + List(WriteTree( resolveFile(pkgPath)(pkg.toList :+ "Routes.scala"), source""" package ${buildPkgTerm((pkgName ++ pkg.toList))} @@ -360,7 +360,7 @@ object ScalaGenerator { ..${customImports} ..$src """.syntax.getBytes(StandardCharsets.UTF_8) - ) + )) ) } } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala index af83e17919..a787925507 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala @@ -100,4 +100,4 @@ case class WriteServer[L <: LA](pkgPath: Path, frameworkImplicitName: Option[L#TermName], dtoComponents: List[String], server: Server[L]) - extends ScalaTerm[L, WriteTree] + extends ScalaTerm[L, List[WriteTree]] diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala index df95870de4..66f5454a91 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala @@ -107,7 +107,7 @@ class ScalaTerms[L <: LA, F[_]](implicit I: InjectK[ScalaTerm[L, ?], F]) { customImports: List[L#Import], frameworkImplicitName: Option[L#TermName], dtoComponents: List[String], - server: Server[L]): Free[F, WriteTree] = + server: Server[L]): Free[F, List[WriteTree]] = Free.inject[ScalaTerm[L, ?], F](WriteServer(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, server)) } object ScalaTerms { From d7357aa21a5bb169ca375b11e07083f3dce78564 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Thu, 7 Mar 2019 18:30:25 -0800 Subject: [PATCH 30/86] Split server's handler defn and resource defn into separate params --- .../com/twilio/guardrail/ServerGenerator.scala | 18 +++++++----------- .../guardrail/generators/JavaGenerator.scala | 3 ++- .../guardrail/generators/ScalaGenerator.scala | 5 +++-- .../protocol/terms/server/ServerTerm.scala | 4 ++-- .../protocol/terms/server/ServerTerms.scala | 4 ++-- .../src/test/scala/core/issues/Issue126.scala | 2 +- .../src/test/scala/core/issues/Issue127.scala | 2 +- .../src/test/scala/core/issues/Issue165.scala | 2 +- .../akkaHttp/AkkaHttpServerTest.scala | 4 ++-- .../generators/akkaHttp/CustomHeaderTest.scala | 2 +- .../akkaHttp/StaticParametersTest.scala | 2 +- .../akkaHttp/server/FormFieldsTest.scala | 2 +- 12 files changed, 24 insertions(+), 26 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala index 94da83999a..497d168f03 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala @@ -1,21 +1,18 @@ package com.twilio.guardrail import _root_.io.swagger.v3.oas.models._ -import cats.Id -import cats.data.NonEmptyList import cats.free.Free import cats.instances.all._ import cats.syntax.all._ -import com.twilio.guardrail.generators.{ Http4sHelper, ScalaParameter } +import com.twilio.guardrail.generators.{Http4sHelper, ScalaParameter} import com.twilio.guardrail.languages.LA -import com.twilio.guardrail.protocol.terms.server.{ ServerTerm, ServerTerms } -import com.twilio.guardrail.terms.{ RouteMeta, ScalaTerms, SwaggerTerms } -import com.twilio.guardrail.terms.framework.FrameworkTerms +import com.twilio.guardrail.protocol.terms.server.ServerTerms import com.twilio.guardrail.shims._ -import scala.collection.JavaConverters._ +import com.twilio.guardrail.terms.framework.FrameworkTerms +import com.twilio.guardrail.terms.{RouteMeta, ScalaTerms, SwaggerTerms} case class Servers[L <: LA](servers: List[Server[L]]) -case class Server[L <: LA](pkg: List[String], extraImports: List[L#Import], src: List[L#Statement]) +case class Server[L <: LA](pkg: List[String], extraImports: List[L#Import], handlerDefinition: L#Definition, serverDefinitions: List[L#Definition]) case class TracingField[L <: LA](param: ScalaParameter[L], term: L#Term) case class RenderedRoutes[L <: LA]( routes: L#Term, @@ -25,7 +22,6 @@ case class RenderedRoutes[L <: LA]( ) object ServerGenerator { - import NelShim._ def formatClassName(str: String): String = s"${str.capitalize}Resource" def formatHandlerName(str: String): String = s"${str.capitalize}Handler" @@ -67,7 +63,7 @@ object ServerGenerator { } (responseDefinitions, serverOperations) = responseServerPair.unzip renderedRoutes <- generateRoutes(resourceName, basePath, serverOperations, protocolElems) - handlerSrc <- renderHandler(formatHandlerName(className.lastOption.getOrElse("")), renderedRoutes.methodSigs, renderedRoutes.handlerDefinitions) + handlerSrc <- renderHandler(handlerName, renderedRoutes.methodSigs, renderedRoutes.handlerDefinitions) extraRouteParams <- getExtraRouteParams(context.tracing) classSrc <- renderClass(resourceName, handlerName, @@ -76,7 +72,7 @@ object ServerGenerator { responseDefinitions.flatten, renderedRoutes.supportDefinitions) } yield { - Server(className, frameworkImports ++ extraImports, handlerSrc +: classSrc) + Server(className, frameworkImports ++ extraImports, handlerSrc, classSrc) } } } yield Servers[L](servers) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala index fede650e3a..bf151fb83c 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala @@ -263,7 +263,8 @@ object JavaGenerator { cu.toString(printer).getBytes(StandardCharsets.UTF_8) ) } - case WriteServer(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, Server(pkg, extraImports, src)) => + + case WriteServer(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, Server(pkg, extraImports, selfHandlerDefinition, serverDefinitions)) => Target.raiseError("TODO: java server generation") } } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala index 49c7244aff..d3e6ff9f06 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala @@ -347,7 +347,7 @@ object ScalaGenerator { """.syntax.getBytes(StandardCharsets.UTF_8) ) ) - case WriteServer(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, Server(pkg, extraImports, src)) => + case WriteServer(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, Server(pkg, extraImports, handlerDefinition, serverDefinitions)) => Target.pure( List(WriteTree( resolveFile(pkgPath)(pkg.toList :+ "Routes.scala"), @@ -358,7 +358,8 @@ object ScalaGenerator { ..${frameworkImplicitName.map(name => q"import ${buildPkgTerm(List("_root_") ++ pkgName)}.${name}._")} import ${buildPkgTerm(List("_root_") ++ dtoComponents)}._ ..${customImports} - ..$src + ${handlerDefinition} + ..${serverDefinitions} """.syntax.getBytes(StandardCharsets.UTF_8) )) ) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala index 1575006a06..0ba508a52c 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala @@ -22,7 +22,7 @@ case class RenderClass[L <: LA](className: String, extraRouteParams: List[L#MethodParameter], responseDefinitions: List[L#Definition], supportDefinitions: List[L#Definition]) - extends ServerTerm[L, List[L#Statement]] + extends ServerTerm[L, List[L#Definition]] case class RenderHandler[L <: LA](handlerName: String, methodSigs: List[L#MethodDeclaration], handlerDefinitions: List[L#Statement]) - extends ServerTerm[L, L#Statement] + extends ServerTerm[L, L#Definition] case class GetExtraImports[L <: LA](tracing: Boolean) extends ServerTerm[L, List[L#Import]] diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala index bd0a9c9521..b6b9e96ada 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala @@ -25,9 +25,9 @@ class ServerTerms[L <: LA, F[_]](implicit I: InjectK[ServerTerm[L, ?], F]) { combinedRouteTerms: L#Term, extraRouteParams: List[L#MethodParameter], responseDefinitions: List[L#Definition], - supportDefinitions: List[L#Definition]): Free[F, List[L#Statement]] = + supportDefinitions: List[L#Definition]): Free[F, List[L#Definition]] = Free.inject[ServerTerm[L, ?], F](RenderClass(resourceName, handlerName, combinedRouteTerms, extraRouteParams, responseDefinitions, supportDefinitions)) - def renderHandler(handlerName: String, methodSigs: List[L#MethodDeclaration], handlerDefinitions: List[L#Statement]) = + def renderHandler(handlerName: String, methodSigs: List[L#MethodDeclaration], handlerDefinitions: List[L#Statement]): Free[F, L#Definition] = Free.inject[ServerTerm[L, ?], F](RenderHandler(handlerName, methodSigs, handlerDefinitions)) def getExtraImports(tracing: Boolean): Free[F, List[L#Import]] = Free.inject[ServerTerm[L, ?], F](GetExtraImports(tracing)) diff --git a/modules/codegen/src/test/scala/core/issues/Issue126.scala b/modules/codegen/src/test/scala/core/issues/Issue126.scala index cf2d78058c..17d53a3c33 100644 --- a/modules/codegen/src/test/scala/core/issues/Issue126.scala +++ b/modules/codegen/src/test/scala/core/issues/Issue126.scala @@ -25,7 +25,7 @@ class Issue126 extends FunSuite with Matchers with SwaggerSpecRunner { val ( _, _, - Servers(Server(pkg, extraImports, genHandler :: genResource :: Nil) :: Nil) + Servers(Server(pkg, extraImports, genHandler, genResource :: Nil) :: Nil) ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val handler = q""" diff --git a/modules/codegen/src/test/scala/core/issues/Issue127.scala b/modules/codegen/src/test/scala/core/issues/Issue127.scala index a286415b99..e7cdf76d81 100644 --- a/modules/codegen/src/test/scala/core/issues/Issue127.scala +++ b/modules/codegen/src/test/scala/core/issues/Issue127.scala @@ -36,7 +36,7 @@ class Issue127 extends FunSuite with Matchers with SwaggerSpecRunner { val ( _, _, - Servers(Server(pkg, extraImports, genHandler :: genResource :: Nil) :: Nil) + Servers(Server(pkg, extraImports, genHandler, genResource :: Nil) :: Nil) ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val handler = q""" diff --git a/modules/codegen/src/test/scala/core/issues/Issue165.scala b/modules/codegen/src/test/scala/core/issues/Issue165.scala index 19fe6e93ef..4cb4f7f3b1 100644 --- a/modules/codegen/src/test/scala/core/issues/Issue165.scala +++ b/modules/codegen/src/test/scala/core/issues/Issue165.scala @@ -38,7 +38,7 @@ class Issue165 extends FunSuite with Matchers with SwaggerSpecRunner { |""".stripMargin test("Ensure routes are generated") { - val (_, _, Servers(Server(_, _, genHandler :: genResource :: _) :: Nil)) = runSwaggerSpec(swagger)(Context.empty, Http4s) + val (_, _, Servers(Server(_, _, genHandler, genResource :: _) :: Nil)) = runSwaggerSpec(swagger)(Context.empty, Http4s) val handler = q""" trait StoreHandler[F[_]] { diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/AkkaHttpServerTest.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/AkkaHttpServerTest.scala index 914f04c3f3..fe689dc7ee 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/AkkaHttpServerTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/AkkaHttpServerTest.scala @@ -127,7 +127,7 @@ class AkkaHttpServerTest extends FunSuite with Matchers with SwaggerSpecRunner { val ( _, _, - Servers(Server(pkg, extraImports, genHandler :: genResource :: Nil) :: Nil) + Servers(Server(pkg, extraImports, genHandler, genResource :: Nil) :: Nil) ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val handler = q""" @@ -268,7 +268,7 @@ class AkkaHttpServerTest extends FunSuite with Matchers with SwaggerSpecRunner { val ( _, _, - Servers(Server(pkg, extraImports, genHandler :: genResource :: Nil) :: Nil) + Servers(Server(pkg, extraImports, genHandler, genResource :: Nil) :: Nil) ) = runSwaggerSpec(swagger)(Context.empty.copy(tracing = true), AkkaHttp) val handler = q""" diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/CustomHeaderTest.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/CustomHeaderTest.scala index 4bdb7dcec1..5934985695 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/CustomHeaderTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/CustomHeaderTest.scala @@ -39,7 +39,7 @@ class CustomHeaderTest extends FunSuite with Matchers with SwaggerSpecRunner { |""".stripMargin test("Should produce static parameter constraints") { - val (_, Clients(client :: Nil), Servers(Server(_, _, genHandler :: genResource :: Nil) :: Nil)) = + val (_, Clients(client :: Nil), Servers(Server(_, _, genHandler, genResource :: Nil) :: Nil)) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val handler = diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/StaticParametersTest.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/StaticParametersTest.scala index 9390eeea4f..b48118c828 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/StaticParametersTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/StaticParametersTest.scala @@ -31,7 +31,7 @@ class StaticParametersTest extends FunSuite with Matchers with SwaggerSpecRunner |""".stripMargin test("Should produce static parameter constraints") { - val (_, _, Servers(Server(_, _, genHandler :: genResource :: Nil) :: Nil)) = + val (_, _, Servers(Server(_, _, genHandler, genResource :: Nil) :: Nil)) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val handler = q""" diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/server/FormFieldsTest.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/server/FormFieldsTest.scala index 15acd70282..580a7f22fd 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/server/FormFieldsTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/server/FormFieldsTest.scala @@ -46,7 +46,7 @@ class FormFieldsServerTest extends FunSuite with Matchers with SwaggerSpecRunner val ( _, _, - Servers(Server(pkg, extraImports, genHandler :: genResource :: Nil) :: Nil) + Servers(Server(pkg, extraImports, genHandler, genResource :: Nil) :: Nil) ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val handler = q""" From 139113bf0dd8d5300b998c1673f4c0117ea78307 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Mon, 4 Mar 2019 17:54:08 -0800 Subject: [PATCH 31/86] Allow route terms to be a list rather than combined --- .../src/main/scala/com/twilio/guardrail/ServerGenerator.scala | 2 +- .../twilio/guardrail/generators/AkkaHttpServerGenerator.scala | 4 ++-- .../twilio/guardrail/generators/Http4sServerGenerator.scala | 4 ++-- .../twilio/guardrail/protocol/terms/server/ServerTerm.scala | 2 +- .../twilio/guardrail/protocol/terms/server/ServerTerms.scala | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala index 497d168f03..d4e98883f4 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala @@ -15,7 +15,7 @@ case class Servers[L <: LA](servers: List[Server[L]]) case class Server[L <: LA](pkg: List[String], extraImports: List[L#Import], handlerDefinition: L#Definition, serverDefinitions: List[L#Definition]) case class TracingField[L <: LA](param: ScalaParameter[L], term: L#Term) case class RenderedRoutes[L <: LA]( - routes: L#Term, + routes: List[L#Term], methodSigs: List[L#MethodDeclaration], supportDefinitions: List[L#Definition], handlerDefinitions: List[L#Statement] diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpServerGenerator.scala index 464b809f37..56bd3710eb 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpServerGenerator.scala @@ -132,7 +132,7 @@ object AkkaHttpServerGenerator { methodSigs = renderedRoutes.map(_.methodSig) } yield { RenderedRoutes[ScalaLanguage]( - combinedRouteTerms, + List(combinedRouteTerms), methodSigs, renderedRoutes.flatMap(_.supportDefinitions), renderedRoutes.flatMap(_.handlerDefinitions) @@ -166,7 +166,7 @@ object AkkaHttpServerGenerator { ..${supportDefinitions}; def routes(..${routesParams})(implicit mat: akka.stream.Materializer): Route = { - ${combinedRouteTerms} + ..${combinedRouteTerms} } ..${responseDefinitions} diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sServerGenerator.scala index 6f0b6df5b0..b1022abb21 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sServerGenerator.scala @@ -59,7 +59,7 @@ object Http4sServerGenerator { methodSigs = renderedRoutes.map(_.methodSig) } yield { RenderedRoutes[ScalaLanguage]( - combinedRouteTerms, + List(combinedRouteTerms), methodSigs, renderedRoutes.flatMap(_.supportDefinitions), renderedRoutes.flatMap(_.handlerDefinitions) @@ -92,7 +92,7 @@ object Http4sServerGenerator { ..${supportDefinitions}; def routes(..${routesParams}): HttpRoutes[F] = HttpRoutes.of { - ${combinedRouteTerms} + ..${combinedRouteTerms} } } """ +: responseDefinitions diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala index 0ba508a52c..b7d4a3fe86 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala @@ -18,7 +18,7 @@ case class GenerateResponseDefinitions[L <: LA](operationId: String, responses: extends ServerTerm[L, List[L#Definition]] case class RenderClass[L <: LA](className: String, handlerName: String, - combinedRouteTerms: L#Term, + combinedRouteTerms: List[L#Term], extraRouteParams: List[L#MethodParameter], responseDefinitions: List[L#Definition], supportDefinitions: List[L#Definition]) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala index b6b9e96ada..8486332737 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala @@ -22,7 +22,7 @@ class ServerTerms[L <: LA, F[_]](implicit I: InjectK[ServerTerm[L, ?], F]) { Free.inject[ServerTerm[L, ?], F](GenerateResponseDefinitions(operationId, responses, protocolElems)) def renderClass(resourceName: String, handlerName: String, - combinedRouteTerms: L#Term, + combinedRouteTerms: List[L#Term], extraRouteParams: List[L#MethodParameter], responseDefinitions: List[L#Definition], supportDefinitions: List[L#Definition]): Free[F, List[L#Definition]] = From 0ba74d751200e7d69fc586ca44148b8a336ba63b Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Mon, 4 Mar 2019 17:57:27 -0800 Subject: [PATCH 32/86] Allow passing annotations to be added to server classes --- .../com/twilio/guardrail/ServerGenerator.scala | 2 ++ .../generators/AkkaHttpServerGenerator.scala | 3 ++- .../generators/EndpointsServerGenerator.scala | 15 +-------------- .../generators/Http4sServerGenerator.scala | 3 ++- .../twilio/guardrail/languages/JavaLanguage.scala | 1 + .../guardrail/languages/LanguageAbstraction.scala | 1 + .../guardrail/languages/ScalaLanguage.scala | 1 + .../protocol/terms/server/ServerTerm.scala | 1 + .../protocol/terms/server/ServerTerms.scala | 3 ++- 9 files changed, 13 insertions(+), 17 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala index d4e98883f4..eda3bb0d81 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala @@ -16,6 +16,7 @@ case class Server[L <: LA](pkg: List[String], extraImports: List[L#Import], hand case class TracingField[L <: LA](param: ScalaParameter[L], term: L#Term) case class RenderedRoutes[L <: LA]( routes: List[L#Term], + classAnnotations: List[L#Annotation], methodSigs: List[L#MethodDeclaration], supportDefinitions: List[L#Definition], handlerDefinitions: List[L#Statement] @@ -67,6 +68,7 @@ object ServerGenerator { extraRouteParams <- getExtraRouteParams(context.tracing) classSrc <- renderClass(resourceName, handlerName, + renderedRoutes.classAnnotations, renderedRoutes.routes, extraRouteParams, responseDefinitions.flatten, diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpServerGenerator.scala index 56bd3710eb..50e550072b 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpServerGenerator.scala @@ -133,6 +133,7 @@ object AkkaHttpServerGenerator { } yield { RenderedRoutes[ScalaLanguage]( List(combinedRouteTerms), + List.empty, methodSigs, renderedRoutes.flatMap(_.supportDefinitions), renderedRoutes.flatMap(_.handlerDefinitions) @@ -156,7 +157,7 @@ object AkkaHttpServerGenerator { } else Target.pure(List.empty) } yield res - case RenderClass(resourceName, handlerName, combinedRouteTerms, extraRouteParams, responseDefinitions, supportDefinitions) => + case RenderClass(resourceName, handlerName, _, combinedRouteTerms, extraRouteParams, responseDefinitions, supportDefinitions) => for { _ <- Target.log.debug("AkkaHttpServerGenerator", "server")(s"renderClass(${resourceName}, ${handlerName}, , ${extraRouteParams})") routesParams = List(param"handler: ${Type.Name(handlerName)}") ++ extraRouteParams diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsServerGenerator.scala index 3e7eae090d..a6fdf1f5aa 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsServerGenerator.scala @@ -1,22 +1,9 @@ package com.twilio.guardrail package generators -import _root_.io.swagger.models.{ HttpMethod, Operation } import cats.arrow.FunctionK -import cats.data.{ NonEmptyList, OptionT } -import cats.instances.all._ -import cats.syntax.applicative._ -import cats.syntax.flatMap._ -import cats.syntax.functor._ -import cats.syntax.traverse._ -import com.twilio.guardrail.SwaggerUtil -import com.twilio.guardrail.extract.{ ScalaPackage, ScalaTracingLabel, ServerRawResponse } -import com.twilio.guardrail.generators.syntax.Scala._ import com.twilio.guardrail.languages.ScalaLanguage import com.twilio.guardrail.protocol.terms.server._ -import com.twilio.guardrail.terms.RouteMeta -import scala.collection.JavaConverters._ -import scala.meta._ object EndpointsServerGenerator { object ServerTermInterp extends FunctionK[ServerTerm[ScalaLanguage, ?], Target] { @@ -26,7 +13,7 @@ object EndpointsServerGenerator { case GenerateRoutes(resourceName, basePath, routes, protocolElems) => ??? case RenderHandler(handlerName, methodSigs, handlerDefinitions) => ??? case GetExtraRouteParams(tracing) => ??? - case RenderClass(resourceName, handlerName, combinedRouteTerms, extraRouteParams, responseDefinitions, supportDefinitions) => ??? + case RenderClass(resourceName, handlerName, annotations, combinedRouteTerms, extraRouteParams, responseDefinitions, supportDefinitions) => ??? case GetExtraImports(tracing) => ??? } } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sServerGenerator.scala index b1022abb21..90bd4b414e 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sServerGenerator.scala @@ -60,6 +60,7 @@ object Http4sServerGenerator { } yield { RenderedRoutes[ScalaLanguage]( List(combinedRouteTerms), + List.empty, methodSigs, renderedRoutes.flatMap(_.supportDefinitions), renderedRoutes.flatMap(_.handlerDefinitions) @@ -83,7 +84,7 @@ object Http4sServerGenerator { } else Target.pure(List.empty) } yield res - case RenderClass(resourceName, handlerName, combinedRouteTerms, extraRouteParams, responseDefinitions, supportDefinitions) => + case RenderClass(resourceName, handlerName, _, combinedRouteTerms, extraRouteParams, responseDefinitions, supportDefinitions) => for { _ <- Target.log.debug("Http4sServerGenerator", "server")(s"renderClass(${resourceName}, ${handlerName}, , ${extraRouteParams})") routesParams = List(param"handler: ${Type.Name(handlerName)}[F]") diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/languages/JavaLanguage.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/languages/JavaLanguage.scala index 7707208c9f..8ef0633478 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/languages/JavaLanguage.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/languages/JavaLanguage.scala @@ -32,6 +32,7 @@ class JavaLanguage extends LanguageAbstraction { type MethodParameter = com.github.javaparser.ast.body.Parameter type Type = com.github.javaparser.ast.`type`.Type type TypeName = com.github.javaparser.ast.expr.Name + type Annotation = com.github.javaparser.ast.expr.AnnotationExpr // Result type FileContents = com.github.javaparser.ast.CompilationUnit diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/languages/LanguageAbstraction.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/languages/LanguageAbstraction.scala index 722e9c9185..ac92ade551 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/languages/LanguageAbstraction.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/languages/LanguageAbstraction.scala @@ -32,6 +32,7 @@ class LanguageAbstraction { type MethodParameter type Type type TypeName + type Annotation // Result type FileContents diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/languages/ScalaLanguage.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/languages/ScalaLanguage.scala index f7656220ae..40aaad8644 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/languages/ScalaLanguage.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/languages/ScalaLanguage.scala @@ -32,6 +32,7 @@ class ScalaLanguage extends LanguageAbstraction { type MethodParameter = scala.meta.Term.Param type Type = scala.meta.Type type TypeName = scala.meta.Type.Name + type Annotation = scala.meta.Mod.Annot // Result type FileContents = scala.meta.Source diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala index b7d4a3fe86..2d303412d2 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala @@ -18,6 +18,7 @@ case class GenerateResponseDefinitions[L <: LA](operationId: String, responses: extends ServerTerm[L, List[L#Definition]] case class RenderClass[L <: LA](className: String, handlerName: String, + annotations: List[L#Annotation], combinedRouteTerms: List[L#Term], extraRouteParams: List[L#MethodParameter], responseDefinitions: List[L#Definition], diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala index 8486332737..42c5ef55ea 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala @@ -22,11 +22,12 @@ class ServerTerms[L <: LA, F[_]](implicit I: InjectK[ServerTerm[L, ?], F]) { Free.inject[ServerTerm[L, ?], F](GenerateResponseDefinitions(operationId, responses, protocolElems)) def renderClass(resourceName: String, handlerName: String, + annotations: List[L#Annotation], combinedRouteTerms: List[L#Term], extraRouteParams: List[L#MethodParameter], responseDefinitions: List[L#Definition], supportDefinitions: List[L#Definition]): Free[F, List[L#Definition]] = - Free.inject[ServerTerm[L, ?], F](RenderClass(resourceName, handlerName, combinedRouteTerms, extraRouteParams, responseDefinitions, supportDefinitions)) + Free.inject[ServerTerm[L, ?], F](RenderClass(resourceName, handlerName, annotations, combinedRouteTerms, extraRouteParams, responseDefinitions, supportDefinitions)) def renderHandler(handlerName: String, methodSigs: List[L#MethodDeclaration], handlerDefinitions: List[L#Statement]): Free[F, L#Definition] = Free.inject[ServerTerm[L, ?], F](RenderHandler(handlerName, methodSigs, handlerDefinitions)) def getExtraImports(tracing: Boolean): Free[F, List[L#Import]] = From 4f0a4861965b14f3ff3e1ff69c0a76fa348116bf Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Mon, 4 Mar 2019 17:58:29 -0800 Subject: [PATCH 33/86] Implement WriteServer for JavaGenerator --- .../guardrail/generators/JavaGenerator.scala | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala index bf151fb83c..f9abbf4387 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala @@ -5,7 +5,7 @@ import cats.instances.option._ import cats.syntax.traverse._ import com.github.javaparser.ast._ import com.github.javaparser.ast.`type`.{ClassOrInterfaceType, PrimitiveType, Type, VoidType, ArrayType => AstArrayType} -import com.github.javaparser.ast.body.Parameter +import com.github.javaparser.ast.body.{BodyDeclaration, Parameter, TypeDeclaration} import com.github.javaparser.ast.expr._ import com.github.javaparser.ast.stmt.Statement import com.twilio.guardrail._ @@ -264,9 +264,45 @@ object JavaGenerator { ) } - case WriteServer(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, Server(pkg, extraImports, selfHandlerDefinition, serverDefinitions)) => - Target.raiseError("TODO: java server generation") + case WriteServer(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, Server(pkg, extraImports, handlerDefinition, serverDefinitions)) => + for { + pkgDecl <- buildPkgDecl(pkgName ++ pkg) + //implicitsImport <- safeParseName((pkgName ++ List("Implicits", "*")).mkString(".")).map(name => new ImportDeclaration(name, false, true)) + //frameworkImplicitsImport <- safeParseName((pkgName ++ List(frameworkImplicitName.getIdentifier, "*")).mkString(".")) + // .map(name => new ImportDeclaration(name, false, true)) + dtoComponentsImport <- safeParseRawImport((dtoComponents :+ "*").mkString(".")) + + handlerDefinition <- selfHandlerDefinition match { + case td: TypeDeclaration[_] => Target.pure(td) + case _ => Target.raiseError(s"Handler definition must be a TypeDeclaration but it is a ${selfHandlerDefinition.getClass.getName}") + } + + serverDefinition <- serverDefinitions match { + case (td: TypeDeclaration[_]) :: Nil => Target.pure(td) + case other :: Nil => Target.raiseError(s"Server definition must be a TypeDeclaration but it is a ${other.getClass.getName}") + case _ :: _ :: Nil => Target.raiseError("Only a single server definition is supported for Java generation") + case _ => Target.raiseError("No server definition provided") + } + } yield { + def writeClass(name: String, definition: TypeDeclaration[_]): WriteTree = { + val cu = new CompilationUnit() + cu.setPackageDeclaration(pkgDecl) + extraImports.map(cu.addImport) + customImports.map(cu.addImport) + //cu.addImport(implicitsImport) + //cu.addImport(frameworkImplicitsImport) + cu.addImport(dtoComponentsImport) + cu.addType(definition) + WriteTree( + resolveFile(pkgPath)(pkg :+ s"${name}.java"), + cu.toString(printer).getBytes(StandardCharsets.UTF_8) + ) + } + List( + writeClass(handlerName, handlerDefinition), + writeClass(resourceName, serverDefinition) + ) + } } } - } From 333f608f6f1783796c866c7a64a228c20b216871 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Mon, 4 Mar 2019 18:00:40 -0800 Subject: [PATCH 34/86] Implement first cut at a Dropwizard server generator --- .../Java/AsyncHttpClientClientGenerator.scala | 3 - .../generators/Java/Dropwizard.scala | 6 +- .../Java/DropwizardServerGenerator.scala | 340 ++++++++++++++++++ .../guardrail/generators/syntax/Java.scala | 3 + 4 files changed, 344 insertions(+), 8 deletions(-) create mode 100644 modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index ae15531743..df659d313a 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -23,9 +23,6 @@ import java.util import java.util.Locale object AsyncHttpClientClientGenerator { - private def completionStageType: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("CompletionStage" ) - private def optionalType: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Optional") - private val URI_TYPE = JavaParser.parseClassOrInterfaceType("URI") private val DEFAULT_ASYNC_HTTP_CLIENT_CONFIG_BUILDER_TYPE = JavaParser.parseClassOrInterfaceType("DefaultAsyncHttpClientConfig.Builder") private val ASYNC_HTTP_CLIENT_TYPE = JavaParser.parseClassOrInterfaceType("AsyncHttpClient") diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/Dropwizard.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/Dropwizard.scala index 0b09ac7e97..92cc2b6fe4 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/Dropwizard.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/Dropwizard.scala @@ -3,18 +3,14 @@ package com.twilio.guardrail.generators.Java import cats.~> import com.twilio.guardrail.generators.Java.AsyncHttpClientClientGenerator.ClientTermInterp import com.twilio.guardrail.generators.Java.DropwizardGenerator.FrameworkInterp +import com.twilio.guardrail.generators.Java.DropwizardServerGenerator.ServerTermInterp import com.twilio.guardrail.generators.Java.JacksonGenerator._ import com.twilio.guardrail.generators.JavaGenerator.JavaInterp import com.twilio.guardrail.generators.SwaggerGenerator import com.twilio.guardrail.languages.JavaLanguage -import com.twilio.guardrail.protocol.terms.server.ServerTerm import com.twilio.guardrail.{ClientServerTerms, CodegenApplication, DefinitionPM, DefinitionPME, DefinitionPMEA, DefinitionPMEAP, FrameworkC, FrameworkCS, FrameworkCSF, ModelInterpreters, Parser, Target} object Dropwizard extends (CodegenApplication[JavaLanguage, ?] ~> Target) { - object ServerTermInterp extends (ServerTerm[JavaLanguage, ?] ~> Target) { - def apply[T](term: ServerTerm[JavaLanguage, T]): Target[T] = Target.raiseError(s"interpServer: ${term.toString()}") - } - val interpDefinitionPM: DefinitionPM[JavaLanguage, ?] ~> Target = ProtocolSupportTermInterp or ModelProtocolTermInterp val interpDefinitionPME: DefinitionPME[JavaLanguage, ?] ~> Target = EnumProtocolTermInterp or interpDefinitionPM val interpDefinitionPMEA: DefinitionPMEA[JavaLanguage, ?] ~> Target = ArrayProtocolTermInterp or interpDefinitionPME diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala new file mode 100644 index 0000000000..0b5e71168d --- /dev/null +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala @@ -0,0 +1,340 @@ +package com.twilio.guardrail.generators.Java + +import cats.data.NonEmptyList +import cats.~> +import cats.instances.list._ +import cats.syntax.flatMap._ +import cats.syntax.traverse._ +import com.github.javaparser.JavaParser +import com.github.javaparser.ast.Modifier._ +import com.github.javaparser.ast.{Modifier, NodeList} +import com.github.javaparser.ast.`type`.{PrimitiveType, VoidType} +import com.github.javaparser.ast.body._ +import com.github.javaparser.ast.expr._ +import com.github.javaparser.ast.stmt._ +import com.twilio.guardrail.generators.Response +import com.twilio.guardrail.{RenderedRoutes, Target} +import com.twilio.guardrail.generators.syntax.Java._ +import com.twilio.guardrail.languages.JavaLanguage +import com.twilio.guardrail.protocol.terms.server._ +import com.twilio.guardrail.terms.RouteMeta +import io.swagger.v3.oas.models.PathItem.HttpMethod +import java.util + +object DropwizardServerGenerator { + private val ASYNC_RESPONSE_TYPE = JavaParser.parseClassOrInterfaceType("AsyncResponse") + private val RESPONSE_BUILDER_TYPE = JavaParser.parseClassOrInterfaceType("ResponseBuilder") + private val LOGGER_TYPE = JavaParser.parseClassOrInterfaceType("Logger") + + private def removeEmpty(s: String): Option[String] = if (s.trim.isEmpty) None else Some(s.trim) + private def splitPathComponents(s: String): List[String] = s.split("/").flatMap(removeEmpty).toList + + private def findPathPrefix(routePaths: List[String]): List[String] = { + def getHeads(sss: List[List[String]]): (List[Option[String]], List[List[String]]) = + (sss.map(_.headOption), sss.map(ss => ss.headOption.fold(List.empty[String])(_ => ss.tail))) + + def checkMatch(matching: List[String], headsToCheck: List[Option[String]], restOfHeads: List[List[String]]): List[String] = { + headsToCheck.headOption.flatMap({ firstO => firstO.map({ first => + if (headsToCheck.tail.forall(_.contains(first))) { + val (nextHeads, nextRest) = getHeads(restOfHeads) + checkMatch(matching :+ first, nextHeads, nextRest) + } else { + matching + } + })}).getOrElse(matching) + } + + val splitRoutePaths = routePaths.map(splitPathComponents) + val (initialHeads, initialRest) = getHeads(splitRoutePaths) + checkMatch(List.empty, initialHeads, initialRest) + } + + + object ServerTermInterp extends (ServerTerm[JavaLanguage, ?] ~> Target) { + def apply[T](term: ServerTerm[JavaLanguage, T]): Target[T] = term match { + case GetExtraImports(tracing) => + List( + "javax.ws.rs.Consumes", + "javax.ws.rs.DELETE", + "javax.ws.rs.FormParam", + "javax.ws.rs.GET", + "javax.ws.rs.HEAD", + "javax.ws.rs.HeaderParam", + "javax.ws.rs.HttpMethod", + "javax.ws.rs.OPTIONS", + "javax.ws.rs.POST", + "javax.ws.rs.PUT", + "javax.ws.rs.Path", + "javax.ws.rs.PathParam", + "javax.ws.rs.Produces", + "javax.ws.rs.QueryParam", + "javax.ws.rs.container.AsyncResponse", + "javax.ws.rs.container.Suspended", + "javax.ws.rs.core.MediaType", + "org.slf4j.Logger", + "org.slf4j.LoggerFactory" + ).map(safeParseRawImport).sequence + + case BuildTracingFields(operation, resourceName, tracing) => + if (tracing) { + Target.raiseError(s"Tracing is not yet supported by this framework") + } else { + Target.pure(Option.empty) + } + + case GenerateRoutes(resourceName, basePath, routes, protocolElems) => + for { + resourceType <- safeParseClassOrInterfaceType(resourceName) + handlerName = s"${resourceName.replaceAll("Resource$", "")}Handler" + handlerType <- safeParseClassOrInterfaceType(handlerName) + } yield { + val basePathComponents = basePath.toList.flatMap(splitPathComponents) + val commonPathPrefix = findPathPrefix(routes.map(_._3.path)) + + val (routeMethods, handlerMethodSigs) = routes.map({ case (operationId, tracingFields, sr @ RouteMeta(path, httpMethod, operation), parameters, responses) => + val method = new MethodDeclaration(util.EnumSet.of(PUBLIC), new VoidType, operationId) + val httpMethodAnnotation = httpMethod match { + case HttpMethod.DELETE => new MarkerAnnotationExpr("DELETE") + case HttpMethod.GET => new MarkerAnnotationExpr("GET") + case HttpMethod.HEAD => new MarkerAnnotationExpr("HEAD") + case HttpMethod.OPTIONS => new MarkerAnnotationExpr("OPTIONS") + case HttpMethod.PATCH => new SingleMemberAnnotationExpr(new Name("HttpMethod"), new StringLiteralExpr("PATCH")) + case HttpMethod.POST => new MarkerAnnotationExpr("POST") + case HttpMethod.PUT => new MarkerAnnotationExpr("PUT") + case HttpMethod.TRACE => new SingleMemberAnnotationExpr(new Name("HttpMethod"), new StringLiteralExpr("TRACE")) + } + method.addAnnotation(httpMethodAnnotation) + + val pathSuffix = splitPathComponents(path).drop(commonPathPrefix.length).mkString("/", "/", "") + method.addAnnotation(new SingleMemberAnnotationExpr(new Name("Path"), new StringLiteralExpr(pathSuffix))) + parameters.formParams.headOption.map(_ => "APPLICATION_FORM_URLENCODED") + .orElse(parameters.bodyParams.map(_ => "APPLICATION_JSON")) + .foreach(produces => + method.addAnnotation(new SingleMemberAnnotationExpr(new Name("Consumes"), new FieldAccessExpr(new NameExpr("MediaType"), produces))) + ) + if (responses.value.exists(_.value.isDefined)) { + method.addAnnotation(new SingleMemberAnnotationExpr(new Name("Produces"), new FieldAccessExpr(new NameExpr("MediaType"), "APPLICATION_JSON"))) + } + + def addParamAnnotation(template: Parameter, annotationName: String, paramName: Name): Parameter = { + val parameter = template.clone() + parameter.addAnnotation(new SingleMemberAnnotationExpr(new Name(annotationName), new StringLiteralExpr(paramName.asString))) + parameter + } + + val methodParams: List[Parameter] = List( + (parameters.pathParams, "PathParam"), + (parameters.headerParams, "HeaderParam"), + (parameters.queryStringParams, "QueryParam"), + (parameters.formParams, "FormParam") + ).flatMap({ case (params, annotationName) => + params.map(param => addParamAnnotation(param.param, annotationName, param.paramName)) + }) ++ parameters.bodyParams.map(_.param) + + methodParams.foreach(method.addParameter) + method.addParameter( + new Parameter(util.EnumSet.of(FINAL), ASYNC_RESPONSE_TYPE, new SimpleName("asyncResponse")).addMarkerAnnotation("Suspended") + ) + + val responseName = s"${operationId.capitalize}Response" + val responseType = JavaParser.parseClassOrInterfaceType(responseName) + + val responseBodyIfBranches = responses.value.collect({ + case r @ Response(_, Some(_)) => r + }).map({ case Response(statusCodeName, valueType) => + val responseType = JavaParser.parseClassOrInterfaceType(s"${responseName}.${statusCodeName}") + new IfStmt( + new InstanceOfExpr(new NameExpr("result"), responseType), + new BlockStmt(new NodeList( + new ExpressionStmt(new VariableDeclarationExpr(new VariableDeclarator(responseType, s"result${statusCodeName}", new CastExpr(responseType, new NameExpr("result"))), FINAL)), + new ExpressionStmt( + new MethodCallExpr(new NameExpr("builder"), "entity", new NodeList[Expression]( + new MethodCallExpr(new NameExpr(s"result${statusCodeName}"), "getValue") + )) + ))), + null + ) + }) + NonEmptyList.fromList(responseBodyIfBranches).foreach(_.reduceLeft({ (prev, next) => + prev.setElseStmt(next) + next + })) + + val whenCompleteLambda = new LambdaExpr( + new NodeList( + new Parameter(util.EnumSet.of(FINAL), JavaParser.parseClassOrInterfaceType(responseName), new SimpleName("result")), + new Parameter(util.EnumSet.of(FINAL), THROWABLE_TYPE, new SimpleName("err")) + ), + new BlockStmt(new NodeList(new IfStmt( + new BinaryExpr(new NameExpr("err"), new NullLiteralExpr, BinaryExpr.Operator.NOT_EQUALS), + new BlockStmt(new NodeList( + new ExpressionStmt(new MethodCallExpr(new NameExpr("logger"), "error", new NodeList[Expression]( + new StringLiteralExpr(s"${handlerName}.${operationId} threw an exception ({}): {}"), + new MethodCallExpr(new MethodCallExpr(new NameExpr("err"), "getClass"), "getName"), + new MethodCallExpr(new NameExpr("err"), "getMessage"), + new NameExpr("err") + ))), + new ExpressionStmt(new MethodCallExpr(new NameExpr("asyncResponse"), "resume", new NodeList[Expression]( + new MethodCallExpr(new MethodCallExpr( + new NameExpr("Response"), + "status", + new NodeList[Expression](new IntegerLiteralExpr(500)) + ), "build") + ))) + )), + new BlockStmt(new NodeList(List( + Option(new ExpressionStmt(new VariableDeclarationExpr( + new VariableDeclarator( + RESPONSE_BUILDER_TYPE, + "builder", + new MethodCallExpr(new NameExpr("Response"), "status", new NodeList[Expression](new MethodCallExpr(new NameExpr("result"), "getStatusCode"))) + ), + FINAL + ))), + responseBodyIfBranches.headOption, + Option(new ExpressionStmt(new MethodCallExpr(new NameExpr("asyncResponse"), "resume", new NodeList[Expression](new MethodCallExpr(new NameExpr("builder"), "build"))))) + ).flatten: _*)) + ))), + true + ) + + val handlerCall = new MethodCallExpr( + new FieldAccessExpr(new ThisExpr, "handler"), operationId, + new NodeList[Expression](methodParams.map(param => new NameExpr(param.getName.asString)): _*) + ) + + method.setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new MethodCallExpr(handlerCall, "whenComplete", new NodeList[Expression](whenCompleteLambda))) + ))) + + val futureResponseType = completionStageType.setTypeArguments(responseType) + val handlerMethodSig = new MethodDeclaration(util.EnumSet.noneOf(classOf[Modifier]), futureResponseType, operationId) + (parameters.pathParams ++ parameters.headerParams ++ parameters.queryStringParams ++ parameters.formParams ++ parameters.bodyParams).foreach({ parameter => + handlerMethodSig.addParameter(parameter.param.clone()) + }) + handlerMethodSig.setBody(null) + + (method, handlerMethodSig) + }).unzip + + val resourceConstructor = new ConstructorDeclaration(util.EnumSet.of(PUBLIC), resourceName) + resourceConstructor.addParameter(new Parameter(util.EnumSet.of(FINAL), handlerType, new SimpleName("handler"))) + resourceConstructor.setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new AssignExpr(new FieldAccessExpr(new ThisExpr, "handler"), new NameExpr("handler"), AssignExpr.Operator.ASSIGN)) + ))) + + val annotations = List( + new SingleMemberAnnotationExpr(new Name("Path"), new StringLiteralExpr((basePathComponents ++ commonPathPrefix).mkString("/", "/", ""))) + ) + + val supportDefinitions = List[BodyDeclaration[_]]( + new FieldDeclaration(util.EnumSet.of(PRIVATE, STATIC, FINAL), new VariableDeclarator( + LOGGER_TYPE, "logger", + new MethodCallExpr(new NameExpr("LoggerFactory"), "getLogger", new NodeList[Expression](new ClassExpr(resourceType))) + )), + new FieldDeclaration(util.EnumSet.of(PRIVATE, FINAL), new VariableDeclarator(handlerType, "handler")), + resourceConstructor + ) + + RenderedRoutes[JavaLanguage](routeMethods, annotations, handlerMethodSigs, supportDefinitions, List.empty) + } + + case GetExtraRouteParams(tracing) => + if (tracing) { + Target.raiseError(s"Tracing is not yet supported by this framework") + } else { + Target.pure(List.empty) + } + + case GenerateResponseDefinitions(operationId, responses, protocolElems) => + for { + abstractResponseClassName <- safeParseSimpleName(s"${operationId.capitalize}Response").map(_.asString) + abstractResponseClassType <- safeParseClassOrInterfaceType(abstractResponseClassName) + + // TODO: verify valueTypes are in protocolElems + } yield { + val abstractResponseClass = { + val cls = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, ABSTRACT), false, abstractResponseClassName) + cls.addField(PrimitiveType.intType, "statusCode", PRIVATE, FINAL) + + val clsConstructor = cls.addConstructor(PROTECTED) + clsConstructor.addParameter(new Parameter(util.EnumSet.of(FINAL), PrimitiveType.intType, new SimpleName("statusCode"))) + clsConstructor.setBody( + new BlockStmt( + new NodeList( + new ExpressionStmt(new AssignExpr(new FieldAccessExpr(new ThisExpr, "statusCode"), new NameExpr("statusCode"), AssignExpr.Operator.ASSIGN)) + ) + ) + ) + + val getStatusCodeMethod = cls.addMethod("getStatusCode", PUBLIC) + getStatusCodeMethod.setType(PrimitiveType.intType) + getStatusCodeMethod.setBody(new BlockStmt(new NodeList( + new ReturnStmt(new FieldAccessExpr(new ThisExpr, "statusCode")) + ))) + + cls + } + + val responseClasses = responses.value.map { response => + val clsName: String = response.statusCodeName.asString + val cls = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, STATIC), false, clsName) + cls.setExtendedTypes(new NodeList(abstractResponseClassType)) + + val (fields, constructor, methods) = response.value.fold({ + val constructor = new ConstructorDeclaration(util.EnumSet.of(PUBLIC), clsName) + constructor.setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new MethodCallExpr("super", new IntegerLiteralExpr(response.statusCode))) + ))) + (List.empty[FieldDeclaration], constructor, List.empty[MethodDeclaration]) + })({ case (valueType, _) => + val valueField = new FieldDeclaration(util.EnumSet.of(PRIVATE, FINAL), new VariableDeclarator(valueType, "value")) + + val constructor = new ConstructorDeclaration(util.EnumSet.of(PUBLIC), clsName) + constructor.addParameter(new Parameter(util.EnumSet.of(FINAL), valueType, new SimpleName("value"))) + constructor.setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new MethodCallExpr("super", new IntegerLiteralExpr(response.statusCode))), + new ExpressionStmt(new AssignExpr(new FieldAccessExpr(new ThisExpr, "value"), new NameExpr("value"), AssignExpr.Operator.ASSIGN)) + ))) + + val getValueMethod = new MethodDeclaration(util.EnumSet.of(PUBLIC), valueType, "getValue") + getValueMethod.setBody(new BlockStmt(new NodeList( + new ReturnStmt(new FieldAccessExpr(new ThisExpr, "value")) + ))) + + (valueField :: Nil, constructor, getValueMethod :: Nil) + }) + (fields ++ Option(constructor) ++ methods).foreach(cls.addMember) + + cls + } + responseClasses.foreach(abstractResponseClass.addMember) + + abstractResponseClass :: Nil + } + + case RenderClass(className, handlerName, classAnnotations, combinedRouteTerms, extraRouteParams, responseDefinitions, supportDefinitions) => + def doRender: Target[List[BodyDeclaration[_]]] = { + val cls = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, className) + classAnnotations.foreach(cls.addAnnotation) + supportDefinitions.foreach(cls.addMember) + combinedRouteTerms.foreach({ + case bd: BodyDeclaration[_] => cls.addMember(bd) + case _ => + }) + + Target.pure(cls :: Nil) + } + + safeParseSimpleName(className) >> + safeParseSimpleName(handlerName) >> + doRender + + case RenderHandler(handlerName, methodSigs, handlerDefinitions) => + val handlerClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), true, handlerName) + methodSigs.foreach(handlerClass.addMember) + Target.pure(handlerClass) + } + } + +} diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index ce50036d5b..4a397c3e53 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -35,6 +35,9 @@ object Java { def safeParseRawImport(s: String): Target[ImportDeclaration] = safeParse("safeParseRawImport")(JavaParser.parseImport, s"import ${s};") def safeParseRawStaticImport(s: String): Target[ImportDeclaration] = safeParse("safeParseStaticImport")(JavaParser.parseImport, s"import static ${s};") + def completionStageType: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("CompletionStage" ) + def optionalType: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Optional") + val STRING_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("String") val THROWABLE_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Throwable") val ASSERTION_ERROR_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("AssertionError") From c651ba168be65393b64be45cc28f70c88de2a1f8 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Mon, 4 Mar 2019 19:59:08 -0800 Subject: [PATCH 35/86] Make the AsyncHttpClient impl use a composable HTTP client function --- .../Java/AsyncHttpClientClientGenerator.scala | 80 +++++++++++++------ .../guardrail/generators/syntax/Java.scala | 1 + 2 files changed, 57 insertions(+), 24 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index df659d313a..180a0b633e 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -25,8 +25,11 @@ import java.util.Locale object AsyncHttpClientClientGenerator { private val URI_TYPE = JavaParser.parseClassOrInterfaceType("URI") private val DEFAULT_ASYNC_HTTP_CLIENT_CONFIG_BUILDER_TYPE = JavaParser.parseClassOrInterfaceType("DefaultAsyncHttpClientConfig.Builder") + private val DEFAULT_ASYNC_HTTP_CLIENT_TYPE = JavaParser.parseClassOrInterfaceType("DefaultAsyncHttpClient") private val ASYNC_HTTP_CLIENT_TYPE = JavaParser.parseClassOrInterfaceType("AsyncHttpClient") + private val ASYNC_HTTP_CLIENT_CONFIG_TYPE = JavaParser.parseClassOrInterfaceType("AsyncHttpClientConfig") private val REQUEST_BUILDER_TYPE = JavaParser.parseClassOrInterfaceType("RequestBuilder") + private val REQUEST_TYPE = JavaParser.parseClassOrInterfaceType("Request") private val RESPONSE_TYPE = JavaParser.parseClassOrInterfaceType("Response") private val OBJECT_MAPPER_TYPE = JavaParser.parseClassOrInterfaceType("ObjectMapper") private val BUILDER_TYPE = JavaParser.parseClassOrInterfaceType("Builder") @@ -34,6 +37,11 @@ object AsyncHttpClientClientGenerator { private val HTTP_ERROR_TYPE = JavaParser.parseClassOrInterfaceType("HttpError") private val EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("Exception") + private val HTTP_CLIENT_FUNCTION_TYPE = functionType.setTypeArguments(new NodeList[Type]( + REQUEST_TYPE, + completionStageType.setTypeArguments(RESPONSE_TYPE) + )) + object ClientTermInterp extends (ClientTerm[JavaLanguage, ?] ~> Target) { def apply[T](term: ClientTerm[JavaLanguage, T]): Target[T] = term match { case GenerateClientOperation(_, RouteMeta(pathStr, httpMethod, operation), methodName, tracing, parameters, responses) => @@ -42,7 +50,7 @@ object AsyncHttpClientClientGenerator { responseParentType <- safeParseClassOrInterfaceType(responseParentName) pathExpr <- jpaths.generateUrlPathParams(pathStr, parameters.pathParams) } yield { - val method = new MethodDeclaration(util.EnumSet.of(PUBLIC), new VoidType, operation.getOperationId) + val method = new MethodDeclaration(util.EnumSet.of(PUBLIC), new VoidType, methodName) method.setType(completionStageType.setTypeArguments(responseParentType)) val pathParams = parameters.pathParams.map(_.param) @@ -52,11 +60,23 @@ object AsyncHttpClientClientGenerator { val bodyParams = parameters.bodyParams.map(_.param).toList (pathParams ++ qsParams ++ formParams ++ headerParams ++ bodyParams).foreach(method.addParameter) - val httpMethodCallName = s"prepare${httpMethod.toString.toLowerCase(Locale.US).capitalize}" - val httpMethodCallExpr = new MethodCallExpr(new FieldAccessExpr(new ThisExpr, "httpClient"), httpMethodCallName, new NodeList[Expression](new NameExpr("url"))) + val requestBuilder = new MethodCallExpr( + new AssignExpr(new VariableDeclarationExpr( + new VariableDeclarator(REQUEST_BUILDER_TYPE, "builder"), FINAL), + new ObjectCreationExpr(null, REQUEST_BUILDER_TYPE, new NodeList[Expression]( + new StringLiteralExpr(httpMethod.toString) + )), + AssignExpr.Operator.ASSIGN + ), + "setUrl", new NodeList[Expression](pathExpr) + ) - val requestExecuteCall = new MethodCallExpr(new NameExpr("requestBuilder"), "execute") - val requestApplyCall = new MethodCallExpr(requestExecuteCall, "thenApply", new NodeList[Expression]( + val httpMethodCallExpr = new MethodCallExpr( + new FieldAccessExpr(new ThisExpr, "httpClient"), + "apply", + new NodeList[Expression](new MethodCallExpr(new NameExpr("builder"), "build")) + ) + val requestCall = new MethodCallExpr(httpMethodCallExpr, "thenApply", new NodeList[Expression]( new LambdaExpr(new NodeList(new Parameter(RESPONSE_TYPE, "response")), new BlockStmt(new NodeList( new SwitchStmt(new MethodCallExpr(new NameExpr("response"), "getStatusCode"), new NodeList( responses.value.map(response => new SwitchEntryStmt(new IntegerLiteralExpr(response.statusCode), new NodeList(response.value match { @@ -101,9 +121,8 @@ object AsyncHttpClientClientGenerator { )) method.setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new AssignExpr(new VariableDeclarationExpr(new VariableDeclarator(STRING_TYPE, "url"), FINAL), pathExpr, AssignExpr.Operator.ASSIGN)), - new ExpressionStmt(new AssignExpr(new VariableDeclarationExpr(new VariableDeclarator(REQUEST_BUILDER_TYPE, "requestBuilder"), FINAL), httpMethodCallExpr, AssignExpr.Operator.ASSIGN)), - new ExpressionStmt(requestApplyCall) + new ExpressionStmt(requestBuilder), + new ReturnStmt(requestCall) ))) RenderedClientOperation[JavaLanguage](method, List.empty) @@ -119,8 +138,10 @@ object AsyncHttpClientClientGenerator { "com.fasterxml.jackson.datatype.jdk8.Jdk8Module", "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", "org.asynchttpclient.AsyncHttpClient", + "org.asynchttpclient.AsyncHttpClientConfig", "org.asynchttpclient.DefaultAsyncHttpClient", "org.asynchttpclient.DefaultAsyncHttpClientConfig", + "org.asynchttpclient.Request", "org.asynchttpclient.RequestBuilder", "org.asynchttpclient.Response" ).map(safeParseRawImport) ++ List( @@ -169,9 +190,8 @@ object AsyncHttpClientClientGenerator { ))) }) - val functionType = JavaParser.parseClassOrInterfaceType("Function") - functionType.setTypeArguments(responseType, genericTypeParam) - val foldMethodParameter = new Parameter(util.EnumSet.of(FINAL), functionType, new SimpleName(responseLambdaName)) + val foldMethodParamType = functionType.setTypeArguments(responseType, genericTypeParam) + val foldMethodParameter = new Parameter(util.EnumSet.of(FINAL), foldMethodParamType, new SimpleName(responseLambdaName)) val foldMethodBranch = new IfStmt( new InstanceOfExpr(new ThisExpr, responseType), @@ -259,7 +279,7 @@ object AsyncHttpClientClientGenerator { }) builderClass.addFieldWithInitializer( - optionalType.setTypeArguments(ASYNC_HTTP_CLIENT_TYPE), "httpClient", + optionalType.setTypeArguments(HTTP_CLIENT_FUNCTION_TYPE), "httpClient", new MethodCallExpr(new NameExpr("Optional"), "empty"), PRIVATE ) @@ -324,7 +344,7 @@ object AsyncHttpClientClientGenerator { addSetter(URI_TYPE, "baseUrl", nonNullInitializer) } addSetter(STRING_TYPE, "clientName", nonNullInitializer) - addSetter(ASYNC_HTTP_CLIENT_TYPE, "httpClient", optionalInitializer(new NameExpr(_))) + addSetter(HTTP_CLIENT_FUNCTION_TYPE, "httpClient", optionalInitializer(new NameExpr(_))) addSetter(OBJECT_MAPPER_TYPE, "objectMapper", optionalInitializer(name => new MethodCallExpr("configureObjectMapper", new NameExpr(name)))) @@ -347,27 +367,39 @@ object AsyncHttpClientClientGenerator { )) ))) } - addInternalGetter(ASYNC_HTTP_CLIENT_TYPE, "httpClient", + addInternalGetter(HTTP_CLIENT_FUNCTION_TYPE, "httpClient", new MethodCallExpr("createDefaultHttpClient")) addInternalGetter(OBJECT_MAPPER_TYPE, "objectMapper", new MethodCallExpr("configureObjectMapper", new ObjectCreationExpr(null, OBJECT_MAPPER_TYPE, new NodeList()))) - val createDefaultHttpClientMethod = builderClass.addMethod("createDefaultHttpClient", PRIVATE, STATIC) - createDefaultHttpClientMethod.setType(ASYNC_HTTP_CLIENT_TYPE) - createDefaultHttpClientMethod.setBody(new BlockStmt(new NodeList(new ReturnStmt({ - val configBuilder = new ObjectCreationExpr(null, DEFAULT_ASYNC_HTTP_CLIENT_CONFIG_BUILDER_TYPE, new NodeList()) - val configBuilderChain = List( + val ahcConfigBuilder = new ObjectCreationExpr(null, DEFAULT_ASYNC_HTTP_CLIENT_CONFIG_BUILDER_TYPE, new NodeList()) + val ahcConfig = new MethodCallExpr( + List( ("setMaxRequestRetry", 2), ("setConnectTimeout", 3000), ("setRequestTimeout", 10000), ("setReadTimeout", 3000), ("setMaxConnections", 1024), ("setMaxConnectionsPerHost", 1024) - ).foldLeft[Expression](configBuilder)({ case (lastExpr, (name, arg)) => + ).foldLeft[Expression](ahcConfigBuilder)({ case (lastExpr, (name, arg)) => new MethodCallExpr(lastExpr, name, new NodeList[Expression](new IntegerLiteralExpr(arg))) - }) - new MethodCallExpr(configBuilderChain, "build", new NodeList[Expression]()) - })))) + }), "build", new NodeList[Expression]()) + + val createDefaultHttpClientMethod = builderClass.addMethod("createDefaultHttpClient", PRIVATE, STATIC) + createDefaultHttpClientMethod.setType(HTTP_CLIENT_FUNCTION_TYPE) + createDefaultHttpClientMethod.setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new VariableDeclarationExpr(new VariableDeclarator(ASYNC_HTTP_CLIENT_CONFIG_TYPE, "config", ahcConfig), FINAL)), + new ExpressionStmt(new VariableDeclarationExpr(new VariableDeclarator( + ASYNC_HTTP_CLIENT_TYPE, + "client", + new ObjectCreationExpr(null, DEFAULT_ASYNC_HTTP_CLIENT_TYPE, new NodeList(new NameExpr("config"))) + ), FINAL)), + new ReturnStmt(new LambdaExpr( + new NodeList(new Parameter(util.EnumSet.of(FINAL), REQUEST_TYPE, new SimpleName("request"))), + new ExpressionStmt(new MethodCallExpr(new MethodCallExpr(new NameExpr("client"), "executeRequest", new NodeList[Expression](new NameExpr("request"))), "toCompletableFuture")), + true + )) + ))) val configureObjectMapperMethod = builderClass.addMethod("configureObjectMapper", PRIVATE, STATIC) configureObjectMapperMethod.setType(OBJECT_MAPPER_TYPE) @@ -388,7 +420,7 @@ object AsyncHttpClientClientGenerator { List( (URI_TYPE, "baseUrl"), (STRING_TYPE, "clientName"), - (ASYNC_HTTP_CLIENT_TYPE, "httpClient"), + (HTTP_CLIENT_FUNCTION_TYPE, "httpClient"), (OBJECT_MAPPER_TYPE, "objectMapper") ).foreach({ case (tpe, name) => clientClass.addField(tpe, name, PRIVATE, FINAL) }) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index 4a397c3e53..d8556260ef 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -37,6 +37,7 @@ object Java { def completionStageType: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("CompletionStage" ) def optionalType: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Optional") + def functionType: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Function") val STRING_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("String") val THROWABLE_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Throwable") From 0a7505e952eeb9cf0a4b8ef30169e4b83a8a164e Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Mon, 4 Mar 2019 21:59:39 -0800 Subject: [PATCH 36/86] Rough operation method parameter impl for AbstractHttpClient --- .../Java/AsyncHttpClientClientGenerator.scala | 150 +++++++++++++++++- .../generators/Java/DropwizardGenerator.scala | 2 +- .../generators/Java/JacksonGenerator.scala | 5 +- .../guardrail/generators/syntax/Java.scala | 34 +++- 4 files changed, 177 insertions(+), 14 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index 180a0b633e..b4be7dbb3f 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -5,22 +5,22 @@ import cats.instances.list._ import cats.syntax.traverse._ import cats.~> import com.github.javaparser.JavaParser -import com.github.javaparser.ast.{ImportDeclaration, NodeList} +import com.github.javaparser.ast.NodeList import com.github.javaparser.ast.Modifier._ import com.github.javaparser.ast.`type`.{ClassOrInterfaceType, Type, VoidType} import com.github.javaparser.ast.body.{ClassOrInterfaceDeclaration, MethodDeclaration, Parameter, VariableDeclarator} import com.github.javaparser.ast.expr.{MethodCallExpr, NameExpr, _} import com.github.javaparser.ast.stmt._ import com.twilio.guardrail.SwaggerUtil.jpaths -import com.twilio.guardrail.generators.Response +import com.twilio.guardrail.generators.{Response, ScalaParameter} import com.twilio.guardrail.generators.syntax.Java._ import com.twilio.guardrail.languages.JavaLanguage import com.twilio.guardrail.protocol.terms.client._ import com.twilio.guardrail.terms.RouteMeta -import com.twilio.guardrail.{RenderedClientOperation, StaticDefns, Target} +import com.twilio.guardrail.{RenderedClientOperation, StaticDefns, Target, languages} import java.net.URI import java.util -import java.util.Locale +import javax.lang.model.`type`.PrimitiveType object AsyncHttpClientClientGenerator { private val URI_TYPE = JavaParser.parseClassOrInterfaceType("URI") @@ -31,17 +31,141 @@ object AsyncHttpClientClientGenerator { private val REQUEST_BUILDER_TYPE = JavaParser.parseClassOrInterfaceType("RequestBuilder") private val REQUEST_TYPE = JavaParser.parseClassOrInterfaceType("Request") private val RESPONSE_TYPE = JavaParser.parseClassOrInterfaceType("Response") + private val FILE_PART_TYPE = JavaParser.parseClassOrInterfaceType("FilePart") + private val STRING_PART_TYPE = JavaParser.parseClassOrInterfaceType("StringPart") private val OBJECT_MAPPER_TYPE = JavaParser.parseClassOrInterfaceType("ObjectMapper") private val BUILDER_TYPE = JavaParser.parseClassOrInterfaceType("Builder") private val MARSHALLING_EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("MarshallingException") private val HTTP_ERROR_TYPE = JavaParser.parseClassOrInterfaceType("HttpError") private val EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("Exception") + private val ILLEGAL_ARGUMENT_EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("IllegalArgumentException") + private val JSON_PROCESSING_EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("JsonProcessingException") private val HTTP_CLIENT_FUNCTION_TYPE = functionType.setTypeArguments(new NodeList[Type]( REQUEST_TYPE, completionStageType.setTypeArguments(RESPONSE_TYPE) )) + private def showParam(param: ScalaParameter[JavaLanguage], overrideParamName: Option[String] = None): Expression = { + val paramName = overrideParamName.getOrElse(param.paramName.asString) + + def doShow(tpe: Type): Expression = tpe match { + case _: PrimitiveType => + new MethodCallExpr(new NameExpr("String"), "valueOf", new NodeList[Expression](new NameExpr(paramName))) + case cls: ClassOrInterfaceType if cls.isOptional => + doShow(cls.containedType) + case cls: ClassOrInterfaceType if cls.isBoxedType => + new MethodCallExpr(new NameExpr(paramName), "toString") + case cls: ClassOrInterfaceType if cls.isNamed("List") => + doShow(cls.containedType) + case cls: ClassOrInterfaceType if cls.getName.asString() == "String" => + new NameExpr(paramName) + case _: ClassOrInterfaceType => + // FIXME: this will cover our autogenerated enum types, but it would be really nice if we could at identify + // our enum times via a list of some sort. this will fail to compile in most non-enum cases, but there's + // the possibility that this could generate incorrect but compilable code. + new MethodCallExpr(new NameExpr(paramName), "getValue") + case _: VoidType => + new NullLiteralExpr + case other => + println(s"WARN: Unhandled arg type ${other.getClass.getName} for arg typed ${other.name} ${param.paramName}") + new NameExpr("UNSUPPORTED_PARAMETER_TYPE_PLEASE_FILE_AN_ISSUE") + } + + doShow(param.argType) + } + + private def optionIfPresent(optionVarType: Type, optionVarName: String, innerStatement: Statement): Statement = { + new ExpressionStmt(new MethodCallExpr(new NameExpr(optionVarName), "ifPresent", new NodeList[Expression]( + new LambdaExpr(new NodeList(new Parameter(util.EnumSet.of(FINAL), optionVarType, new SimpleName("arg"))), + innerStatement, + true + )) + )) + } + + private def generateBuilderMethodCalls(params: List[ScalaParameter[JavaLanguage]], builderMethodName: String): List[Statement] = { + val needsMultipart = params.exists(_.isFile) + params.map({ param => + val finalMethodName = if (needsMultipart) "addBodyPart" else builderMethodName + val argName = if (param.required) param.paramName.asString else "arg" + val containedType = param.argType.containedType + val isList = if (param.required) param.argType.isNamed("List") else containedType.isNamed("List") + val listType = if (param.required) containedType else containedType.containedType + + val makeArgList: String => NodeList[Expression] = name => + if (param.isFile) { + new NodeList[Expression](new ObjectCreationExpr(null, FILE_PART_TYPE, new NodeList( + new StringLiteralExpr(param.argName.value), + new NameExpr(name) + ))) + } else if (needsMultipart) { + new NodeList[Expression](new ObjectCreationExpr(null, STRING_PART_TYPE, new NodeList( + new StringLiteralExpr(param.argName.value), + showParam(param, Some(name)) + ))) + } else { + new NodeList[Expression](new StringLiteralExpr(param.argName.value), showParam(param, Some(name))) + } + + val builderStatement: Statement = if (isList) { + new ForEachStmt( + new VariableDeclarationExpr(listType, "member", FINAL), + new NameExpr(argName), + new BlockStmt(new NodeList( + new ExpressionStmt(new MethodCallExpr(new NameExpr("builder"), finalMethodName, makeArgList("member"))) + )) + ) + } else { + new ExpressionStmt(new MethodCallExpr(new NameExpr("builder"), finalMethodName, makeArgList(argName))) + } + + if (param.required) { + builderStatement + } else { + optionIfPresent(containedType, param.paramName.asString, builderStatement) + } + }) + } + + private def generateBodyMethodCall(param: Option[ScalaParameter[JavaLanguage]]): Option[Statement] = { + def wrapSetBody(expr: Expression): MethodCallExpr = { + new MethodCallExpr(new NameExpr("builder"), "setBody", new NodeList[Expression](expr)) + } + + param.map({ param => + if (param.isFile) { + new ExpressionStmt(wrapSetBody(new ObjectCreationExpr(null, FILE_PART_TYPE, new NodeList( + new StringLiteralExpr(param.argName.value), + new NameExpr(param.paramName.asString) + )))) + } else { + new TryStmt( + new BlockStmt(new NodeList( + new ExpressionStmt(wrapSetBody(new MethodCallExpr( + new FieldAccessExpr(new ThisExpr, "objectMapper"), + "writeValueAsString", + new NodeList[Expression](new NameExpr(param.paramName.asString)) + ))) + )), + new NodeList( + new CatchClause( + new Parameter(util.EnumSet.of(FINAL), JSON_PROCESSING_EXCEPTION_TYPE, new SimpleName("e")), + new BlockStmt(new NodeList( + new ThrowStmt(new ObjectCreationExpr( + null, + ILLEGAL_ARGUMENT_EXCEPTION_TYPE, + new NodeList(new MethodCallExpr(new NameExpr("e"), "getMessage"), new NameExpr("e")) + )) + )) + ) + ), + null + ) + } + }) + } + object ClientTermInterp extends (ClientTerm[JavaLanguage, ?] ~> Target) { def apply[T](term: ClientTerm[JavaLanguage, T]): Target[T] = term match { case GenerateClientOperation(_, RouteMeta(pathStr, httpMethod, operation), methodName, tracing, parameters, responses) => @@ -71,13 +195,20 @@ object AsyncHttpClientClientGenerator { "setUrl", new NodeList[Expression](pathExpr) ) + val builderMethodCalls: List[Statement] = List( + generateBuilderMethodCalls(parameters.queryStringParams, "addQueryParam"), + generateBuilderMethodCalls(parameters.formParams, "addFormParam"), + generateBuilderMethodCalls(parameters.headerParams, "addHeader"), + generateBodyMethodCall(parameters.bodyParams).toList + ).flatten + val httpMethodCallExpr = new MethodCallExpr( new FieldAccessExpr(new ThisExpr, "httpClient"), "apply", new NodeList[Expression](new MethodCallExpr(new NameExpr("builder"), "build")) ) val requestCall = new MethodCallExpr(httpMethodCallExpr, "thenApply", new NodeList[Expression]( - new LambdaExpr(new NodeList(new Parameter(RESPONSE_TYPE, "response")), new BlockStmt(new NodeList( + new LambdaExpr(new NodeList(new Parameter(util.EnumSet.of(FINAL), RESPONSE_TYPE, new SimpleName("response"))), new BlockStmt(new NodeList( new SwitchStmt(new MethodCallExpr(new NameExpr("response"), "getStatusCode"), new NodeList( responses.value.map(response => new SwitchEntryStmt(new IntegerLiteralExpr(response.statusCode), new NodeList(response.value match { case None => new ReturnStmt(new ObjectCreationExpr(null, JavaParser.parseClassOrInterfaceType(s"${responseParentName}.${response.statusCodeName.asString}"), new NodeList())) @@ -121,8 +252,9 @@ object AsyncHttpClientClientGenerator { )) method.setBody(new BlockStmt(new NodeList( - new ExpressionStmt(requestBuilder), - new ReturnStmt(requestCall) + new ExpressionStmt(requestBuilder) +: + builderMethodCalls :+ + new ReturnStmt(requestCall): _* ))) RenderedClientOperation[JavaLanguage](method, List.empty) @@ -134,6 +266,7 @@ object AsyncHttpClientClientGenerator { "java.util.Optional", "java.util.concurrent.CompletionStage", "java.util.function.Function", + "com.fasterxml.jackson.core.JsonProcessingException", "com.fasterxml.jackson.databind.ObjectMapper", "com.fasterxml.jackson.datatype.jdk8.Jdk8Module", "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", @@ -143,7 +276,8 @@ object AsyncHttpClientClientGenerator { "org.asynchttpclient.DefaultAsyncHttpClientConfig", "org.asynchttpclient.Request", "org.asynchttpclient.RequestBuilder", - "org.asynchttpclient.Response" + "org.asynchttpclient.request.body.multipart.FilePart", + "org.asynchttpclient.request.body.multipart.StringPart" ).map(safeParseRawImport) ++ List( "java.util.Objects.requireNonNull" ).map(safeParseRawStaticImport)).sequence diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala index a5817a4b08..3e481ddf47 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala @@ -20,7 +20,7 @@ object DropwizardGenerator { object FrameworkInterp extends (FrameworkTerm[JavaLanguage, ?] ~> Target) { def apply[T](term: FrameworkTerm[JavaLanguage, T]): Target[T] = term match { - case FileType(format) => safeParseType(format.getOrElse("org.asynchttpclient.request.body.multipart.FilePart")) + case FileType(format) => safeParseType(format.getOrElse("java.io.File")) case ObjectType(format) => safeParseType("com.fasterxml.jackson.databind.JsonNode") case GetFrameworkImports(tracing) => diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala index e14296adee..3047ee4a06 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -35,7 +35,7 @@ object JacksonGenerator { private def sortParams(params: List[ProtocolParameter[JavaLanguage]]): (List[ParameterTerm], List[ParameterTerm]) = { // TODO: if a required field has a default specified, include it in optionalTerms instead val (req, opt) = params.partition(_.term.getType match { - case cls: ClassOrInterfaceType => !isOptionalType(cls) + case cls: ClassOrInterfaceType => !cls.isOptional case _ => true }) @@ -71,9 +71,6 @@ object JacksonGenerator { }) } - private def isOptionalType(cls: ClassOrInterfaceType): Boolean = - (cls.getScope.asScala.fold("")(_.asString + ".") + cls.getName.asString) == "java.util.Optional" - private def lookupTypeName(tpeName: String, concreteTypes: List[PropMeta[JavaLanguage]])(f: Type => Target[Type]): Option[Target[Type]] = concreteTypes .find(_.clsName == tpeName) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index d8556260ef..a56010a2e8 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -4,11 +4,12 @@ import cats.implicits._ import com.github.javaparser.JavaParser import com.github.javaparser.ast.{CompilationUnit, ImportDeclaration} import com.github.javaparser.ast.`type`.{ClassOrInterfaceType, Type} -import com.github.javaparser.ast.body.Parameter +import com.github.javaparser.ast.body.{ClassOrInterfaceDeclaration, Parameter} import com.github.javaparser.ast.expr.{Expression, Name, SimpleName} import com.github.javaparser.printer.PrettyPrinterConfiguration import com.github.javaparser.printer.PrettyPrinterConfiguration.IndentType import com.twilio.guardrail.Target +import java.nio.charset.StandardCharsets import java.util.Optional import scala.reflect.ClassTag import scala.util.Try @@ -18,6 +19,37 @@ object Java { def asScala: Option[T] = if (o.isPresent) Option(o.get) else None } + implicit class RichType(val tpe: Type) extends AnyVal { + def isOptional: Boolean = + tpe match { + case cls: ClassOrInterfaceType => + val scope = cls.getScope.asScala + cls.getNameAsString == "Optional" && (scope.isEmpty || scope.map(_.asString).contains("java.util")) + case _ => false + } + + def containedType: Type = + tpe match { + case cls: ClassOrInterfaceType => cls.getTypeArguments.asScala.filter(_.size == 1).fold(tpe)(_.get(0)) + case _ => tpe + } + + def isNamed(name: String): Boolean = + tpe match { + case cls: ClassOrInterfaceType if name.contains(".") => + (cls.getScope.asScala.fold("")(_.getName.asString + ".") + cls.getNameAsString) == name + case cls: ClassOrInterfaceType => cls.getNameAsString == name + case _ => false + } + + def name: Option[String] = + tpe match { + case cls: ClassOrInterfaceType => + Some(cls.getScope.asScala.fold("")(_.getName.asString + ".") + cls.getNameAsString) + case _ => None + } + } + private[this] def safeParse[T](log: String)(parser: String => T, s: String)(implicit cls: ClassTag[T]): Target[T] = { Target.log.debug(log)(s) >> ( Try(parser(s)).toEither.fold(t => Target.raiseError(s"Unable to parse '${s}' to a ${cls.runtimeClass.getName}: ${t.getMessage}"), Target.pure) From 4bd7fe42e096bbdf13a8b2cf127debaffbb5ea56 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Tue, 5 Mar 2019 02:59:22 -0800 Subject: [PATCH 37/86] Don't include clientName/tracingName in AHC if tracing disabled --- .../Java/AsyncHttpClientClientGenerator.scala | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index b4be7dbb3f..23e9b85369 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -371,6 +371,7 @@ object AsyncHttpClientClientGenerator { Target.pure(List(abstractResponseClass)) case BuildStaticDefns(clientName, tracingName, serverUrls, ctorArgs, tracing) => + Target.pure( StaticDefns[JavaLanguage]( className = clientName, @@ -386,8 +387,6 @@ object AsyncHttpClientClientGenerator { val serverUrl = serverUrls.map(_.head).map(uri => new URI(uri.toString + basePath.getOrElse(""))) val baseUrlField = builderClass.addField(URI_TYPE, "baseUrl", PRIVATE, FINAL) - val clientNameField = builderClass.addField(STRING_TYPE, "clientName", PRIVATE, FINAL) - serverUrl.foreach({ serverUrl => builderClass.addFieldWithInitializer( URI_TYPE, "DEFAULT_BASE_URL", @@ -402,15 +401,18 @@ object AsyncHttpClientClientGenerator { baseUrlField.getVariables.iterator().next().setInitializer(new NameExpr("DEFAULT_BASE_URL")) }) - tracingName.foreach({ tracingName => - builderClass.addFieldWithInitializer( - STRING_TYPE, "DEFAULT_CLIENT_NAME", - new StringLiteralExpr(tracingName), - PRIVATE, STATIC, FINAL - ) - clientNameField.setFinal(false) - clientNameField.getVariables.iterator().next().setInitializer(new NameExpr("DEFAULT_CLIENT_NAME")) - }) + if (tracing) { + val clientNameField = builderClass.addField(STRING_TYPE, "clientName", PRIVATE, FINAL) + tracingName.foreach({ tracingName => + builderClass.addFieldWithInitializer( + STRING_TYPE, "DEFAULT_CLIENT_NAME", + new StringLiteralExpr(tracingName), + PRIVATE, STATIC, FINAL + ) + clientNameField.setFinal(false) + clientNameField.getVariables.iterator().next().setInitializer(new NameExpr("DEFAULT_CLIENT_NAME")) + }) + } builderClass.addFieldWithInitializer( optionalType.setTypeArguments(HTTP_CLIENT_FUNCTION_TYPE), "httpClient", @@ -434,7 +436,7 @@ object AsyncHttpClientClientGenerator { ) } (serverUrl, tracingName) match { - case (None, None) => + case (None, None) if tracing => builderConstructor.setParameters(new NodeList( createConstructorParameter(URI_TYPE, "baseUrl"), createConstructorParameter(STRING_TYPE, "clientName") @@ -444,7 +446,7 @@ object AsyncHttpClientClientGenerator { createBuilderConstructorAssignment("clientName") ))) - case (Some(_), None) => + case (Some(_), None) if tracing => builderConstructor.setParameters(new NodeList( createConstructorParameter(STRING_TYPE, "clientName") )) @@ -452,7 +454,7 @@ object AsyncHttpClientClientGenerator { createBuilderConstructorAssignment("clientName") ))) - case (None, Some(_)) => + case (None, _) => builderConstructor.setParameters(new NodeList( createConstructorParameter(URI_TYPE, "baseUrl") )) @@ -461,6 +463,8 @@ object AsyncHttpClientClientGenerator { ))) case (Some(_), Some(_)) => // no params + + case (Some(_), _) if !tracing => // no params } def addSetter(tpe: Type, name: String, initializer: String => Expression): Unit = { @@ -477,7 +481,9 @@ object AsyncHttpClientClientGenerator { if (serverUrl.isDefined) { addSetter(URI_TYPE, "baseUrl", nonNullInitializer) } - addSetter(STRING_TYPE, "clientName", nonNullInitializer) + if (tracing) { + addSetter(STRING_TYPE, "clientName", nonNullInitializer) + } addSetter(HTTP_CLIENT_FUNCTION_TYPE, "httpClient", optionalInitializer(new NameExpr(_))) addSetter(OBJECT_MAPPER_TYPE, "objectMapper", optionalInitializer(name => new MethodCallExpr("configureObjectMapper", new NameExpr(name)))) @@ -552,23 +558,23 @@ object AsyncHttpClientClientGenerator { val clientClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, clientName) List( - (URI_TYPE, "baseUrl"), - (STRING_TYPE, "clientName"), - (HTTP_CLIENT_FUNCTION_TYPE, "httpClient"), - (OBJECT_MAPPER_TYPE, "objectMapper") - ).foreach({ case (tpe, name) => clientClass.addField(tpe, name, PRIVATE, FINAL) }) + Some((URI_TYPE, "baseUrl")), + if (tracing) Some((STRING_TYPE, "clientName")) else None, + Some((HTTP_CLIENT_FUNCTION_TYPE, "httpClient")), + Some((OBJECT_MAPPER_TYPE, "objectMapper")) + ).flatten.foreach({ case (tpe, name) => clientClass.addField(tpe, name, PRIVATE, FINAL) }) val constructor = clientClass.addConstructor(PRIVATE) constructor.addParameter(new Parameter(util.EnumSet.of(FINAL), BUILDER_TYPE, new SimpleName("builder"))) def newFieldAccessExpr(scope: Expression, name: String): Expression = new FieldAccessExpr(scope, name) def newMethodCallExpr(scope: Expression, name: String): Expression = new MethodCallExpr(scope, s"get${name.capitalize}") constructor.setBody(new BlockStmt(new NodeList( - List[(String, (Expression, String) => Expression)]( - ("baseUrl", newFieldAccessExpr), - ("clientName", newFieldAccessExpr), - ("httpClient", newMethodCallExpr), - ("objectMapper", newMethodCallExpr) - ).map({ case (name, value) => + List[Option[(String, (Expression, String) => Expression)]]( + Some(("baseUrl", newFieldAccessExpr)), + if (tracing) Some(("clientName", newFieldAccessExpr)) else None, + Some(("httpClient", newMethodCallExpr)), + Some(("objectMapper", newMethodCallExpr)) + ).flatten.map({ case (name, value) => new ExpressionStmt(new AssignExpr( new FieldAccessExpr(new ThisExpr, name), value(new NameExpr("builder"), name), From 5ce81766a571e0d3b81feebb6fdadba0c4536d6f Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Tue, 5 Mar 2019 13:20:25 -0800 Subject: [PATCH 38/86] Add defaultValue member to ProtocolParameter --- .../main/scala/com/twilio/guardrail/ProtocolGenerator.scala | 3 ++- .../twilio/guardrail/generators/CirceProtocolGenerator.scala | 2 +- .../twilio/guardrail/generators/Java/JacksonGenerator.scala | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala index a2d90f3858..8317d3fa59 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala @@ -26,7 +26,8 @@ case class ProtocolParameter[L <: LA](term: L#MethodParameter, name: String, dep: Option[L#TermName], readOnlyKey: Option[String], - emptyToNull: EmptyToNullBehaviour) + emptyToNull: EmptyToNullBehaviour, + defaultValue: Option[L#Term]) case class SuperClass[L <: LA]( clsName: String, diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala index 6299a3b5d7..0d48d13637 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala @@ -166,7 +166,7 @@ object CirceProtocolGenerator { )(Function.const((tpe, defaultValue)) _) term = param"${Term.Name(argName)}: ${finalDeclType}".copy(default = finalDefaultValue) dep = classDep.filterNot(_.value == clsName) // Filter out our own class name - } yield ProtocolParameter[ScalaLanguage](term, name, dep, readOnlyKey, emptyToNull) + } yield ProtocolParameter[ScalaLanguage](term, name, dep, readOnlyKey, emptyToNull, finalDefaultValue) case RenderDTOClass(clsName, selfParams, parents) => val discriminators = parents.flatMap(_.discriminators) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala index 3047ee4a06..d455f78521 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -269,9 +269,9 @@ object JacksonGenerator { (safeParseType(s"java.util.Optional<${tpe}>"), Option(defaultValue.fold[Target[Expression]](safeParseExpression[Expression](s"java.util.Optional.empty()"))(t => safeParseExpression[Expression](s"java.util.Optional.of($t)"))).sequence).mapN((_, _)) )(Function.const(Target.pure((tpe, defaultValue))) _) (finalDeclType, finalDefaultValue) = _declDefaultPair - term <- safeParseParameter(s"${finalDeclType} ${argName}") // FIXME: How do we deal with default values? .copy(default = finalDefaultValue) + term <- safeParseParameter(s"${finalDeclType} ${argName}") dep = classDep.filterNot(_.value == clsName) // Filter out our own class name - } yield ProtocolParameter[JavaLanguage](term, name, dep, readOnlyKey, emptyToNull) + } yield ProtocolParameter[JavaLanguage](term, name, dep, readOnlyKey, emptyToNull, defaultValue) } case RenderDTOClass(clsName, selfParams, parents) => From 30e7265ec0ede7b79c2aa1d8a411042cae145cc9 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Tue, 5 Mar 2019 14:32:37 -0800 Subject: [PATCH 39/86] Error out in AsyncHttpClient if tracing is requested --- .../Java/AsyncHttpClientClientGenerator.scala | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index 23e9b85369..ac5f6d29d3 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -261,26 +261,30 @@ object AsyncHttpClientClientGenerator { } case GetImports(tracing) => - (List( - "java.net.URI", - "java.util.Optional", - "java.util.concurrent.CompletionStage", - "java.util.function.Function", - "com.fasterxml.jackson.core.JsonProcessingException", - "com.fasterxml.jackson.databind.ObjectMapper", - "com.fasterxml.jackson.datatype.jdk8.Jdk8Module", - "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", - "org.asynchttpclient.AsyncHttpClient", - "org.asynchttpclient.AsyncHttpClientConfig", - "org.asynchttpclient.DefaultAsyncHttpClient", - "org.asynchttpclient.DefaultAsyncHttpClientConfig", - "org.asynchttpclient.Request", - "org.asynchttpclient.RequestBuilder", - "org.asynchttpclient.request.body.multipart.FilePart", - "org.asynchttpclient.request.body.multipart.StringPart" - ).map(safeParseRawImport) ++ List( - "java.util.Objects.requireNonNull" - ).map(safeParseRawStaticImport)).sequence + if (tracing) { + Target.raiseError("Tracing is not yet supported by this framework") + } else { + (List( + "java.net.URI", + "java.util.Optional", + "java.util.concurrent.CompletionStage", + "java.util.function.Function", + "com.fasterxml.jackson.core.JsonProcessingException", + "com.fasterxml.jackson.databind.ObjectMapper", + "com.fasterxml.jackson.datatype.jdk8.Jdk8Module", + "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", + "org.asynchttpclient.AsyncHttpClient", + "org.asynchttpclient.AsyncHttpClientConfig", + "org.asynchttpclient.DefaultAsyncHttpClient", + "org.asynchttpclient.DefaultAsyncHttpClientConfig", + "org.asynchttpclient.Request", + "org.asynchttpclient.RequestBuilder", + "org.asynchttpclient.request.body.multipart.FilePart", + "org.asynchttpclient.request.body.multipart.StringPart" + ).map(safeParseRawImport) ++ List( + "java.util.Objects.requireNonNull" + ).map(safeParseRawStaticImport)).sequence + } case GetExtraImports(tracing) => Target.pure(List.empty) @@ -371,7 +375,6 @@ object AsyncHttpClientClientGenerator { Target.pure(List(abstractResponseClass)) case BuildStaticDefns(clientName, tracingName, serverUrls, ctorArgs, tracing) => - Target.pure( StaticDefns[JavaLanguage]( className = clientName, From 0eea0d59cc1fe9aa7db7977a87d966bbfdde0814 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Tue, 5 Mar 2019 14:36:37 -0800 Subject: [PATCH 40/86] Escape reserved words in Java --- .../generators/Java/JacksonGenerator.scala | 2 +- .../twilio/guardrail/generators/JavaGenerator.scala | 6 +++--- .../twilio/guardrail/generators/syntax/Java.scala | 13 +++++++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala index d455f78521..155224112c 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -269,7 +269,7 @@ object JacksonGenerator { (safeParseType(s"java.util.Optional<${tpe}>"), Option(defaultValue.fold[Target[Expression]](safeParseExpression[Expression](s"java.util.Optional.empty()"))(t => safeParseExpression[Expression](s"java.util.Optional.of($t)"))).sequence).mapN((_, _)) )(Function.const(Target.pure((tpe, defaultValue))) _) (finalDeclType, finalDefaultValue) = _declDefaultPair - term <- safeParseParameter(s"${finalDeclType} ${argName}") + term <- safeParseParameter(s"final ${finalDeclType} ${argName.escapeReservedWord}") dep = classDep.filterNot(_.value == clsName) // Filter out our own class name } yield ProtocolParameter[JavaLanguage](term, name, dep, readOnlyKey, emptyToNull, defaultValue) } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala index f9abbf4387..3ceecc486d 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala @@ -79,14 +79,14 @@ object JavaGenerator { Option(tpe).map(_.trim).filterNot(_.isEmpty).map(safeParseName).sequence case PureTermName(tpe) => - Option(tpe).map(_.trim).filterNot(_.isEmpty).map(safeParseName).getOrElse(Target.raiseError("A structure's name is empty")) + Option(tpe).map(_.trim).filterNot(_.isEmpty).map(_.escapeReservedWord).map(safeParseName).getOrElse(Target.raiseError("A structure's name is empty")) case PureTypeName(tpe) => Option(tpe).map(_.trim).filterNot(_.isEmpty).map(safeParseName).getOrElse(Target.raiseError("A structure's name is empty")) case PureMethodParameter(nameStr, tpe, default) => // FIXME: java methods do not support default param values -- what should we do here? - safeParseSimpleName(nameStr.asString).map(name => new Parameter(util.EnumSet.of(Modifier.FINAL), tpe, name)) + safeParseSimpleName(nameStr.asString.escapeReservedWord).map(name => new Parameter(util.EnumSet.of(Modifier.FINAL), tpe, name)) case TypeNamesEqual(a, b) => Target.pure(a.asString == b.asString) @@ -112,7 +112,7 @@ object JavaGenerator { Target.pure(term.asString) case AlterMethodParameterName(param, name) => - safeParseSimpleName(name.asString).map(new Parameter( + safeParseSimpleName(name.asString.escapeReservedWord).map(new Parameter( param.getTokenRange.orElse(null), param.getModifiers, param.getAnnotations, diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index a56010a2e8..6d00c35458 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -85,6 +85,19 @@ object Java { .setPrintJavadoc(true) .setTabWidth(4) + // from https://en.wikipedia.org/wiki/List_of_Java_keywords + private val reservedWords = Set( + "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", + "do", "double", "else", "enum", "exports", "extends", "false", "final", "finally", "float", "for", "goto", "if", + "implements", "import", "instanceof", "int", "interface", "long", "module", "native", "new", "null", "package", + "private", "protected", "public", "requires", "return", "short", "static", "strictfp", "super", "switch", + "synchronized", "this", "throw", "throws", "transient", "true", "try", "var", "void", "volatile", "while" + ) + + implicit class RichJavaString(val s: String) extends AnyVal { + def escapeReservedWord: String = if (reservedWords.contains(s)) s + "_" else s + } + /* implicit class PrintStructure(value: Node) { def toAST: String = { From 1830b7a6523224c7ebfbf573983266b413e69208 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Tue, 5 Mar 2019 15:51:57 -0800 Subject: [PATCH 41/86] Add initial Java compilation & tests --- .gitignore | 3 ++ build.sbt | 33 +++++++++++++++---- .../src/main/java/support/PositiveLong.java | 30 +++++++++++++++++ .../core/Dropwizard/DropwizardServerTest.java | 10 ++++++ 4 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 modules/sample-dropwizard/src/main/java/support/PositiveLong.java create mode 100644 modules/sample-dropwizard/src/test/java/core/Dropwizard/DropwizardServerTest.java diff --git a/.gitignore b/.gitignore index c332c980f5..067879c320 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,11 @@ target/ modules/sample*/target/ modules/sample*/src/main/scala/* modules/sample*/src/main/scala/generated/* +modules/sample*/src/main/java/* +modules/sample*/src/main/java/generated/* !modules/sample*/src/main/scala/App.scala !modules/sample*/src/main/scala/support +!modules/sample*/src/main/java/support !modules/sample*/src/test/resources/ .idea diff --git a/build.sbt b/build.sbt index 77686e3615..d1e69021b3 100644 --- a/build.sbt +++ b/build.sbt @@ -19,6 +19,8 @@ val http4sVersion = "0.19.0" val scalatestVersion = "3.0.7" val javaparserVersion = "3.7.1" val endpointsVersion = "0.8.0" +val ahcVersion = "2.8.1" +val dropwizardVersion = "1.3.9" mainClass in assembly := Some("com.twilio.guardrail.CLI") @@ -58,7 +60,7 @@ val exampleJavaArgs: List[List[String]] = exampleCases ( List(s"--${kind}") ++ List("--specPath", path.toString()) ++ - List("--outputPath", s"modules/sample/src/main/java/generated") ++ + List("--outputPath", s"modules/sample-${frameworkPackage}/src/main/java/generated") ++ List("--packageName", s"${prefix}.${kind}.${frameworkPackage}") ++ List("--framework", frameworkName) ) ++ tracingFlag ++ extra) @@ -110,11 +112,12 @@ artifact in (Compile, assembly) := { addArtifact(artifact in (Compile, assembly), assembly) val resetSample = TaskKey[Unit]("resetSample", "Reset sample module") -val frameworks = List("akkaHttp", "endpoints", "http4s") +val scalaFrameworks = List("akkaHttp", "endpoints", "http4s") +val javaFrameworks = List("dropwizard") resetSample := { import scala.sys.process._ - (List("sample") ++ frameworks.map(x => s"sample-${x}")) + (List("sample") ++ (scalaFrameworks ++ javaFrameworks).map(x => s"sample-${x}")) .foreach(sampleName => s"git clean -fdx modules/${sampleName}/src modules/${sampleName}/target" !) } @@ -122,10 +125,12 @@ resetSample := { addCommandAlias("example", "runtimeSuite") addCommandAlias("cli", "runMain com.twilio.guardrail.CLI") -addCommandAlias("runtimeSuite", "; resetSample ; runScalaExample ; " + frameworks.map(x => s"${x}Sample/test").mkString("; ")) +addCommandAlias("runtimeScalaSuite", "; resetSample ; runScalaExample ; " + scalaFrameworks.map(x => s"${x}Sample/test").mkString("; ")) +addCommandAlias("runtimeJavaSuite", "; resetSample ; runJavaExample ; " + javaFrameworks.map(x => s"${x}Sample/test").mkString("; ")) +addCommandAlias("runtimeSuite", "runtimeScalaSuite ; runtimeJavaSuite") addCommandAlias("scalaTestSuite", "; codegen/test ; runtimeSuite") -addCommandAlias("format", "; codegen/scalafmt ; codegen/test:scalafmt ; " + frameworks.map(x => s"${x}Sample/scalafmt ; ${x}Sample/test:scalafmt").mkString("; ")) -addCommandAlias("checkFormatting", "; codegen/scalafmtCheck ; " + frameworks.map(x => s"${x}Sample/scalafmtCheck ; ${x}Sample/test:scalafmtCheck").mkString("; ")) +addCommandAlias("format", "; codegen/scalafmt ; codegen/test:scalafmt ; " + scalaFrameworks.map(x => s"${x}Sample/scalafmt ; ${x}Sample/test:scalafmt").mkString("; ")) +addCommandAlias("checkFormatting", "; codegen/scalafmtCheck ; " + scalaFrameworks.map(x => s"${x}Sample/scalafmtCheck ; ${x}Sample/test:scalafmtCheck").mkString("; ")) addCommandAlias("testSuite", "; scalaTestSuite") addCommandAlias( @@ -275,6 +280,22 @@ lazy val endpointsSample = (project in file("modules/sample-endpoints")) scalafmtOnCompile := false ) +lazy val dropwizardSample = (project in file("modules/sample-dropwizard")) + .settings( + codegenSettings, + libraryDependencies ++= Seq( + "io.dropwizard" % "dropwizard-core" % dropwizardVersion, + "org.asynchttpclient" % "async-http-client" % ahcVersion, + "org.scalatest" %% "scalatest" % scalatestVersion % Test, + "junit" % "junit" % "4.12" % Test, + "com.novocode" % "junit-interface" % "0.11" % Test + ), + skip in publish := true, + scalafmtOnCompile := false, + testOptions in Test := Seq(Tests.Argument(TestFrameworks.JUnit, "-a")) + ) + watchSources ++= (baseDirectory.value / "modules/sample/src/test" ** "*.scala").get +watchSources ++= (baseDirectory.value / "modules/sample/src/test" ** "*.java").get logBuffered in Test := false diff --git a/modules/sample-dropwizard/src/main/java/support/PositiveLong.java b/modules/sample-dropwizard/src/main/java/support/PositiveLong.java new file mode 100644 index 0000000000..aa1cb161fa --- /dev/null +++ b/modules/sample-dropwizard/src/main/java/support/PositiveLong.java @@ -0,0 +1,30 @@ +package support; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public class PositiveLong { + @JsonCreator + public static PositiveLong parse(final String s) { + final long value = Long.parseLong(s); + if (value < 0) { + throw new IllegalArgumentException(value + " is not positive"); + } + return new PositiveLong(value); + } + + private final long value; + + private PositiveLong(final long value) { + this.value = value; + } + + public String getValue() { + return String.valueOf(this.value); + } + + @JsonValue + public String toString() { + return getValue(); + } +} diff --git a/modules/sample-dropwizard/src/test/java/core/Dropwizard/DropwizardServerTest.java b/modules/sample-dropwizard/src/test/java/core/Dropwizard/DropwizardServerTest.java new file mode 100644 index 0000000000..9ea60dcc52 --- /dev/null +++ b/modules/sample-dropwizard/src/test/java/core/Dropwizard/DropwizardServerTest.java @@ -0,0 +1,10 @@ +package core.Dropwizard; + +import org.junit.Test; + +public class DropwizardServerTest { + @Test + public void testServer() { + + } +} From 3dafa0fce947f72abb53b4dd771a4920a3d09d52 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Tue, 5 Mar 2019 16:09:27 -0800 Subject: [PATCH 42/86] Use Jackson TypeReference when deserializing to a generic type --- .../Java/AsyncHttpClientClientGenerator.scala | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index ac5f6d29d3..f58ee3e304 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -8,7 +8,7 @@ import com.github.javaparser.JavaParser import com.github.javaparser.ast.NodeList import com.github.javaparser.ast.Modifier._ import com.github.javaparser.ast.`type`.{ClassOrInterfaceType, Type, VoidType} -import com.github.javaparser.ast.body.{ClassOrInterfaceDeclaration, MethodDeclaration, Parameter, VariableDeclarator} +import com.github.javaparser.ast.body._ import com.github.javaparser.ast.expr.{MethodCallExpr, NameExpr, _} import com.github.javaparser.ast.stmt._ import com.twilio.guardrail.SwaggerUtil.jpaths @@ -46,6 +46,9 @@ object AsyncHttpClientClientGenerator { completionStageType.setTypeArguments(RESPONSE_TYPE) )) + private def typeReferenceType(typeArg: Type): ClassOrInterfaceType = + JavaParser.parseClassOrInterfaceType("TypeReference").setTypeArguments(typeArg) + private def showParam(param: ScalaParameter[JavaLanguage], overrideParamName: Option[String] = None): Expression = { val paramName = overrideParamName.getOrElse(param.paramName.asString) @@ -166,6 +169,15 @@ object AsyncHttpClientClientGenerator { }) } + private def jacksonTypeReferenceFor(tpe: Type): Expression = { + tpe match { + case cls: ClassOrInterfaceType if cls.getTypeArguments.isPresent => + new ObjectCreationExpr(null, typeReferenceType(cls), new NodeList).setAnonymousClassBody(new NodeList) + case other => + new ClassExpr(other) + } + } + object ClientTermInterp extends (ClientTerm[JavaLanguage, ?] ~> Target) { def apply[T](term: ClientTerm[JavaLanguage, T]): Target[T] = term match { case GenerateClientOperation(_, RouteMeta(pathStr, httpMethod, operation), methodName, tracing, parameters, responses) => @@ -221,7 +233,7 @@ object AsyncHttpClientClientGenerator { "readValue", new NodeList[Expression]( new MethodCallExpr(new NameExpr("response"), "getResponseBodyAsStream"), - new ClassExpr(valueType) + jacksonTypeReferenceFor(valueType) ) ), AssignExpr.Operator.ASSIGN)), new IfStmt( @@ -270,6 +282,7 @@ object AsyncHttpClientClientGenerator { "java.util.concurrent.CompletionStage", "java.util.function.Function", "com.fasterxml.jackson.core.JsonProcessingException", + "com.fasterxml.jackson.core.type.TypeReference", "com.fasterxml.jackson.databind.ObjectMapper", "com.fasterxml.jackson.datatype.jdk8.Jdk8Module", "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", From 0fcf385db15c96e7254ee90ca5fabc1f466901cf Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Tue, 5 Mar 2019 17:22:09 -0800 Subject: [PATCH 43/86] Actually write out the Java server response definition classes --- .../Java/DropwizardServerGenerator.scala | 2 +- .../guardrail/generators/JavaGenerator.scala | 59 ++++++++----------- 2 files changed, 26 insertions(+), 35 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala index 0b5e71168d..a876778b50 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala @@ -323,7 +323,7 @@ object DropwizardServerGenerator { case _ => }) - Target.pure(cls :: Nil) + Target.pure(cls +: responseDefinitions) } safeParseSimpleName(className) >> diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala index 3ceecc486d..bc641358ff 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala @@ -1,6 +1,7 @@ package com.twilio.guardrail.generators import cats.~> +import cats.instances.list._ import cats.instances.option._ import cats.syntax.traverse._ import com.github.javaparser.ast._ @@ -265,44 +266,34 @@ object JavaGenerator { } case WriteServer(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, Server(pkg, extraImports, handlerDefinition, serverDefinitions)) => + def writeClass(pkgDecl: PackageDeclaration, extraImports: List[ImportDeclaration], definition: TypeDeclaration[_]): WriteTree = { + val cu = new CompilationUnit() + cu.setPackageDeclaration(pkgDecl) + extraImports.map(cu.addImport) + customImports.map(cu.addImport) + extraImports.map(cu.addImport) + cu.addType(definition) + WriteTree( + resolveFile(pkgPath)(pkg :+ s"${definition.getNameAsString}.java"), + cu.toString(printer).getBytes(StandardCharsets.UTF_8) + ) + } + + def writeDefinition(pkgDecl: PackageDeclaration, extraImports: List[ImportDeclaration], definition: BodyDeclaration[_]): Target[WriteTree] = { + definition match { + case td: TypeDeclaration[_] => Target.pure(writeClass(pkgDecl, extraImports, td)) + case other => Target.raiseError(s"Class definition must be a TypeDeclaration but it is a ${other.getClass.getName}") + } + } + for { - pkgDecl <- buildPkgDecl(pkgName ++ pkg) - //implicitsImport <- safeParseName((pkgName ++ List("Implicits", "*")).mkString(".")).map(name => new ImportDeclaration(name, false, true)) - //frameworkImplicitsImport <- safeParseName((pkgName ++ List(frameworkImplicitName.getIdentifier, "*")).mkString(".")) - // .map(name => new ImportDeclaration(name, false, true)) + pkgDecl <- buildPkgDecl(pkgName ++ pkg) dtoComponentsImport <- safeParseRawImport((dtoComponents :+ "*").mkString(".")) - handlerDefinition <- selfHandlerDefinition match { - case td: TypeDeclaration[_] => Target.pure(td) - case _ => Target.raiseError(s"Handler definition must be a TypeDeclaration but it is a ${selfHandlerDefinition.getClass.getName}") - } + handlerTree <- writeDefinition(pkgDecl, extraImports :+ dtoComponentsImport, handlerDefinition) - serverDefinition <- serverDefinitions match { - case (td: TypeDeclaration[_]) :: Nil => Target.pure(td) - case other :: Nil => Target.raiseError(s"Server definition must be a TypeDeclaration but it is a ${other.getClass.getName}") - case _ :: _ :: Nil => Target.raiseError("Only a single server definition is supported for Java generation") - case _ => Target.raiseError("No server definition provided") - } - } yield { - def writeClass(name: String, definition: TypeDeclaration[_]): WriteTree = { - val cu = new CompilationUnit() - cu.setPackageDeclaration(pkgDecl) - extraImports.map(cu.addImport) - customImports.map(cu.addImport) - //cu.addImport(implicitsImport) - //cu.addImport(frameworkImplicitsImport) - cu.addImport(dtoComponentsImport) - cu.addType(definition) - WriteTree( - resolveFile(pkgPath)(pkg :+ s"${name}.java"), - cu.toString(printer).getBytes(StandardCharsets.UTF_8) - ) - } - List( - writeClass(handlerName, handlerDefinition), - writeClass(resourceName, serverDefinition) - ) - } + serverTrees <- serverDefinitions.map(writeDefinition(pkgDecl, extraImports :+ dtoComponentsImport, _)).sequence + } yield handlerTree +: serverTrees } } } From 73c94fbaa62026e96776b0e663fef7dc6a817220 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Tue, 5 Mar 2019 17:24:12 -0800 Subject: [PATCH 44/86] Add/fix a bunch of missing/incorrect imports for the Java generation --- .../Java/AsyncHttpClientClientGenerator.scala | 1 + .../generators/Java/DropwizardGenerator.scala | 6 +-- .../Java/DropwizardServerGenerator.scala | 5 +- .../generators/Java/JacksonGenerator.scala | 16 +++---- .../guardrail/generators/JavaGenerator.scala | 47 +++++++------------ 5 files changed, 32 insertions(+), 43 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index f58ee3e304..83b2c4de52 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -292,6 +292,7 @@ object AsyncHttpClientClientGenerator { "org.asynchttpclient.DefaultAsyncHttpClientConfig", "org.asynchttpclient.Request", "org.asynchttpclient.RequestBuilder", + "org.asynchttpclient.Response", "org.asynchttpclient.request.body.multipart.FilePart", "org.asynchttpclient.request.body.multipart.StringPart" ).map(safeParseRawImport) ++ List( diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala index 3e481ddf47..edebdea5db 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala @@ -16,7 +16,7 @@ import com.twilio.guardrail.terms.framework._ import java.util object DropwizardGenerator { - private val RESPONSE_TYPE = JavaParser.parseClassOrInterfaceType("Response") + private val RESPONSE_TYPE = JavaParser.parseClassOrInterfaceType("org.asynchttpclient.Response") object FrameworkInterp extends (FrameworkTerm[JavaLanguage, ?] ~> Target) { def apply[T](term: FrameworkTerm[JavaLanguage, T]): Target[T] = term match { @@ -24,9 +24,7 @@ object DropwizardGenerator { case ObjectType(format) => safeParseType("com.fasterxml.jackson.databind.JsonNode") case GetFrameworkImports(tracing) => - List( - "org.asynchttpclient.Response" - ).map(safeParseRawImport).sequence + Target.pure(List.empty) case GetFrameworkImplicits() => Target.pure(None) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala index a876778b50..6450e900d9 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala @@ -23,7 +23,7 @@ import java.util object DropwizardServerGenerator { private val ASYNC_RESPONSE_TYPE = JavaParser.parseClassOrInterfaceType("AsyncResponse") - private val RESPONSE_BUILDER_TYPE = JavaParser.parseClassOrInterfaceType("ResponseBuilder") + private val RESPONSE_BUILDER_TYPE = JavaParser.parseClassOrInterfaceType("Response.ResponseBuilder") private val LOGGER_TYPE = JavaParser.parseClassOrInterfaceType("Logger") private def removeEmpty(s: String): Option[String] = if (s.trim.isEmpty) None else Some(s.trim) @@ -49,7 +49,6 @@ object DropwizardServerGenerator { checkMatch(List.empty, initialHeads, initialRest) } - object ServerTermInterp extends (ServerTerm[JavaLanguage, ?] ~> Target) { def apply[T](term: ServerTerm[JavaLanguage, T]): Target[T] = term match { case GetExtraImports(tracing) => @@ -71,6 +70,8 @@ object DropwizardServerGenerator { "javax.ws.rs.container.AsyncResponse", "javax.ws.rs.container.Suspended", "javax.ws.rs.core.MediaType", + "javax.ws.rs.core.Response", + "java.util.concurrent.CompletionStage", "org.slf4j.Logger", "org.slf4j.LoggerFactory" ).map(safeParseRawImport).sequence diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala index 155224112c..3f6d7f944f 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -188,8 +188,8 @@ object JacksonGenerator { case RenderStaticDefns(clsName, members, accessors, encoder, decoder) => for { extraImports <- List( - "com.fasterxml.jackson.annotations.JsonCreator", - "com.fasterxml.jackson.annotations.JsonValue" + "com.fasterxml.jackson.annotation.JsonCreator", + "com.fasterxml.jackson.annotation.JsonValue" ).map(safeParseRawImport).sequence } yield StaticDefns[JavaLanguage]( className = clsName, @@ -463,11 +463,11 @@ object JacksonGenerator { case ProtocolImports() => (List( "com.fasterxml.jackson.annotation.JsonCreator", - "com.fasterxml.jackson.annotation.JsonDeserialize", "com.fasterxml.jackson.annotation.JsonIgnoreProperties", - "com.fasterxml.jackson.annotation.JsonProperty" + "com.fasterxml.jackson.annotation.JsonProperty", + "com.fasterxml.jackson.databind.annotation.JsonDeserialize" ).map(safeParseRawImport) ++ List( - "java.lang.Objects.requireNonNull" + "java.util.Objects.requireNonNull" ).map(safeParseRawStaticImport)).sequence case PackageObjectImports() => @@ -502,9 +502,9 @@ object JacksonGenerator { case RenderADTStaticDefns(clsName, discriminator, encoder, decoder) => for { extraImports <- List( - "com.fasterxml.jackson.annotations.JsonIgnoreProperties", - "com.fasterxml.jackson.annotations.JsonSubTypes", - "com.fasterxml.jackson.annotations.JsonTypeInfo" + "com.fasterxml.jackson.annotation.JsonIgnoreProperties", + "com.fasterxml.jackson.annotation.JsonSubTypes", + "com.fasterxml.jackson.annotation.JsonTypeInfo" ).map(safeParseRawImport).sequence } yield StaticDefns[JavaLanguage]( clsName, diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala index bc641358ff..ec30b373de 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala @@ -147,17 +147,11 @@ object JavaGenerator { case RenderFrameworkDefinitions(pkgPath, pkgName, frameworkImports, frameworkDefinitions, frameworkDefinitionsName) => for { - pkgDecl <- buildPkgDecl(pkgName) - //implicitsImport <- safeParseName((pkgName ++ List("Implicits", "*")).mkString(".")).map(name => new ImportDeclaration(name, false, true)) - //frameworkImplicitsImport <- safeParseName((pkgName ++ List(frameworkImplicitName.getIdentifier, "*")).mkString(".")) - // .map(name => new ImportDeclaration(name, false, true)) - //dtoComponentsImport <- safeParseName((dtoComponents :+ "*").mkString(".")).map(name => new ImportDeclaration(name, false, true)) + pkgDecl <- buildPkgDecl(pkgName) } yield { val cu = new CompilationUnit() cu.setPackageDeclaration(pkgDecl) frameworkImports.map(cu.addImport) - //cu.addImport(implicitsImport) - //cu.addImport(frameworkImplicitsImport) cu.addType(frameworkDefinitions) WriteTree( resolveFile(pkgPath)(List(s"${frameworkDefinitionsName.asString}.java")), @@ -169,10 +163,11 @@ object JavaGenerator { Target.pure(None) case WriteProtocolDefinition(outputPath, pkgName, definitions, dtoComponents, imports, elem) => - elem match { - case EnumDefinition(_, _, _, cls, staticDefns) => - val clsCopy = cls.clone() - buildPkgDecl(pkgName).map { pkgDecl => + for { + pkgDecl <- buildPkgDecl(definitions) + } yield { + elem match { + case EnumDefinition(_, _, _, cls, staticDefns) => val cu = new CompilationUnit() cu.setPackageDeclaration(pkgDecl) imports.foreach(cu.addImport) @@ -189,10 +184,8 @@ object JavaGenerator { ), List.empty[Statement] ) - } - case ClassDefinition(_, _, cls, staticDefns, _) => - buildPkgDecl(pkgName).map { pkgDecl => + case ClassDefinition(_, _, cls, staticDefns, _) => val cu = new CompilationUnit() cu.setPackageDeclaration(pkgDecl) imports.foreach(cu.addImport) @@ -209,10 +202,8 @@ object JavaGenerator { ), List.empty[Statement] ) - } - case ADT(name, tpe, trt, staticDefns) => - buildPkgDecl(pkgName).map { pkgDecl => + case ADT(name, tpe, trt, staticDefns) => val cu = new CompilationUnit() cu.setPackageDeclaration(pkgDecl) imports.foreach(cu.addImport) @@ -229,10 +220,10 @@ object JavaGenerator { ), List.empty[Statement] ) - } - case RandomType(_, _) => - Target.pure((List.empty, List.empty)) + case RandomType(_, _) => + (List.empty, List.empty) + } } case WriteClient(pkgPath, @@ -243,17 +234,14 @@ object JavaGenerator { Client(pkg, clientName, imports, staticDefns, client, responseDefinitions)) => for { pkgDecl <- buildPkgDecl(pkgName ++ pkg) - //implicitsImport <- safeParseName((pkgName ++ List("Implicits", "*")).mkString(".")).map(name => new ImportDeclaration(name, false, true)) - //frameworkImplicitsImport <- safeParseName((pkgName ++ List(frameworkImplicitName.getIdentifier, "*")).mkString(".")) - // .map(name => new ImportDeclaration(name, false, true)) + commonImport <- safeParseRawImport((pkgName :+ "*").mkString(".")) dtoComponentsImport <- safeParseRawImport((dtoComponents :+ "*").mkString(".")) } yield { val cu = new CompilationUnit() cu.setPackageDeclaration(pkgDecl) imports.map(cu.addImport) customImports.map(cu.addImport) - //cu.addImport(implicitsImport) - //cu.addImport(frameworkImplicitsImport) + cu.addImport(commonImport) cu.addImport(dtoComponentsImport) val clientCopy = client.head.merge.clone() // FIXME: WriteClient needs to be altered to return `NonEmptyList[WriteTree]` to accommodate Java not being able to put multiple classes in the same file. Scala just jams them all together, but this could be improved over there as well. staticDefns.definitions.foreach(clientCopy.addMember) @@ -271,7 +259,6 @@ object JavaGenerator { cu.setPackageDeclaration(pkgDecl) extraImports.map(cu.addImport) customImports.map(cu.addImport) - extraImports.map(cu.addImport) cu.addType(definition) WriteTree( resolveFile(pkgPath)(pkg :+ s"${definition.getNameAsString}.java"), @@ -288,11 +275,13 @@ object JavaGenerator { for { pkgDecl <- buildPkgDecl(pkgName ++ pkg) - dtoComponentsImport <- safeParseRawImport((dtoComponents :+ "*").mkString(".")) - handlerTree <- writeDefinition(pkgDecl, extraImports :+ dtoComponentsImport, handlerDefinition) + commonImport <- safeParseRawImport((pkgName :+ "*").mkString(".")) + dtoComponentsImport <- safeParseRawImport((dtoComponents :+ "*").mkString(".")) + allExtraImports = extraImports ++ List(commonImport, dtoComponentsImport) - serverTrees <- serverDefinitions.map(writeDefinition(pkgDecl, extraImports :+ dtoComponentsImport, _)).sequence + handlerTree <- writeDefinition(pkgDecl, allExtraImports, handlerDefinition) + serverTrees <- serverDefinitions.map(writeDefinition(pkgDecl, allExtraImports, _)).sequence } yield handlerTree +: serverTrees } } From 1cda5ba6b8a13238000bb73124351501f0c31f90 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Tue, 5 Mar 2019 23:42:30 -0800 Subject: [PATCH 45/86] Implement a "show" pattern for Java This makes use of an interface Shower and a singleton class Shower, which is akin to Jackson's ObjectMapper, in that we (and users) can register custom methods to convert arbitrary types to strings. --- .../src/main/resources/java/Shower.java | 80 +++++++++++++++++++ .../Java/AsyncHttpClientClientGenerator.scala | 32 +++----- .../generators/Java/DropwizardGenerator.scala | 5 +- .../generators/Java/JacksonGenerator.scala | 11 +++ .../guardrail/generators/JavaGenerator.scala | 2 + .../guardrail/generators/syntax/Java.scala | 16 +++- .../src/main/java/support/PositiveLong.java | 6 +- 7 files changed, 124 insertions(+), 28 deletions(-) create mode 100644 modules/codegen/src/main/resources/java/Shower.java diff --git a/modules/codegen/src/main/resources/java/Shower.java b/modules/codegen/src/main/resources/java/Shower.java new file mode 100644 index 0000000000..22a1f4eb5a --- /dev/null +++ b/modules/codegen/src/main/resources/java/Shower.java @@ -0,0 +1,80 @@ +public class Shower { + @SuppressWarnings("serial") + public static class UnshowableInstanceException extends RuntimeException { + public UnshowableInstanceException(final Object instance) { + super("Instance of type " + instance.getClass().getName() + " is not showable"); + } + } + + public static interface Showable { + String show(T value); + } + + private static final Shower instance = new Shower(); + + public static Shower getInstance() { + return instance; + } + + private final java.util.Map, Showable> showables = new java.util.concurrent.ConcurrentHashMap<>(); + + private Shower() { + registerDefaultInstances(); + } + + public void register(final Class cls, final Showable showable) { + this.showables.put(cls, showable); + } + + @SuppressWarnings("unchecked") + public String show(final Object value) { + return show(value, (Class)value.getClass()); + } + + @SuppressWarnings("unchecked") + public boolean canShow(final Class cls) { + if (this.showables.containsKey(cls)) { + return true; + } else { + final Class superclass = cls.getSuperclass(); + if (superclass != null) { + return canShow(superclass); + } else { + return false; + } + } + } + + @SuppressWarnings("unchecked") + private String show(final Object value, final Class cls) { + if (this.showables.containsKey(cls)) { + final Showable showable = (Showable)this.showables.get(cls); + return showable.show(value); + } else { + final Class superclass = cls.getSuperclass(); + if (superclass != null) { + return show(value, superclass); + } else { + throw new UnshowableInstanceException(value); + } + } + } + + private void registerDefaultInstances() { + register(Boolean.class, String::valueOf); + register(Byte.class, String::valueOf); + register(Character.class, String::valueOf); + register(Short.class, String::valueOf); + register(Integer.class, String::valueOf); + register(Long.class, String::valueOf); + register(java.math.BigInteger.class, java.math.BigInteger::toString); + register(Float.class, String::valueOf); + register(Double.class, String::valueOf); + register(java.math.BigDecimal.class, java.math.BigDecimal::toString); + register(String.class, value -> value); + register(java.time.LocalDate.class, value -> value.format(java.time.format.DateTimeFormatter.ISO_DATE)); + register(java.time.OffsetDateTime.class, value -> value.format(java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME)); + register(java.net.URL.class, java.net.URL::toString); + register(java.net.URI.class, java.net.URI::toString); + } +} diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index 83b2c4de52..ec15285559 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -20,7 +20,6 @@ import com.twilio.guardrail.terms.RouteMeta import com.twilio.guardrail.{RenderedClientOperation, StaticDefns, Target, languages} import java.net.URI import java.util -import javax.lang.model.`type`.PrimitiveType object AsyncHttpClientClientGenerator { private val URI_TYPE = JavaParser.parseClassOrInterfaceType("URI") @@ -51,28 +50,21 @@ object AsyncHttpClientClientGenerator { private def showParam(param: ScalaParameter[JavaLanguage], overrideParamName: Option[String] = None): Expression = { val paramName = overrideParamName.getOrElse(param.paramName.asString) + new MethodCallExpr( + new MethodCallExpr(new NameExpr("Shower"), "getInstance"), + "show", + new NodeList[Expression](new NameExpr(paramName)) + ) def doShow(tpe: Type): Expression = tpe match { - case _: PrimitiveType => - new MethodCallExpr(new NameExpr("String"), "valueOf", new NodeList[Expression](new NameExpr(paramName))) - case cls: ClassOrInterfaceType if cls.isOptional => + case cls: ClassOrInterfaceType if cls.isOptional || cls.isNamed("List") => doShow(cls.containedType) - case cls: ClassOrInterfaceType if cls.isBoxedType => - new MethodCallExpr(new NameExpr(paramName), "toString") - case cls: ClassOrInterfaceType if cls.isNamed("List") => - doShow(cls.containedType) - case cls: ClassOrInterfaceType if cls.getName.asString() == "String" => - new NameExpr(paramName) - case _: ClassOrInterfaceType => - // FIXME: this will cover our autogenerated enum types, but it would be really nice if we could at identify - // our enum times via a list of some sort. this will fail to compile in most non-enum cases, but there's - // the possibility that this could generate incorrect but compilable code. - new MethodCallExpr(new NameExpr(paramName), "getValue") - case _: VoidType => - new NullLiteralExpr - case other => - println(s"WARN: Unhandled arg type ${other.getClass.getName} for arg typed ${other.name} ${param.paramName}") - new NameExpr("UNSUPPORTED_PARAMETER_TYPE_PLEASE_FILE_AN_ISSUE") + case _ => + new MethodCallExpr( + new MethodCallExpr(new NameExpr("Shower"), "getInstance"), + "show", + new NodeList[Expression](new NameExpr(paramName)) + ) } doShow(param.argType) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala index edebdea5db..e4217188aa 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala @@ -83,10 +83,13 @@ object DropwizardGenerator { new ReturnStmt(new FieldAccessExpr(new ThisExpr, "response")) ))) + val showerClass = SHOWER_CLASS_DEF + Target.pure(List( (new Name("ClientException"), clientExceptionClass), (new Name("MarshallingException"), marshallingExceptionClass), - (new Name("HttpError"), httpErrorClass) + (new Name("HttpError"), httpErrorClass), + (new Name(showerClass.getNameAsString), showerClass) )) case LookupStatusCode(key) => diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala index 3f6d7f944f..ad548adefa 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -168,6 +168,16 @@ object JacksonGenerator { )) ))) + val staticInitializer = new InitializerDeclaration(true, new BlockStmt(new NodeList( + new ExpressionStmt(new MethodCallExpr( + new MethodCallExpr(new NameExpr("Shower"), "getInstance"), + "register", + new NodeList[Expression]( + new ClassExpr(JavaParser.parseClassOrInterfaceType(clsName)), + new MethodReferenceExpr(new NameExpr(clsName), null, "getName") + ) + )) + ))) val enumClass = new EnumDeclaration( util.EnumSet.of(PUBLIC), @@ -176,6 +186,7 @@ object JacksonGenerator { new NodeList(), new NodeList(enumDefns: _*), new NodeList( + staticInitializer, nameField, constructor, getNameMethod, diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala index ec30b373de..bd56f8737f 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala @@ -165,6 +165,7 @@ object JavaGenerator { case WriteProtocolDefinition(outputPath, pkgName, definitions, dtoComponents, imports, elem) => for { pkgDecl <- buildPkgDecl(definitions) + showerImport <- safeParseRawImport((pkgName :+ "Shower").mkString(".")) } yield { elem match { case EnumDefinition(_, _, _, cls, staticDefns) => @@ -172,6 +173,7 @@ object JavaGenerator { cu.setPackageDeclaration(pkgDecl) imports.foreach(cu.addImport) staticDefns.extraImports.foreach(cu.addImport) + cu.addImport(showerImport) val clsCopy = cls.clone() staticDefns.definitions.foreach(clsCopy.addMember) cu.addType(clsCopy) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index 6d00c35458..827fff6313 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -1,11 +1,10 @@ package com.twilio.guardrail.generators.syntax -import cats.implicits._ import com.github.javaparser.JavaParser -import com.github.javaparser.ast.{CompilationUnit, ImportDeclaration} import com.github.javaparser.ast.`type`.{ClassOrInterfaceType, Type} import com.github.javaparser.ast.body.{ClassOrInterfaceDeclaration, Parameter} import com.github.javaparser.ast.expr.{Expression, Name, SimpleName} +import com.github.javaparser.ast.{CompilationUnit, ImportDeclaration} import com.github.javaparser.printer.PrettyPrinterConfiguration import com.github.javaparser.printer.PrettyPrinterConfiguration.IndentType import com.twilio.guardrail.Target @@ -51,9 +50,9 @@ object Java { } private[this] def safeParse[T](log: String)(parser: String => T, s: String)(implicit cls: ClassTag[T]): Target[T] = { - Target.log.debug(log)(s) >> ( + Target.log.function(s"${log}: ${s}") { Try(parser(s)).toEither.fold(t => Target.raiseError(s"Unable to parse '${s}' to a ${cls.runtimeClass.getName}: ${t.getMessage}"), Target.pure) - ) + } } def safeParseCode(s: String): Target[CompilationUnit] = safeParse("safeParseCode")(JavaParser.parse, s) @@ -98,6 +97,15 @@ object Java { def escapeReservedWord: String = if (reservedWords.contains(s)) s + "_" else s } + lazy val SHOWER_CLASS_DEF: ClassOrInterfaceDeclaration = { + JavaParser.parseResource(getClass.getClassLoader, "java/Shower.java", StandardCharsets.UTF_8) + .getClassByName("Shower") + .asScala + .getOrElse( + throw new AssertionError("Shower.java in class resources is not valid") + ) + } + /* implicit class PrintStructure(value: Node) { def toAST: String = { diff --git a/modules/sample-dropwizard/src/main/java/support/PositiveLong.java b/modules/sample-dropwizard/src/main/java/support/PositiveLong.java index aa1cb161fa..ef6b12cd3f 100644 --- a/modules/sample-dropwizard/src/main/java/support/PositiveLong.java +++ b/modules/sample-dropwizard/src/main/java/support/PositiveLong.java @@ -19,12 +19,12 @@ private PositiveLong(final long value) { this.value = value; } - public String getValue() { - return String.valueOf(this.value); + public long getValue() { + return this.value; } @JsonValue public String toString() { - return getValue(); + return String.valueOf(getValue()); } } From 4006086c0578be001563b472a85af5484feb7be1 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Tue, 5 Mar 2019 23:43:37 -0800 Subject: [PATCH 46/86] Add a package-info.java file to the DTO components path In the case where a swagger spec doesn't specify any definitions/components, we end up with no DTO components package at all, which means that wildcard imports in other classes will cause a compilation error. This way we just don't have to worry about it. --- .../com/twilio/guardrail/generators/JavaGenerator.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala index bd56f8737f..4e0a7c91b3 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala @@ -160,7 +160,12 @@ object JavaGenerator { } case WritePackageObject(dtoPackagePath, dtoComponents, customImports, packageObjectImports, protocolImports, packageObjectContents, extraTypes) => - Target.pure(None) + for { + pkgDecl <- buildPkgDecl(dtoComponents) + } yield Some(WriteTree( + resolveFile(dtoPackagePath)(List.empty).resolve("package-info.java"), + pkgDecl.toString(printer).getBytes(StandardCharsets.UTF_8) + )) case WriteProtocolDefinition(outputPath, pkgName, definitions, dtoComponents, imports, elem) => for { From ccbbcd0a4771cdbf96ca7b056cbceb58f2c2eced Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Wed, 6 Mar 2019 00:19:51 -0800 Subject: [PATCH 47/86] Unbox boxed types in Java server responses --- .../generators/Java/DropwizardServerGenerator.scala | 9 +++++---- .../com/twilio/guardrail/generators/syntax/Java.scala | 6 ++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala index 6450e900d9..110d39ac99 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala @@ -8,7 +8,7 @@ import cats.syntax.traverse._ import com.github.javaparser.JavaParser import com.github.javaparser.ast.Modifier._ import com.github.javaparser.ast.{Modifier, NodeList} -import com.github.javaparser.ast.`type`.{PrimitiveType, VoidType} +import com.github.javaparser.ast.`type`.{PrimitiveType, Type, VoidType} import com.github.javaparser.ast.body._ import com.github.javaparser.ast.expr._ import com.github.javaparser.ast.stmt._ @@ -289,16 +289,17 @@ object DropwizardServerGenerator { ))) (List.empty[FieldDeclaration], constructor, List.empty[MethodDeclaration]) })({ case (valueType, _) => - val valueField = new FieldDeclaration(util.EnumSet.of(PRIVATE, FINAL), new VariableDeclarator(valueType, "value")) + val unboxedValueType: Type = valueType.unbox + val valueField = new FieldDeclaration(util.EnumSet.of(PRIVATE, FINAL), new VariableDeclarator(unboxedValueType, "value")) val constructor = new ConstructorDeclaration(util.EnumSet.of(PUBLIC), clsName) - constructor.addParameter(new Parameter(util.EnumSet.of(FINAL), valueType, new SimpleName("value"))) + constructor.addParameter(new Parameter(util.EnumSet.of(FINAL), unboxedValueType, new SimpleName("value"))) constructor.setBody(new BlockStmt(new NodeList( new ExpressionStmt(new MethodCallExpr("super", new IntegerLiteralExpr(response.statusCode))), new ExpressionStmt(new AssignExpr(new FieldAccessExpr(new ThisExpr, "value"), new NameExpr("value"), AssignExpr.Operator.ASSIGN)) ))) - val getValueMethod = new MethodDeclaration(util.EnumSet.of(PUBLIC), valueType, "getValue") + val getValueMethod = new MethodDeclaration(util.EnumSet.of(PUBLIC), unboxedValueType, "getValue") getValueMethod.setBody(new BlockStmt(new NodeList( new ReturnStmt(new FieldAccessExpr(new ThisExpr, "value")) ))) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index 827fff6313..fe36be8654 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -33,6 +33,12 @@ object Java { case _ => tpe } + def unbox: Type = + tpe match { + case cls: ClassOrInterfaceType if cls.isBoxedType => cls.toUnboxedType + case _ => tpe + } + def isNamed(name: String): Boolean = tpe match { case cls: ClassOrInterfaceType if name.contains(".") => From d4b43b82b7f74e558f27c0f9a1fc76a3c3bc58aa Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Wed, 6 Mar 2019 11:28:04 -0800 Subject: [PATCH 48/86] Add Shower test --- .../Dropwizard/DropwizardShowerTest.scala | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardShowerTest.scala diff --git a/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardShowerTest.scala b/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardShowerTest.scala new file mode 100644 index 0000000000..06767f13a9 --- /dev/null +++ b/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardShowerTest.scala @@ -0,0 +1,129 @@ +package core.Dropwizard + +import alias.client.dropwizard.Shower +import java.math +import java.net.{URI, URL} +import java.time.{LocalDate, OffsetDateTime} +import org.scalatest.{FreeSpec, Matchers} +import scala.util.Try + +class DropwizardShowerTest extends FreeSpec with Matchers { + private val shower = Shower.getInstance + + "Showers should be able to show for each builtin type" - { + "Boolean" in { + assert(shower.canShow(classOf[java.lang.Boolean])) + val b: java.lang.Boolean = java.lang.Boolean.TRUE + shower.show(b) shouldBe "true" + } + + "Byte" in { + assert(shower.canShow(classOf[java.lang.Byte])) + val b: java.lang.Byte = java.lang.Byte.valueOf("42") + shower.show(b) shouldBe "42" + } + + "Character" in { + assert(shower.canShow(classOf[java.lang.Character])) + val c: java.lang.Character = "*".toCharArray()(0) + shower.show(c) shouldBe "*" + } + + "Short" in { + assert(shower.canShow(classOf[java.lang.Short])) + val s: java.lang.Short = java.lang.Short.valueOf("42", 10) + shower.show(s) shouldBe "42" + } + + "Integer" in { + assert(shower.canShow(classOf[java.lang.Integer])) + val i: java.lang.Integer = 42 + shower.show(i) shouldBe "42" + } + + "Long" in { + assert(shower.canShow(classOf[java.lang.Long])) + val l: java.lang.Long = 42L + shower.show(l) shouldBe "42" + } + + "BigInteger" in { + assert(shower.canShow(classOf[math.BigInteger])) + val bi: math.BigInteger = math.BigInteger.valueOf(42) + shower.show(bi) shouldBe "42" + } + + "Float" in { + assert(shower.canShow(classOf[java.lang.Float])) + val f: java.lang.Float = 42F + shower.show(f) shouldBe "42.0" + } + + "Double" in { + assert(shower.canShow(classOf[java.lang.Double])) + val d: java.lang.Double = 42D + shower.show(d) shouldBe "42.0" + } + + "BigDecimal" in { + assert(shower.canShow(classOf[math.BigDecimal])) + val bd: math.BigDecimal = math.BigDecimal.valueOf(42D) + shower.show(bd) shouldBe "42.0" + } + + "String" in { + assert(shower.canShow(classOf[java.lang.String])) + val s: java.lang.String = "42" + shower.show(s) shouldBe "42" + } + + "LocalDate" in { + assert(shower.canShow(classOf[LocalDate])) + val dateStr = "2010-04-01" + val date: LocalDate = LocalDate.parse(dateStr) + shower.show(date) shouldBe dateStr + } + + "OffsetDateTime" in { + assert(shower.canShow(classOf[OffsetDateTime])) + val dateTimeStr = "2010-04-01T09:42:42Z" + val dateTime: OffsetDateTime = OffsetDateTime.parse(dateTimeStr) + shower.show(dateTime) shouldBe dateTimeStr + } + + "URI" in { + assert(shower.canShow(classOf[URI])) + val uriStr = "https://example.com/" + val uri = new URI(uriStr) + shower.show(uri) shouldBe uriStr + } + + "URL" in { + assert(shower.canShow(classOf[URL])) + val urlStr = "https://example.com/" + val url = new URL(urlStr) + shower.show(url) shouldBe urlStr + } + } + + "Shower should be able to show for registered custom types" - { + "Some random case class I'm going to make right here" in { + case class Foo(first: String, last: String) + assert(!shower.canShow(classOf[Foo])) + shower.register[Foo](classOf[Foo], value => s"${value.first} ${value.last}") + assert(shower.canShow(classOf[Foo])) + val foo = Foo("Blast", "Hardcheese") + shower.show(foo) shouldBe s"${foo.first} ${foo.last}" + } + } + + "Shower should not be able to show for unregistered types" - { + "This test class" in { + assert(!shower.canShow(getClass)) + Try(shower.show(this)).fold( + _.getClass shouldBe classOf[Shower.UnshowableInstanceException], + _ => fail("shower.show() should have thrown and UnshowableInstanceException") + ) + } + } +} From 8646e0536eefce578e452d805c19605204dc5e5a Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Wed, 6 Mar 2019 14:16:29 -0800 Subject: [PATCH 49/86] Move Dropwizard test to scala --- build.sbt | 13 +-- .../core/Dropwizard/DropwizardServerTest.java | 10 -- .../Dropwizard/DropwizardRoundTripTest.scala | 105 ++++++++++++++++++ 3 files changed, 111 insertions(+), 17 deletions(-) delete mode 100644 modules/sample-dropwizard/src/test/java/core/Dropwizard/DropwizardServerTest.java create mode 100644 modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardRoundTripTest.scala diff --git a/build.sbt b/build.sbt index d1e69021b3..362782d588 100644 --- a/build.sbt +++ b/build.sbt @@ -284,15 +284,14 @@ lazy val dropwizardSample = (project in file("modules/sample-dropwizard")) .settings( codegenSettings, libraryDependencies ++= Seq( - "io.dropwizard" % "dropwizard-core" % dropwizardVersion, - "org.asynchttpclient" % "async-http-client" % ahcVersion, - "org.scalatest" %% "scalatest" % scalatestVersion % Test, - "junit" % "junit" % "4.12" % Test, - "com.novocode" % "junit-interface" % "0.11" % Test + "io.dropwizard" % "dropwizard-core" % dropwizardVersion, + "org.asynchttpclient" % "async-http-client" % ahcVersion, + "org.scala-lang.modules" %% "scala-java8-compat" % "0.9.0" % Test, + "org.scalatest" %% "scalatest" % scalatestVersion % Test, + "org.mockito" %% "mockito-scala" % "1.2.0" % Test, ), skip in publish := true, - scalafmtOnCompile := false, - testOptions in Test := Seq(Tests.Argument(TestFrameworks.JUnit, "-a")) + scalafmtOnCompile := false ) watchSources ++= (baseDirectory.value / "modules/sample/src/test" ** "*.scala").get diff --git a/modules/sample-dropwizard/src/test/java/core/Dropwizard/DropwizardServerTest.java b/modules/sample-dropwizard/src/test/java/core/Dropwizard/DropwizardServerTest.java deleted file mode 100644 index 9ea60dcc52..0000000000 --- a/modules/sample-dropwizard/src/test/java/core/Dropwizard/DropwizardServerTest.java +++ /dev/null @@ -1,10 +0,0 @@ -package core.Dropwizard; - -import org.junit.Test; - -public class DropwizardServerTest { - @Test - public void testServer() { - - } -} diff --git a/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardRoundTripTest.scala b/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardRoundTripTest.scala new file mode 100644 index 0000000000..b00b4a2392 --- /dev/null +++ b/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardRoundTripTest.scala @@ -0,0 +1,105 @@ +package core.Dropwizard + +import alias.client.dropwizard.foo.FooClient +import alias.server.dropwizard.foo.{DoFooResponse, FooHandler, FooResource} +import com.fasterxml.jackson.databind.ObjectMapper +import examples.server.dropwizard.definitions.User +import examples.server.dropwizard.user._ +import java.io.ByteArrayInputStream +import java.nio.ByteBuffer +import java.util +import java.util.Optional +import java.util.concurrent.{CompletableFuture, CompletionStage} +import javax.ws.rs.container.AsyncResponse +import org.asynchttpclient.{Request, Response} +import org.mockito.{ArgumentMatchersSugar, MockitoSugar} +import org.scalatest.concurrent.Waiters +import org.scalatest.{FreeSpec, Matchers} +import scala.collection.JavaConverters._ +import scala.compat.java8.FunctionConverters._ +import scala.compat.java8.OptionConverters._ +import scala.reflect.ClassTag +import scala.util.Try + +class DropwizardRoundTripTest extends FreeSpec with Matchers with Waiters with MockitoSugar with ArgumentMatchersSugar { + private val mapper = new ObjectMapper + + private def mockAsyncResponse[T](implicit cls: ClassTag[T]): (AsyncResponse, CompletableFuture[T]) = { + val asyncResponse = mock[AsyncResponse] + val future = new CompletableFuture[T] + + when(asyncResponse.resume(any[T])) thenAnswer[AnyRef] { response => response match { + case t: Throwable => future.completeExceptionally(t) + case other: T => future.complete(other) + case other => fail(s"AsyncResponse.resume expected an object of type ${cls.runtimeClass.getName}, but got ${other.getClass.getName} instead") + }} + + (asyncResponse, future) + } + + "Test server" in { + val USERNAME = "foobar" + + val (asyncResponse, future) = mockAsyncResponse[GetUserByNameResponse] + + val resource = new UserResource(new UserHandler { + override def createUser(body: User): CompletionStage[CreateUserResponse] = ??? + override def createUsersWithArrayInput(body: util.List[User]): CompletionStage[CreateUsersWithArrayInputResponse] = ??? + override def createUsersWithListInput(body: util.List[User]): CompletionStage[CreateUsersWithListInputResponse] = ??? + override def loginUser(username: String, password: String): CompletionStage[LoginUserResponse] = ??? + override def logoutUser(): CompletionStage[LogoutUserResponse] = ??? + override def updateUser(username: String, body: User): CompletionStage[UpdateUserResponse] = ??? + override def deleteUser(username: String): CompletionStage[DeleteUserResponse] = ??? + + override def getUserByName(username: String): CompletionStage[GetUserByNameResponse] = { + if (USERNAME == "foobar") { + future.complete(new GetUserByNameResponse.Ok(User.b)) + } + } + }) + val resource = new FooResource(new FooHandler { + override def doFoo(long: Optional[java.lang.Long], body: Optional[java.lang.Long]): CompletionStage[DoFooResponse] = { + future.complete(new DoFooResponse.Created(long.orElse(42L))) + future + } + }) + + val httpClient: Request => CompletionStage[Response] = { request => + if (request.getUri.getPath == "/foo") { + println("got request") + val queryParams = request.getQueryParams.asScala + val long = queryParams.find(_.getName == "long").map[java.lang.Long](_.getValue.toLong).asJava + val body = Try(java.lang.Long.valueOf(new String(request.getByteData, request.getCharset))).toOption.asJava + resource.doFoo(long, body, asyncResponse) + future.thenApply({ + case _: DoFooResponse.Created => + println("applying response object to http response") + val response = mock[Response] + when(response.getStatusCode) thenReturn 201 + when(response.getResponseBody(any)) thenReturn "" + when(response.getResponseBody) thenReturn "" + when(response.getResponseBodyAsStream) thenReturn new ByteArrayInputStream(new Array[Byte](0)) + when(response.getResponseBodyAsByteBuffer) thenReturn ByteBuffer.allocate(0) + response + }) + } else { + CompletableFuture.completedFuture(new Response.ResponseBuilder().build()) + } + } + + val client = new FooClient.Builder() + .withHttpClient(httpClient.asJava) + .withObjectMapper(mapper) + .build() + + val w = new Waiter + client.doFoo(Optional.of(42), Optional.of(99)).whenComplete({ (response, t) => + w { + t shouldBe null + response.getClass shouldBe classOf[FooClient.DoFooResponse.Created] + } + w.dismiss() + }) + w.await(dismissals(1)) + } +} From e6eefe32239bb2bb5ce38ceafd5ddf115abc3d02 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Wed, 6 Mar 2019 16:09:58 -0800 Subject: [PATCH 50/86] Don't use DTO's Builder class for Jackson deserialization Unfortunately it's not going to work with how I want to do things. For serialization, it was fine. For deserialization, Jackson was passing null to the with*() method on the Builder when particular fields were null or missing, which was causing Optional.of() to throw an exception. I don't want to change it to Optional.ofNullable(), because I want to disallow _humans_ passing null to those methods. --- .../generators/Java/JacksonGenerator.scala | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala index ad548adefa..86722330fc 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -295,13 +295,6 @@ object JacksonGenerator { val terms = requiredTerms ++ optionalTerms val dtoClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, clsName) - dtoClass.addAnnotation(new NormalAnnotationExpr( - new Name("JsonDeserialize"), - new NodeList(new MemberValuePair( - "builder", - new ClassExpr(JavaParser.parseClassOrInterfaceType(s"${clsName}.Builder")) - )) - )) dtoClass.addAnnotation(new NormalAnnotationExpr( new Name("JsonIgnoreProperties"), new NodeList(new MemberValuePair( @@ -329,8 +322,13 @@ object JacksonGenerator { }) val primaryConstructor = dtoClass.addConstructor(PRIVATE) - primaryConstructor.addMarkerAnnotation("JsonCreator") // FIXME: is this needed? - primaryConstructor.addParameter(new Parameter(util.EnumSet.of(FINAL), JavaParser.parseClassOrInterfaceType("Builder"), new SimpleName("builder"))); + primaryConstructor.addMarkerAnnotation("JsonCreator") + primaryConstructor.setParameters(new NodeList( + terms.map({ case ParameterTerm(propertyName, parameterName, fieldType, _) => + new Parameter(util.EnumSet.of(FINAL), fieldType, new SimpleName(parameterName)) + .addAnnotation(new SingleMemberAnnotationExpr(new Name("JsonProperty"), new StringLiteralExpr(propertyName))) + }): _* + )) primaryConstructor.setBody( new BlockStmt( new NodeList( @@ -338,8 +336,8 @@ object JacksonGenerator { new ExpressionStmt(new AssignExpr( new FieldAccessExpr(new ThisExpr, parameterName), fieldType match { - case _: PrimitiveType => new FieldAccessExpr(new NameExpr("builder"), parameterName) - case _ => new MethodCallExpr("requireNonNull", new FieldAccessExpr(new NameExpr("builder"), parameterName)) + case _: PrimitiveType => new NameExpr(parameterName) + case _ => new MethodCallExpr("requireNonNull", new NameExpr(parameterName)) }, AssignExpr.Operator.ASSIGN )) @@ -430,7 +428,7 @@ object JacksonGenerator { new ReturnStmt(new ObjectCreationExpr( null, JavaParser.parseClassOrInterfaceType(clsName), - new NodeList(new ThisExpr) + new NodeList(terms.map(param => new FieldAccessExpr(new ThisExpr, param.parameterName)): _*) )) ))) @@ -476,7 +474,6 @@ object JacksonGenerator { "com.fasterxml.jackson.annotation.JsonCreator", "com.fasterxml.jackson.annotation.JsonIgnoreProperties", "com.fasterxml.jackson.annotation.JsonProperty", - "com.fasterxml.jackson.databind.annotation.JsonDeserialize" ).map(safeParseRawImport) ++ List( "java.util.Objects.requireNonNull" ).map(safeParseRawStaticImport)).sequence From 77f6f2cea779ae521dd23b5785783dc488a76d06 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Wed, 6 Mar 2019 23:01:36 -0800 Subject: [PATCH 51/86] Add toString, equals, and hashCode to Java DTOs --- .../generators/Java/JacksonGenerator.scala | 81 ++++++++++++++++++- .../guardrail/generators/syntax/Java.scala | 1 + 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala index 86722330fc..6ff5cdb582 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -3,6 +3,7 @@ package generators package Java import _root_.io.swagger.v3.oas.models.media._ +import cats.data.NonEmptyList import cats.implicits._ import cats.~> import com.github.javaparser.ast.`type`.{ClassOrInterfaceType, PrimitiveType, Type} @@ -14,7 +15,7 @@ import com.twilio.guardrail.protocol.terms.protocol._ import java.util.Locale import scala.collection.JavaConverters._ import com.github.javaparser.JavaParser -import com.github.javaparser.ast.{ImportDeclaration,NodeList} +import com.github.javaparser.ast.{ImportDeclaration, NodeList} import com.github.javaparser.ast.stmt._ import com.github.javaparser.ast.Modifier.{ABSTRACT, FINAL, PRIVATE, PUBLIC, STATIC} import com.github.javaparser.ast.body._ @@ -286,6 +287,7 @@ object JacksonGenerator { } case RenderDTOClass(clsName, selfParams, parents) => + val dtoClassType = JavaParser.parseClassOrInterfaceType(clsName) val discriminators = parents.flatMap(_.discriminators) val parentOpt = parents.headOption val params = (parents.reverse.flatMap(_.params) ++ selfParams).filterNot( @@ -355,6 +357,83 @@ object JacksonGenerator { ))) }) + val toStringFieldExprs = NonEmptyList.fromList(terms).toList.flatMap(l => + (new StringLiteralExpr(s"${l.head.parameterName}="), new FieldAccessExpr(new ThisExpr, l.head.parameterName)) +: + l.tail.map(param => ( + new StringLiteralExpr(s", ${param.parameterName}="), + new FieldAccessExpr(new ThisExpr, param.parameterName) + )) + ) + + val toStringMethod = dtoClass.addMethod("toString", PUBLIC) + .setType(STRING_TYPE) + .addMarkerAnnotation("Override") + toStringMethod.setBody(new BlockStmt(new NodeList(new ReturnStmt( + new BinaryExpr( + toStringFieldExprs.foldLeft[Expression](new StringLiteralExpr(s"${clsName}{"))({ case (prevExpr, (strExpr, fieldExpr)) => + new BinaryExpr( + new BinaryExpr(prevExpr, strExpr, BinaryExpr.Operator.PLUS), + fieldExpr, + BinaryExpr.Operator.PLUS + ) + }), + new StringLiteralExpr("}"), + BinaryExpr.Operator.PLUS + ) + )))) + + val equalsConditions: List[Expression] = terms.map({ case ParameterTerm(_, parameterName, fieldType, _) => + fieldType match { + case _: PrimitiveType => new BinaryExpr( + new FieldAccessExpr(new ThisExpr, parameterName), + new FieldAccessExpr(new NameExpr("other"), parameterName), + BinaryExpr.Operator.EQUALS + ) + case _ => new MethodCallExpr( + new FieldAccessExpr(new ThisExpr, parameterName), + "equals", + new NodeList[Expression](new FieldAccessExpr(new NameExpr("other"), parameterName)) + ) + } + }) + val returnExpr = NonEmptyList.fromList(equalsConditions).map(_.reduceLeft( + (prevExpr, condExpr) => new BinaryExpr(prevExpr, condExpr, BinaryExpr.Operator.AND) + )).getOrElse(new BooleanLiteralExpr(true)) + + val equalsMethod = dtoClass.addMethod("equals", PUBLIC) + .setType(PrimitiveType.booleanType) + .addMarkerAnnotation("Override") + .addParameter(new Parameter(util.EnumSet.of(FINAL), OBJECT_TYPE, new SimpleName("o"))) + equalsMethod.setBody(new BlockStmt(new NodeList( + new IfStmt( + new BinaryExpr(new ThisExpr, new NameExpr("o"), BinaryExpr.Operator.EQUALS), + new BlockStmt(new NodeList(new ReturnStmt(new BooleanLiteralExpr(true)))), + null + ), + new IfStmt( + new BinaryExpr( + new BinaryExpr(new NameExpr("o"), new NullLiteralExpr, BinaryExpr.Operator.EQUALS), + new BinaryExpr(new MethodCallExpr("getClass"), new MethodCallExpr(new NameExpr("o"), "getClass"), BinaryExpr.Operator.NOT_EQUALS), + BinaryExpr.Operator.OR + ), + new BlockStmt(new NodeList(new ReturnStmt(new BooleanLiteralExpr(false)))), + null + ), + new ExpressionStmt(new VariableDeclarationExpr(new VariableDeclarator( + dtoClassType, "other", new CastExpr(dtoClassType, new NameExpr("o")) + ), FINAL)), + new ReturnStmt(returnExpr) + ))) + + val hashCodeMethod = dtoClass.addMethod("hashCode", PUBLIC) + .setType(PrimitiveType.intType) + .addMarkerAnnotation("Override") + hashCodeMethod.setBody(new BlockStmt(new NodeList(new ReturnStmt(new MethodCallExpr( + new NameExpr("java.util.Objects"), + "hash", + new NodeList[Expression](terms.map(term => new FieldAccessExpr(new ThisExpr, term.parameterName)): _*) + ))))) + val builderMethod = dtoClass.addMethod("builder", PUBLIC, STATIC) builderMethod.setType("Builder") builderMethod.setParameters(new NodeList( diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index fe36be8654..8c1542344e 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -76,6 +76,7 @@ object Java { def optionalType: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Optional") def functionType: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Function") + val OBJECT_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Object") val STRING_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("String") val THROWABLE_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Throwable") val ASSERTION_ERROR_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("AssertionError") From 6f771cbac5e872104e55298b346ca2bcd4e1026e Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Wed, 6 Mar 2019 23:33:10 -0800 Subject: [PATCH 52/86] Flesh out the DW test a bit more (it actually passes now) --- .../Dropwizard/DropwizardRoundTripTest.scala | 97 ++++++++----------- .../src/test/scala/helpers/MockHelpers.scala | 58 +++++++++++ 2 files changed, 99 insertions(+), 56 deletions(-) create mode 100644 modules/sample-dropwizard/src/test/scala/helpers/MockHelpers.scala diff --git a/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardRoundTripTest.scala b/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardRoundTripTest.scala index b00b4a2392..363d14dfdd 100644 --- a/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardRoundTripTest.scala +++ b/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardRoundTripTest.scala @@ -1,46 +1,26 @@ package core.Dropwizard -import alias.client.dropwizard.foo.FooClient -import alias.server.dropwizard.foo.{DoFooResponse, FooHandler, FooResource} import com.fasterxml.jackson.databind.ObjectMapper +import examples.client.dropwizard.user.UserClient import examples.server.dropwizard.definitions.User import examples.server.dropwizard.user._ -import java.io.ByteArrayInputStream -import java.nio.ByteBuffer +import helpers.MockHelpers._ import java.util import java.util.Optional import java.util.concurrent.{CompletableFuture, CompletionStage} -import javax.ws.rs.container.AsyncResponse import org.asynchttpclient.{Request, Response} import org.mockito.{ArgumentMatchersSugar, MockitoSugar} import org.scalatest.concurrent.Waiters import org.scalatest.{FreeSpec, Matchers} -import scala.collection.JavaConverters._ import scala.compat.java8.FunctionConverters._ -import scala.compat.java8.OptionConverters._ -import scala.reflect.ClassTag -import scala.util.Try class DropwizardRoundTripTest extends FreeSpec with Matchers with Waiters with MockitoSugar with ArgumentMatchersSugar { - private val mapper = new ObjectMapper - - private def mockAsyncResponse[T](implicit cls: ClassTag[T]): (AsyncResponse, CompletableFuture[T]) = { - val asyncResponse = mock[AsyncResponse] - val future = new CompletableFuture[T] - - when(asyncResponse.resume(any[T])) thenAnswer[AnyRef] { response => response match { - case t: Throwable => future.completeExceptionally(t) - case other: T => future.complete(other) - case other => fail(s"AsyncResponse.resume expected an object of type ${cls.runtimeClass.getName}, but got ${other.getClass.getName} instead") - }} - - (asyncResponse, future) - } + private implicit val mapper = new ObjectMapper "Test server" in { val USERNAME = "foobar" - val (asyncResponse, future) = mockAsyncResponse[GetUserByNameResponse] + val (asyncResponse, serverFuture: CompletableFuture[GetUserByNameResponse]) = mockAsyncResponse[GetUserByNameResponse] val resource = new UserResource(new UserHandler { override def createUser(body: User): CompletionStage[CreateUserResponse] = ??? @@ -52,51 +32,56 @@ class DropwizardRoundTripTest extends FreeSpec with Matchers with Waiters with M override def deleteUser(username: String): CompletionStage[DeleteUserResponse] = ??? override def getUserByName(username: String): CompletionStage[GetUserByNameResponse] = { - if (USERNAME == "foobar") { - future.complete(new GetUserByNameResponse.Ok(User.b)) + username match { + case USERNAME => + serverFuture.complete(new GetUserByNameResponse.Ok(User.builder() + .withEmail("foo@bar.com") + .withFirstName("Foo") + .withLastName("Bar") + .withId(1) + .withUsername(USERNAME) + .build() + )) + case "" => + serverFuture.complete(new GetUserByNameResponse.BadRequest) + case _ => + serverFuture.complete(new GetUserByNameResponse.NotFound) } - } - }) - val resource = new FooResource(new FooHandler { - override def doFoo(long: Optional[java.lang.Long], body: Optional[java.lang.Long]): CompletionStage[DoFooResponse] = { - future.complete(new DoFooResponse.Created(long.orElse(42L))) - future + serverFuture } }) val httpClient: Request => CompletionStage[Response] = { request => - if (request.getUri.getPath == "/foo") { - println("got request") - val queryParams = request.getQueryParams.asScala - val long = queryParams.find(_.getName == "long").map[java.lang.Long](_.getValue.toLong).asJava - val body = Try(java.lang.Long.valueOf(new String(request.getByteData, request.getCharset))).toOption.asJava - resource.doFoo(long, body, asyncResponse) - future.thenApply({ - case _: DoFooResponse.Created => - println("applying response object to http response") - val response = mock[Response] - when(response.getStatusCode) thenReturn 201 - when(response.getResponseBody(any)) thenReturn "" - when(response.getResponseBody) thenReturn "" - when(response.getResponseBodyAsStream) thenReturn new ByteArrayInputStream(new Array[Byte](0)) - when(response.getResponseBodyAsByteBuffer) thenReturn ByteBuffer.allocate(0) - response - }) - } else { - CompletableFuture.completedFuture(new Response.ResponseBuilder().build()) + val userPath = "^/v2/user/([^/]*)$".r + request.getUri.getPath match { + case userPath(username) => + resource.getUserByName(username, asyncResponse) + serverFuture.thenApply({ response => response match { + case r: GetUserByNameResponse.Ok => mockAHCResponse(request.getUrl, r.getStatusCode, Some(r.getValue)) + case r: GetUserByNameResponse.BadRequest => mockAHCResponse(request.getUrl, r.getStatusCode) + case r: GetUserByNameResponse.NotFound => mockAHCResponse(request.getUrl, r.getStatusCode) + }}) + case _ => + CompletableFuture.completedFuture(mockAHCResponse(request.getUrl, 404)) } } - val client = new FooClient.Builder() + val client = new UserClient.Builder() .withHttpClient(httpClient.asJava) .withObjectMapper(mapper) .build() val w = new Waiter - client.doFoo(Optional.of(42), Optional.of(99)).whenComplete({ (response, t) => - w { - t shouldBe null - response.getClass shouldBe classOf[FooClient.DoFooResponse.Created] + client.getUserByName(USERNAME).whenComplete({ (response, t) => + w { t shouldBe null } + response match { + case r: UserClient.GetUserByNameResponse.Ok => + w { + r.getValue.getUsername.get shouldBe USERNAME + r.getValue.getPassword shouldBe Optional.empty + } + case _: UserClient.GetUserByNameResponse.BadRequest => w { fail("Got BadRequest") } + case _: UserClient.GetUserByNameResponse.NotFound => w { fail("Got NotFound") } } w.dismiss() }) diff --git a/modules/sample-dropwizard/src/test/scala/helpers/MockHelpers.scala b/modules/sample-dropwizard/src/test/scala/helpers/MockHelpers.scala new file mode 100644 index 0000000000..f0fbb4c14b --- /dev/null +++ b/modules/sample-dropwizard/src/test/scala/helpers/MockHelpers.scala @@ -0,0 +1,58 @@ +package helpers + +import com.fasterxml.jackson.databind.ObjectMapper +import io.netty.handler.codec.http.EmptyHttpHeaders +import java.io.ByteArrayInputStream +import java.nio.ByteBuffer +import java.nio.charset.StandardCharsets +import java.util.Collections +import java.util.concurrent.CompletableFuture +import javax.ws.rs.container.AsyncResponse +import org.asynchttpclient.Response +import org.asynchttpclient.uri.Uri +import org.mockito.{ArgumentMatchersSugar, MockitoSugar} +import org.scalatest.Assertions +import scala.reflect.ClassTag + +object MockHelpers extends Assertions with MockitoSugar with ArgumentMatchersSugar { + def mockAsyncResponse[T](implicit cls: ClassTag[T]): (AsyncResponse, CompletableFuture[T]) = { + val asyncResponse = mock[AsyncResponse] + val future = new CompletableFuture[T] + + when(asyncResponse.resume(any[T])) thenAnswer[AnyRef] { response => response match { + case t: Throwable => future.completeExceptionally(t) + case other: T => future.complete(other) + case other => fail(s"AsyncResponse.resume expected an object of type ${cls.runtimeClass.getName}, but got ${other.getClass.getName} instead") + }} + + (asyncResponse, future) + } + + def mockAHCResponse[T](uri: String, status: Int, maybeBody: Option[T] = None)(implicit mapper: ObjectMapper): Response = { + val response = mock[Response] + when(response.getUri) thenReturn Uri.create(uri) + when(response.hasResponseStatus) thenReturn true + when(response.getStatusCode) thenReturn status + when(response.getStatusText) thenReturn "Some Status" + when(response.hasResponseHeaders) thenReturn true + when(response.getHeaders) thenReturn EmptyHttpHeaders.INSTANCE + when(response.getHeader(any)) thenReturn null + when(response.getHeaders(any)) thenReturn Collections.emptyList() + maybeBody match { + case None => + when(response.hasResponseBody) thenReturn true + case Some(body) => + val responseBytes = mapper.writeValueAsBytes(body) + val responseStr = new String(responseBytes, StandardCharsets.UTF_8) + println(responseStr) + when(response.hasResponseBody) thenReturn true + when(response.getResponseBody(any)) thenReturn responseStr + when(response.getResponseBody) thenReturn responseStr + when(response.getResponseBodyAsStream) thenReturn new ByteArrayInputStream(responseBytes) + when(response.getResponseBodyAsByteBuffer) thenReturn ByteBuffer.wrap(responseBytes) + when(response.getResponseBodyAsBytes) thenReturn responseBytes + } + response + } + +} From 7b27ce3566101338a5a5024e6c75f309bf9b121d Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Thu, 7 Mar 2019 00:48:22 -0800 Subject: [PATCH 53/86] Add a GenerateSupportDefinitions phase to client & server --- .../twilio/guardrail/ClientGenerator.scala | 5 +- .../scala/com/twilio/guardrail/Common.scala | 19 +++-- .../twilio/guardrail/ServerGenerator.scala | 5 +- .../generators/AkkaHttpClientGenerator.scala | 3 + .../generators/AkkaHttpServerGenerator.scala | 3 + .../generators/EndpointsClientGenerator.scala | 2 + .../generators/EndpointsServerGenerator.scala | 1 + .../generators/Http4sClientGenerator.scala | 3 + .../generators/Http4sServerGenerator.scala | 3 + .../Java/AsyncHttpClientClientGenerator.scala | 71 ++++++++++++++++++- .../generators/Java/DropwizardGenerator.scala | 64 +---------------- .../Java/DropwizardServerGenerator.scala | 8 ++- .../scala/com/twilio/guardrail/package.scala | 2 +- .../protocol/terms/client/ClientTerm.scala | 3 +- .../protocol/terms/client/ClientTerms.scala | 4 +- .../protocol/terms/server/ServerTerm.scala | 3 +- .../protocol/terms/server/ServerTerms.scala | 6 +- .../src/test/scala/core/issues/Issue122.scala | 2 +- .../src/test/scala/core/issues/Issue126.scala | 2 +- .../src/test/scala/core/issues/Issue127.scala | 2 +- .../src/test/scala/core/issues/Issue165.scala | 2 +- .../src/test/scala/core/issues/Issue166.scala | 2 +- .../scala/support/SwaggerSpecRunner.scala | 6 +- .../test/scala/tests/core/BacktickTest.scala | 2 +- .../tests/core/DereferencingAliasesSpec.scala | 2 +- .../AkkaHttpClientGeneratorTest.scala | 4 +- .../akkaHttp/AkkaHttpServerTest.scala | 4 +- .../akkaHttp/CustomHeaderTest.scala | 2 +- .../tests/generators/akkaHttp/EnumTest.scala | 2 +- .../akkaHttp/StaticParametersTest.scala | 2 +- .../client/AkkaHttpClientTracingTest.scala | 4 +- .../akkaHttp/client/BasicTest.scala | 2 +- .../client/DefaultParametersTest.scala | 2 +- .../akkaHttp/client/FormFieldsTest.scala | 2 +- .../akkaHttp/client/HardcodedQSSpec.scala | 2 +- .../akkaHttp/client/HttpBodiesTest.scala | 2 +- .../akkaHttp/client/MultipartTest.scala | 2 +- .../akkaHttp/client/ParamConflictsTest.scala | 2 +- .../akkaHttp/client/SchemeTest.scala | 2 +- .../client/contentType/TextPlainTest.scala | 2 +- .../akkaHttp/server/FormFieldsTest.scala | 2 +- .../tests/generators/http4s/BasicTest.scala | 2 +- .../http4s/client/DefaultParametersTest.scala | 2 +- 43 files changed, 154 insertions(+), 113 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/ClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/ClientGenerator.scala index 0978b52824..c245f00a5d 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/ClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/ClientGenerator.scala @@ -10,7 +10,7 @@ import com.twilio.guardrail.terms.framework.FrameworkTerms import com.twilio.guardrail.terms.{ RouteMeta, ScalaTerms, SwaggerTerms } import java.net.URI -case class Clients[L <: LA](clients: List[Client[L]]) +case class Clients[L <: LA](clients: List[Client[L]], supportDefinitions: List[SupportDefinition[L]]) case class Client[L <: LA](pkg: List[String], clientName: String, imports: List[L#Import], @@ -35,6 +35,7 @@ object ClientGenerator { for { clientImports <- getImports(context.tracing) clientExtraImports <- getExtraImports(context.tracing) + supportDefinitions <- generateSupportDefinitions(context.tracing) clients <- groupedRoutes.traverse({ case (className, unsortedRoutes) => val routes = unsortedRoutes.sortBy(r => (r.path, r.method)) @@ -73,6 +74,6 @@ object ClientGenerator { Client[L](className, clientName, (clientImports ++ frameworkImports ++ clientExtraImports), staticDefns, client, responseDefinitions.flatten) } }) - } yield Clients[L](clients) + } yield Clients[L](clients, supportDefinitions) } } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala index 30a0ea1c57..b35ec0b478 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala @@ -19,6 +19,8 @@ import scala.io.AnsiColor import scala.meta._ import java.net.URI +case class SupportDefinition[L <: LA](className: L#TermName, imports: List[L#Import], definition: L#ClassDefinition) + object Common { val resolveFile: Path => List[String] => Path = root => _.foldLeft(root)(_.resolve(_)) @@ -68,16 +70,17 @@ object Common { for { clientMeta <- ClientGenerator .fromSwagger[L, F](context, frameworkImports)(serverUrls, basePath, groupedRoutes)(protocolElems) - Clients(clients) = clientMeta - } yield CodegenDefinitions[L](clients, List.empty) + Clients(clients, supportDefinitions) = clientMeta + } yield CodegenDefinitions[L](clients, List.empty, supportDefinitions) case CodegenTarget.Server => for { serverMeta <- ServerGenerator .fromSwagger[L, F](context, swagger, frameworkImports)(protocolElems) - Servers(servers) = serverMeta - } yield CodegenDefinitions[L](List.empty, servers) - case CodegenTarget.Models => Free.pure[F, CodegenDefinitions[L]](CodegenDefinitions[L](List.empty, List.empty)) + Servers(servers, supportDefinitions) = serverMeta + } yield CodegenDefinitions[L](List.empty, servers, supportDefinitions) + case CodegenTarget.Models => + Free.pure[F, CodegenDefinitions[L]](CodegenDefinitions[L](List.empty, List.empty, List.empty)) } } yield (proto, codegen) } @@ -98,7 +101,7 @@ object Common { val dtoComponents: List[String] = definitions ++ dtoPackage val ProtocolDefinitions(protocolElems, protocolImports, packageObjectImports, packageObjectContents) = proto - val CodegenDefinitions(clients, servers) = codegen + val CodegenDefinitions(clients, servers, supportDefinitions) = codegen for { protoOut <- protocolElems.traverse(writeProtocolDefinition(outputPath, pkgName, definitions, dtoComponents, customImports ++ protocolImports, _)) @@ -125,6 +128,7 @@ object Common { frameworkImplicitsFile <- frameworkImplicits.fold(Free.pure[F, Option[WriteTree]](None))({ case (name, defn) => renderFrameworkImplicits(pkgPath, pkgName, frameworkImports, protocolImports, defn, name).map(Option.apply) }) frameworkDefinitionsFiles <- frameworkDefinitions.traverse({ case (name, defn) => renderFrameworkDefinitions(pkgPath, pkgName, frameworkImports, defn, name) }) + supportDefinitionsFiles <- supportDefinitions.traverse({ case SupportDefinition(name, imports, defn) => renderFrameworkDefinitions(pkgPath, pkgName, imports, defn, name) }) } yield ( protocolDefinitions ++ @@ -132,7 +136,8 @@ object Common { files ++ implicits.toList ++ frameworkImplicitsFile.toList ++ - frameworkDefinitionsFiles + frameworkDefinitionsFiles ++ + supportDefinitionsFiles ).toList } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala index eda3bb0d81..3b62fa9f43 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala @@ -11,7 +11,7 @@ import com.twilio.guardrail.shims._ import com.twilio.guardrail.terms.framework.FrameworkTerms import com.twilio.guardrail.terms.{RouteMeta, ScalaTerms, SwaggerTerms} -case class Servers[L <: LA](servers: List[Server[L]]) +case class Servers[L <: LA](servers: List[Server[L]], supportDefinitions: List[SupportDefinition[L]]) case class Server[L <: LA](pkg: List[String], extraImports: List[L#Import], handlerDefinition: L#Definition, serverDefinitions: List[L#Definition]) case class TracingField[L <: LA](param: ScalaParameter[L], term: L#Term) case class RenderedRoutes[L <: LA]( @@ -45,6 +45,7 @@ object ServerGenerator { .mapValues(_.map(_._2)) .toList extraImports <- getExtraImports(context.tracing) + supportDefinitions <- generateSupportDefinitions(context.tracing) servers <- groupedRoutes.traverse { case (className, unsortedRoutes) => val routes = unsortedRoutes.sortBy(r => (r.path, r.method)) @@ -77,6 +78,6 @@ object ServerGenerator { Server(className, frameworkImports ++ extraImports, handlerSrc, classSrc) } } - } yield Servers[L](servers) + } yield Servers[L](servers, supportDefinitions) } } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpClientGenerator.scala index 311abf35a0..e45826455e 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpClientGenerator.scala @@ -320,6 +320,9 @@ object AkkaHttpClientGenerator { case GenerateResponseDefinitions(operationId, responses, protocolElems) => Target.pure(Http4sHelper.generateResponseDefinitions(operationId, responses, protocolElems)) + case GenerateSupportDefinitions(tracing) => + Target.pure(List.empty) + case BuildStaticDefns(clientName, tracingName, serverUrls, ctorArgs, tracing) => def extraConstructors(tracingName: Option[String], serverUrls: Option[NonEmptyList[URI]], diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpServerGenerator.scala index 50e550072b..14db8c24a1 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpServerGenerator.scala @@ -183,6 +183,9 @@ object AkkaHttpServerGenerator { Option(q"import scala.language.higherKinds") ).flatten } + + case GenerateSupportDefinitions(tracing) => + Target.pure(List.empty) } def httpMethodToAkka(method: HttpMethod): Target[Term] = method match { diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsClientGenerator.scala index c749231783..d3b58d2834 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsClientGenerator.scala @@ -399,6 +399,8 @@ object EndpointsClientGenerator { Target.pure(List(List(formatHost(serverUrls)) ++ (if (tracing) Some(formatClientName(tracingName)) else None))) case GenerateResponseDefinitions(operationId, responses, protocolElems) => Target.pure(Http4sHelper.generateResponseDefinitions(operationId, responses, protocolElems)) + case GenerateSupportDefinitions(tracing) => + Target.pure(List.empty) case BuildStaticDefns(clientName, tracingName, serverUrls, ctorArgs, tracing) => def paramsToArgs(params: List[List[Term.Param]]): List[List[Term]] = params diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsServerGenerator.scala index a6fdf1f5aa..fe9be5de43 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsServerGenerator.scala @@ -13,6 +13,7 @@ object EndpointsServerGenerator { case GenerateRoutes(resourceName, basePath, routes, protocolElems) => ??? case RenderHandler(handlerName, methodSigs, handlerDefinitions) => ??? case GetExtraRouteParams(tracing) => ??? + case GenerateSupportDefinitions(tracing) => ??? case RenderClass(resourceName, handlerName, annotations, combinedRouteTerms, extraRouteParams, responseDefinitions, supportDefinitions) => ??? case GetExtraImports(tracing) => ??? } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sClientGenerator.scala index d5890090c4..a777a4c743 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sClientGenerator.scala @@ -314,6 +314,9 @@ object Http4sClientGenerator { case GenerateResponseDefinitions(operationId, responses, protocolElems) => Target.pure(Http4sHelper.generateResponseDefinitions(operationId, responses, protocolElems)) + case GenerateSupportDefinitions(tracing) => + Target.pure(List.empty) + case BuildStaticDefns(clientName, tracingName, serverUrls, ctorArgs, tracing) => def extraConstructors(tracingName: Option[String], serverUrls: Option[NonEmptyList[URI]], diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sServerGenerator.scala index 90bd4b414e..dc1e3035fb 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sServerGenerator.scala @@ -84,6 +84,9 @@ object Http4sServerGenerator { } else Target.pure(List.empty) } yield res + case GenerateSupportDefinitions(tracing) => + Target.pure(List.empty) + case RenderClass(resourceName, handlerName, _, combinedRouteTerms, extraRouteParams, responseDefinitions, supportDefinitions) => for { _ <- Target.log.debug("Http4sServerGenerator", "server")(s"renderClass(${resourceName}, ${handlerName}, , ${extraRouteParams})") diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index ec15285559..b87e18c429 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -17,7 +17,7 @@ import com.twilio.guardrail.generators.syntax.Java._ import com.twilio.guardrail.languages.JavaLanguage import com.twilio.guardrail.protocol.terms.client._ import com.twilio.guardrail.terms.RouteMeta -import com.twilio.guardrail.{RenderedClientOperation, StaticDefns, Target, languages} +import com.twilio.guardrail.{RenderedClientOperation, StaticDefns, SupportDefinition, Target} import java.net.URI import java.util @@ -380,6 +380,75 @@ object AsyncHttpClientClientGenerator { Target.pure(List(abstractResponseClass)) + case GenerateSupportDefinitions(tracing) => + for { + httpErrorImports <- List( + "org.asynchttpclient.Response" + ).map(safeParseRawImport).sequence + } yield { + def addStdConstructors(cls: ClassOrInterfaceDeclaration): Unit = { + val msgConstructor = cls.addConstructor(PUBLIC) + msgConstructor.addParameter(new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("message"))) + msgConstructor.setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new MethodCallExpr("super", new NameExpr("message"))) + ))) + + val msgCauseConstructor = cls.addConstructor(PUBLIC) + msgCauseConstructor.setParameters(new NodeList( + new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("message")), + new Parameter(util.EnumSet.of(FINAL), THROWABLE_TYPE, new SimpleName("cause")) + )) + msgCauseConstructor.setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new MethodCallExpr("super", new NameExpr("message"), new NameExpr("cause"))) + ))) + } + + val clientExceptionClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, ABSTRACT), false, "ClientException") + clientExceptionClass.addExtendedType("RuntimeException") + addStdConstructors(clientExceptionClass) + + val marshallingExceptionClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, "MarshallingException") + marshallingExceptionClass.addExtendedType("ClientException") + addStdConstructors(marshallingExceptionClass) + + val httpErrorClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, "HttpError") + httpErrorClass.addExtendedType("ClientException") + httpErrorClass.addField(RESPONSE_TYPE, "response", PRIVATE, FINAL) + + val responseConstructor = httpErrorClass.addConstructor(PUBLIC) + responseConstructor.addParameter(new Parameter(util.EnumSet.of(FINAL), RESPONSE_TYPE, new SimpleName("response"))) + responseConstructor.setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new MethodCallExpr( + "super", + new BinaryExpr( + new StringLiteralExpr("HTTP server responded with status "), + new MethodCallExpr(new NameExpr("response"), "getStatusCode"), + BinaryExpr.Operator.PLUS + ) + )), + new ExpressionStmt(new AssignExpr( + new FieldAccessExpr(new ThisExpr, "response"), + new NameExpr("response"), + AssignExpr.Operator.ASSIGN + )) + ))) + + val getResponseMethod = httpErrorClass.addMethod("getResponse", PUBLIC) + getResponseMethod.setType(RESPONSE_TYPE) + getResponseMethod.setBody(new BlockStmt(new NodeList( + new ReturnStmt(new FieldAccessExpr(new ThisExpr, "response")) + ))) + + val showerClass = SHOWER_CLASS_DEF + + List( + SupportDefinition[JavaLanguage](new Name("ClientException"), List.empty, clientExceptionClass), + SupportDefinition[JavaLanguage](new Name("MarshallingException"), List.empty, marshallingExceptionClass), + SupportDefinition[JavaLanguage](new Name("HttpError"), httpErrorImports, httpErrorClass), + SupportDefinition[JavaLanguage](new Name(showerClass.getNameAsString), List.empty, SHOWER_CLASS_DEF) + ) + } + case BuildStaticDefns(clientName, tracingName, serverUrls, ctorArgs, tracing) => Target.pure( StaticDefns[JavaLanguage]( diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala index e4217188aa..da21e77465 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala @@ -16,8 +16,6 @@ import com.twilio.guardrail.terms.framework._ import java.util object DropwizardGenerator { - private val RESPONSE_TYPE = JavaParser.parseClassOrInterfaceType("org.asynchttpclient.Response") - object FrameworkInterp extends (FrameworkTerm[JavaLanguage, ?] ~> Target) { def apply[T](term: FrameworkTerm[JavaLanguage, T]): Target[T] = term match { case FileType(format) => safeParseType(format.getOrElse("java.io.File")) @@ -30,67 +28,7 @@ object DropwizardGenerator { Target.pure(None) case GetFrameworkDefinitions() => - def addStdConstructors(cls: ClassOrInterfaceDeclaration): Unit = { - val msgConstructor = cls.addConstructor(PUBLIC) - msgConstructor.addParameter(new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("message"))) - msgConstructor.setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new MethodCallExpr("super", new NameExpr("message"))) - ))) - - val msgCauseConstructor = cls.addConstructor(PUBLIC) - msgCauseConstructor.setParameters(new NodeList( - new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("message")), - new Parameter(util.EnumSet.of(FINAL), THROWABLE_TYPE, new SimpleName("cause")) - )) - msgCauseConstructor.setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new MethodCallExpr("super", new NameExpr("message"), new NameExpr("cause"))) - ))) - } - - val clientExceptionClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, ABSTRACT), false, "ClientException") - clientExceptionClass.addExtendedType("RuntimeException") - addStdConstructors(clientExceptionClass) - - val marshallingExceptionClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, "MarshallingException") - marshallingExceptionClass.addExtendedType("ClientException") - addStdConstructors(marshallingExceptionClass) - - val httpErrorClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, "HttpError") - httpErrorClass.addExtendedType("ClientException") - httpErrorClass.addField(RESPONSE_TYPE, "response", PRIVATE, FINAL) - - val responseConstructor = httpErrorClass.addConstructor(PUBLIC) - responseConstructor.addParameter(new Parameter(util.EnumSet.of(FINAL), RESPONSE_TYPE, new SimpleName("response"))) - responseConstructor.setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new MethodCallExpr( - "super", - new BinaryExpr( - new StringLiteralExpr("HTTP server responded with status "), - new MethodCallExpr(new NameExpr("response"), "getStatusCode"), - BinaryExpr.Operator.PLUS - ) - )), - new ExpressionStmt(new AssignExpr( - new FieldAccessExpr(new ThisExpr, "response"), - new NameExpr("response"), - AssignExpr.Operator.ASSIGN - )) - ))) - - val getResponseMethod = httpErrorClass.addMethod("getResponse", PUBLIC) - getResponseMethod.setType(RESPONSE_TYPE) - getResponseMethod.setBody(new BlockStmt(new NodeList( - new ReturnStmt(new FieldAccessExpr(new ThisExpr, "response")) - ))) - - val showerClass = SHOWER_CLASS_DEF - - Target.pure(List( - (new Name("ClientException"), clientExceptionClass), - (new Name("MarshallingException"), marshallingExceptionClass), - (new Name("HttpError"), httpErrorClass), - (new Name(showerClass.getNameAsString), showerClass) - )) + Target.pure(List.empty) case LookupStatusCode(key) => def parseStatusCode(code: Int, termName: String): Target[(Int, Name)] = diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala index 110d39ac99..8f213b48f0 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala @@ -13,7 +13,7 @@ import com.github.javaparser.ast.body._ import com.github.javaparser.ast.expr._ import com.github.javaparser.ast.stmt._ import com.twilio.guardrail.generators.Response -import com.twilio.guardrail.{RenderedRoutes, Target} +import com.twilio.guardrail.{RenderedRoutes, SupportDefinition, Target} import com.twilio.guardrail.generators.syntax.Java._ import com.twilio.guardrail.languages.JavaLanguage import com.twilio.guardrail.protocol.terms.server._ @@ -315,6 +315,12 @@ object DropwizardServerGenerator { abstractResponseClass :: Nil } + case GenerateSupportDefinitions(tracing) => + val showerClass = SHOWER_CLASS_DEF + Target.pure(List( + SupportDefinition[JavaLanguage](new Name(showerClass.getNameAsString), List.empty, showerClass) + )) + case RenderClass(className, handlerName, classAnnotations, combinedRouteTerms, extraRouteParams, responseDefinitions, supportDefinitions) => def doRender: Target[List[BodyDeclaration[_]]] = { val cls = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, className) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/package.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/package.scala index 49ed8ea188..31ccb3c070 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/package.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/package.scala @@ -13,7 +13,7 @@ import com.twilio.guardrail.terms.{ ScalaTerm, SwaggerTerm } import com.twilio.swagger.core.StructuredLogger package guardrail { - case class CodegenDefinitions[L <: LA](clients: List[Client[L]], servers: List[Server[L]]) + case class CodegenDefinitions[L <: LA](clients: List[Client[L]], servers: List[Server[L]], supportDefinitions: List[SupportDefinition[L]]) sealed trait ErrorKind case object UserError extends ErrorKind diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/client/ClientTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/client/ClientTerm.scala index 0567ad74fa..e647817bc5 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/client/ClientTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/client/ClientTerm.scala @@ -4,7 +4,7 @@ import cats.data.NonEmptyList import com.twilio.guardrail.generators.{ Responses, ScalaParameters } import com.twilio.guardrail.languages.LA import com.twilio.guardrail.terms.RouteMeta -import com.twilio.guardrail.{ RenderedClientOperation, StaticDefns, StrictProtocolElems } +import com.twilio.guardrail.{RenderedClientOperation, StaticDefns, StrictProtocolElems, SupportDefinition} import java.net.URI sealed trait ClientTerm[L <: LA, T] @@ -21,6 +21,7 @@ case class ClientClsArgs[L <: LA](tracingName: Option[String], serverUrls: Optio extends ClientTerm[L, List[List[L#MethodParameter]]] case class GenerateResponseDefinitions[L <: LA](operationId: String, responses: Responses[L], protocolElems: List[StrictProtocolElems[L]]) extends ClientTerm[L, List[L#Definition]] +case class GenerateSupportDefinitions[L <: LA](tracing: Boolean) extends ClientTerm[L, List[SupportDefinition[L]]] case class BuildStaticDefns[L <: LA](clientName: String, tracingName: Option[String], serverUrls: Option[NonEmptyList[URI]], diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/client/ClientTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/client/ClientTerms.scala index 4e3a2f8700..3c2bcb99b7 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/client/ClientTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/client/ClientTerms.scala @@ -6,7 +6,7 @@ import cats.free.Free import com.twilio.guardrail.generators.{ Responses, ScalaParameters } import com.twilio.guardrail.languages.LA import com.twilio.guardrail.terms.RouteMeta -import com.twilio.guardrail.{ RenderedClientOperation, StaticDefns, StrictProtocolElems } +import com.twilio.guardrail.{RenderedClientOperation, StaticDefns, StrictProtocolElems, SupportDefinition} import java.net.URI class ClientTerms[L <: LA, F[_]](implicit I: InjectK[ClientTerm[L, ?], F]) { @@ -24,6 +24,8 @@ class ClientTerms[L <: LA, F[_]](implicit I: InjectK[ClientTerm[L, ?], F]) { Free.inject[ClientTerm[L, ?], F](ClientClsArgs[L](tracingName, serverUrls, tracing)) def generateResponseDefinitions(operationId: String, responses: Responses[L], protocolElems: List[StrictProtocolElems[L]]): Free[F, List[L#Definition]] = Free.inject[ClientTerm[L, ?], F](GenerateResponseDefinitions[L](operationId, responses, protocolElems)) + def generateSupportDefinitions(tracing: Boolean): Free[F, List[SupportDefinition[L]]] = + Free.inject[ClientTerm[L, ?], F](GenerateSupportDefinitions[L](tracing)) def buildStaticDefns(clientName: String, tracingName: Option[String], serverUrls: Option[NonEmptyList[URI]], diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala index 2d303412d2..d54b2131ca 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala @@ -1,6 +1,6 @@ package com.twilio.guardrail.protocol.terms.server -import com.twilio.guardrail.{ RenderedRoutes, StrictProtocolElems, TracingField } +import com.twilio.guardrail.{RenderedRoutes, StrictProtocolElems, SupportDefinition, TracingField} import com.twilio.guardrail.terms.RouteMeta import com.twilio.guardrail.languages.LA import com.twilio.guardrail.generators.{ Responses, ScalaParameters } @@ -16,6 +16,7 @@ case class GenerateRoutes[L <: LA](resourceName: String, case class GetExtraRouteParams[L <: LA](tracing: Boolean) extends ServerTerm[L, List[L#MethodParameter]] case class GenerateResponseDefinitions[L <: LA](operationId: String, responses: Responses[L], protocolElems: List[StrictProtocolElems[L]]) extends ServerTerm[L, List[L#Definition]] +case class GenerateSupportDefinitions[L <: LA](tracing: Boolean) extends ServerTerm[L, List[SupportDefinition[L]]] case class RenderClass[L <: LA](className: String, handlerName: String, annotations: List[L#Annotation], diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala index 42c5ef55ea..37745d87f3 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala @@ -2,8 +2,8 @@ package com.twilio.guardrail.protocol.terms.server import cats.InjectK import cats.free.Free -import com.twilio.guardrail.{ RenderedRoutes, StrictProtocolElems, TracingField } -import com.twilio.guardrail.generators.{ Responses, ScalaParameters } +import com.twilio.guardrail.{RenderedRoutes, StrictProtocolElems, SupportDefinition, TracingField} +import com.twilio.guardrail.generators.{Responses, ScalaParameters} import com.twilio.guardrail.terms.RouteMeta import com.twilio.guardrail.languages.LA import io.swagger.v3.oas.models.Operation @@ -20,6 +20,8 @@ class ServerTerms[L <: LA, F[_]](implicit I: InjectK[ServerTerm[L, ?], F]) { Free.inject[ServerTerm[L, ?], F](GetExtraRouteParams(tracing)) def generateResponseDefinitions(operationId: String, responses: Responses[L], protocolElems: List[StrictProtocolElems[L]]): Free[F, List[L#Definition]] = Free.inject[ServerTerm[L, ?], F](GenerateResponseDefinitions(operationId, responses, protocolElems)) + def generateSupportDefinitions(tracing: Boolean): Free[F, List[SupportDefinition[L]]] = + Free.inject[ServerTerm[L, ?], F](GenerateSupportDefinitions(tracing)) def renderClass(resourceName: String, handlerName: String, annotations: List[L#Annotation], diff --git a/modules/codegen/src/test/scala/core/issues/Issue122.scala b/modules/codegen/src/test/scala/core/issues/Issue122.scala index 88e8ce8892..d639455684 100644 --- a/modules/codegen/src/test/scala/core/issues/Issue122.scala +++ b/modules/codegen/src/test/scala/core/issues/Issue122.scala @@ -53,7 +53,7 @@ class Issue122 extends FunSuite with Matchers with SwaggerSpecRunner { test("Ensure clients are able to pass sequences of values for array form parameters") { val ( _, - Clients(Client(tags, className, imports, staticDefns, cls, _) :: _), + Clients(Client(tags, className, imports, staticDefns, cls, _) :: _, Nil), _ ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val cmp = companionForStaticDefns(staticDefns) diff --git a/modules/codegen/src/test/scala/core/issues/Issue126.scala b/modules/codegen/src/test/scala/core/issues/Issue126.scala index 17d53a3c33..9c005c3962 100644 --- a/modules/codegen/src/test/scala/core/issues/Issue126.scala +++ b/modules/codegen/src/test/scala/core/issues/Issue126.scala @@ -25,7 +25,7 @@ class Issue126 extends FunSuite with Matchers with SwaggerSpecRunner { val ( _, _, - Servers(Server(pkg, extraImports, genHandler, genResource :: Nil) :: Nil) + Servers(Server(pkg, extraImports, genHandler, genResource :: Nil) :: Nil, Nil) ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val handler = q""" diff --git a/modules/codegen/src/test/scala/core/issues/Issue127.scala b/modules/codegen/src/test/scala/core/issues/Issue127.scala index e7cdf76d81..7475de6916 100644 --- a/modules/codegen/src/test/scala/core/issues/Issue127.scala +++ b/modules/codegen/src/test/scala/core/issues/Issue127.scala @@ -36,7 +36,7 @@ class Issue127 extends FunSuite with Matchers with SwaggerSpecRunner { val ( _, _, - Servers(Server(pkg, extraImports, genHandler, genResource :: Nil) :: Nil) + Servers(Server(pkg, extraImports, genHandler, genResource :: Nil) :: Nil, Nil) ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val handler = q""" diff --git a/modules/codegen/src/test/scala/core/issues/Issue165.scala b/modules/codegen/src/test/scala/core/issues/Issue165.scala index 4cb4f7f3b1..ffd88c8363 100644 --- a/modules/codegen/src/test/scala/core/issues/Issue165.scala +++ b/modules/codegen/src/test/scala/core/issues/Issue165.scala @@ -38,7 +38,7 @@ class Issue165 extends FunSuite with Matchers with SwaggerSpecRunner { |""".stripMargin test("Ensure routes are generated") { - val (_, _, Servers(Server(_, _, genHandler, genResource :: _) :: Nil)) = runSwaggerSpec(swagger)(Context.empty, Http4s) + val (_, _, Servers(Server(_, _, genHandler, genResource :: _) :: Nil, Nil)) = runSwaggerSpec(swagger)(Context.empty, Http4s) val handler = q""" trait StoreHandler[F[_]] { diff --git a/modules/codegen/src/test/scala/core/issues/Issue166.scala b/modules/codegen/src/test/scala/core/issues/Issue166.scala index 3da5f8b59c..56f2fa75ff 100644 --- a/modules/codegen/src/test/scala/core/issues/Issue166.scala +++ b/modules/codegen/src/test/scala/core/issues/Issue166.scala @@ -54,7 +54,7 @@ class Issue166 extends FunSuite with Matchers with SwaggerSpecRunner { ) val ProtocolDefinitions(ClassDefinition(_, _, cls, _, _) :: Nil, _, _, _) = proto - val CodegenDefinitions(Nil, Nil) = codegen + val CodegenDefinitions(Nil, Nil, Nil) = codegen val definition = q""" case class Blix(map: String) diff --git a/modules/codegen/src/test/scala/support/SwaggerSpecRunner.scala b/modules/codegen/src/test/scala/support/SwaggerSpecRunner.scala index 3e40d33a46..40bd1b4763 100644 --- a/modules/codegen/src/test/scala/support/SwaggerSpecRunner.scala +++ b/modules/codegen/src/test/scala/support/SwaggerSpecRunner.scala @@ -35,7 +35,7 @@ trait SwaggerSpecRunner { import F._ import Sw._ - val (proto, CodegenDefinitions(clients, Nil)) = Target.unsafeExtract( + val (proto, CodegenDefinitions(clients, Nil, Nil)) = Target.unsafeExtract( Common .prepareDefinitions[ScalaLanguage, CodegenApplication[ScalaLanguage, ?]]( CodegenTarget.Client, @@ -45,7 +45,7 @@ trait SwaggerSpecRunner { .foldMap(framework) ) - val (_, CodegenDefinitions(Nil, servers)) = Target.unsafeExtract( + val (_, CodegenDefinitions(Nil, servers, Nil)) = Target.unsafeExtract( Common .prepareDefinitions[ScalaLanguage, CodegenApplication[ScalaLanguage, ?]]( CodegenTarget.Server, @@ -55,7 +55,7 @@ trait SwaggerSpecRunner { .foldMap(framework) ) - (proto, Clients(clients), Servers(servers)) + (proto, Clients(clients, Nil), Servers(servers, Nil)) } } diff --git a/modules/codegen/src/test/scala/tests/core/BacktickTest.scala b/modules/codegen/src/test/scala/tests/core/BacktickTest.scala index e6c666ce91..47cf836cc7 100644 --- a/modules/codegen/src/test/scala/tests/core/BacktickTest.scala +++ b/modules/codegen/src/test/scala/tests/core/BacktickTest.scala @@ -68,7 +68,7 @@ class BacktickTest extends FunSuite with Matchers with SwaggerSpecRunner { test("Ensure paths are generated with escapes") { val ( _, - Clients(Client(tags, className, imports, staticDefns, cls, _) :: _), + Clients(Client(tags, className, imports, staticDefns, cls, _) :: _, Nil), _ ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val cmp = companionForStaticDefns(staticDefns) diff --git a/modules/codegen/src/test/scala/tests/core/DereferencingAliasesSpec.scala b/modules/codegen/src/test/scala/tests/core/DereferencingAliasesSpec.scala index 3a4cb7b628..fe33e5906d 100644 --- a/modules/codegen/src/test/scala/tests/core/DereferencingAliasesSpec.scala +++ b/modules/codegen/src/test/scala/tests/core/DereferencingAliasesSpec.scala @@ -63,7 +63,7 @@ class DereferencingAliasesSpec extends FunSuite with Matchers with SwaggerSpecRu test("All types should be dereferenced") { val ( ProtocolDefinitions(_ :: _ :: _ :: ClassDefinition(_, _, cls, staticDefns, _) :: _, _, _, _), - Clients(Client(_, clientName, _, clientStaticDefns, clientCls, _) :: _), + Clients(Client(_, clientName, _, clientStaticDefns, clientCls, _) :: _, Nil), _ ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val cmp = companionForStaticDefns(staticDefns) diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/AkkaHttpClientGeneratorTest.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/AkkaHttpClientGeneratorTest.scala index d1fb34be40..859c2662f4 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/AkkaHttpClientGeneratorTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/AkkaHttpClientGeneratorTest.scala @@ -112,7 +112,7 @@ class AkkaHttpClientGeneratorTest extends FunSuite with Matchers with SwaggerSpe test("Ensure responses are generated") { val ( _, - Clients(Client(tags, className, _, staticDefns, cls, _) :: Nil), + Clients(Client(tags, className, _, staticDefns, cls, _) :: Nil, Nil), _ ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val cmp = companionForStaticDefns(staticDefns) @@ -182,7 +182,7 @@ class AkkaHttpClientGeneratorTest extends FunSuite with Matchers with SwaggerSpe test("Ensure traced responses are generated") { val ( _, - Clients(List(Client(tags, className, _, staticDefns, cls, _))), + Clients(List(Client(tags, className, _, staticDefns, cls, _)), Nil), _ ) = runSwaggerSpec(swagger)(Context.empty.copy(framework = Some("akka-http"), tracing = true), AkkaHttp) val cmp = companionForStaticDefns(staticDefns) diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/AkkaHttpServerTest.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/AkkaHttpServerTest.scala index fe689dc7ee..69c3c2d8fd 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/AkkaHttpServerTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/AkkaHttpServerTest.scala @@ -127,7 +127,7 @@ class AkkaHttpServerTest extends FunSuite with Matchers with SwaggerSpecRunner { val ( _, _, - Servers(Server(pkg, extraImports, genHandler, genResource :: Nil) :: Nil) + Servers(Server(pkg, extraImports, genHandler, genResource :: Nil) :: Nil, Nil) ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val handler = q""" @@ -268,7 +268,7 @@ class AkkaHttpServerTest extends FunSuite with Matchers with SwaggerSpecRunner { val ( _, _, - Servers(Server(pkg, extraImports, genHandler, genResource :: Nil) :: Nil) + Servers(Server(pkg, extraImports, genHandler, genResource :: Nil) :: Nil, Nil) ) = runSwaggerSpec(swagger)(Context.empty.copy(tracing = true), AkkaHttp) val handler = q""" diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/CustomHeaderTest.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/CustomHeaderTest.scala index 5934985695..e25360cc56 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/CustomHeaderTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/CustomHeaderTest.scala @@ -39,7 +39,7 @@ class CustomHeaderTest extends FunSuite with Matchers with SwaggerSpecRunner { |""".stripMargin test("Should produce static parameter constraints") { - val (_, Clients(client :: Nil), Servers(Server(_, _, genHandler, genResource :: Nil) :: Nil)) = + val (_, Clients(client :: Nil, Nil), Servers(Server(_, _, genHandler, genResource :: Nil) :: Nil, Nil)) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val handler = diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/EnumTest.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/EnumTest.scala index 33ac68122a..6bcb5333ab 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/EnumTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/EnumTest.scala @@ -94,7 +94,7 @@ class EnumTest extends FunSuite with Matchers with SwaggerSpecRunner { test("Use enums") { val ( _, - Clients(Client(tags, className, _, staticDefns, cls, _) :: _), + Clients(Client(tags, className, _, staticDefns, cls, _) :: _, Nil), _ ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val cmp = companionForStaticDefns(staticDefns) diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/StaticParametersTest.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/StaticParametersTest.scala index b48118c828..a08eb169b5 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/StaticParametersTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/StaticParametersTest.scala @@ -31,7 +31,7 @@ class StaticParametersTest extends FunSuite with Matchers with SwaggerSpecRunner |""".stripMargin test("Should produce static parameter constraints") { - val (_, _, Servers(Server(_, _, genHandler, genResource :: Nil) :: Nil)) = + val (_, _, Servers(Server(_, _, genHandler, genResource :: Nil) :: Nil, Nil)) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val handler = q""" diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/AkkaHttpClientTracingTest.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/AkkaHttpClientTracingTest.scala index 4faa829cb6..c6f5cd5801 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/AkkaHttpClientTracingTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/AkkaHttpClientTracingTest.scala @@ -32,7 +32,7 @@ class AkkaHttpClientTracingTest extends FunSuite with Matchers with SwaggerSpecR | description: Success |""".stripMargin - val (_, Clients(Client(_, _, _, _, cls, _) :: _), _) = + val (_, Clients(Client(_, _, _, _, cls, _) :: _, Nil), _) = runSwaggerSpec(swagger)(Context.empty.copy(tracing = true), AkkaHttp) val client = q""" @@ -87,7 +87,7 @@ class AkkaHttpClientTracingTest extends FunSuite with Matchers with SwaggerSpecR val ( _, - Clients(Client(tags, className, _, _, cls, _) :: _), + Clients(Client(tags, className, _, _, cls, _) :: _, Nil), _ ) = runSwaggerSpec(swagger)(Context.empty.copy(tracing = true), AkkaHttp) diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/BasicTest.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/BasicTest.scala index 43f7a4722b..808a51f6cc 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/BasicTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/BasicTest.scala @@ -107,7 +107,7 @@ class BasicTest extends FunSuite with Matchers with SwaggerSpecRunner { test("Properly handle all methods") { val ( _, - Clients(Client(tags, className, _, _, cls, _) :: _), + Clients(Client(tags, className, _, _, cls, _) :: _, Nil), _ ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/DefaultParametersTest.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/DefaultParametersTest.scala index 61766832a8..345123dcd4 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/DefaultParametersTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/DefaultParametersTest.scala @@ -123,7 +123,7 @@ class DefaultParametersTest extends FunSuite with Matchers with SwaggerSpecRunne test("Ensure responses are generated") { val ( _, - Clients(Client(tags, className, _, staticDefns, cls, _) :: _), + Clients(Client(tags, className, _, staticDefns, cls, _) :: _, Nil), _ ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val cmp = companionForStaticDefns(staticDefns) diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/FormFieldsTest.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/FormFieldsTest.scala index e7cd2cd9f8..4cbf0aa90b 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/FormFieldsTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/FormFieldsTest.scala @@ -43,7 +43,7 @@ class FormFieldsTest extends FunSuite with Matchers with SwaggerSpecRunner { test("Properly handle all methods") { val ( _, - Clients(Client(tags, className, _, _, cls, _) :: _), + Clients(Client(tags, className, _, _, cls, _) :: _, Nil), _ ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/HardcodedQSSpec.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/HardcodedQSSpec.scala index 66477608c8..01599b3f2c 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/HardcodedQSSpec.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/HardcodedQSSpec.scala @@ -47,7 +47,7 @@ class HardcodedQSSpec extends FunSuite with Matchers with SwaggerSpecRunner { test("Test all cases") { val ( _, - Clients(Client(tags, className, _, _, cls, _) :: _), + Clients(Client(tags, className, _, _, cls, _) :: _, Nil), _ ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/HttpBodiesTest.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/HttpBodiesTest.scala index 2df23a4f0d..d6e65d8ca2 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/HttpBodiesTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/HttpBodiesTest.scala @@ -85,7 +85,7 @@ class HttpBodiesTest extends FunSuite with Matchers with SwaggerSpecRunner { test("Properly handle all methods") { val ( _, - Clients(Client(tags, className, _, _, cls, _) :: _), + Clients(Client(tags, className, _, _, cls, _) :: _, Nil), _ ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/MultipartTest.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/MultipartTest.scala index 3e8ad569d1..07f87f49e5 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/MultipartTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/MultipartTest.scala @@ -48,7 +48,7 @@ class MultipartTest extends FunSuite with Matchers with SwaggerSpecRunner { test("Multipart form data") { val ( _, - Clients(Client(_, className, _, _, cls, _) :: _), + Clients(Client(_, className, _, _, cls, _) :: _, Nil), _ ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/ParamConflictsTest.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/ParamConflictsTest.scala index d9c6f95e7e..6390301da4 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/ParamConflictsTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/ParamConflictsTest.scala @@ -46,7 +46,7 @@ class ParamConflictsTest extends FunSuite with Matchers with SwaggerSpecRunner { test("Generate non-conflicting names in clients") { val ( _, - Clients(Client(tags, className, _, _, cls, _) :: _), + Clients(Client(tags, className, _, _, cls, _) :: _, Nil), _ ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/SchemeTest.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/SchemeTest.scala index cb4904e9ca..575a69b704 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/SchemeTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/SchemeTest.scala @@ -35,7 +35,7 @@ class SchemeTest extends FunSuite with Matchers with SwaggerSpecRunner { |""".stripMargin test("Use first scheme") { - val (_, Clients(Client(_, clientName, _, staticDefns, cls, _) :: _), _) = + val (_, Clients(Client(_, clientName, _, staticDefns, cls, _) :: _, Nil), _) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val cmp = companionForStaticDefns(staticDefns) diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/contentType/TextPlainTest.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/contentType/TextPlainTest.scala index ba79028903..2901d46cf9 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/contentType/TextPlainTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/client/contentType/TextPlainTest.scala @@ -36,7 +36,7 @@ class TextPlainTest extends FunSuite with Matchers with SwaggerSpecRunner { test("Properly handle all methods") { val ( _, - Clients(Client(tags, className, _, staticDefns, cls, _) :: _), + Clients(Client(tags, className, _, staticDefns, cls, _) :: _, Nil), _ ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val cmp = companionForStaticDefns(staticDefns) diff --git a/modules/codegen/src/test/scala/tests/generators/akkaHttp/server/FormFieldsTest.scala b/modules/codegen/src/test/scala/tests/generators/akkaHttp/server/FormFieldsTest.scala index 580a7f22fd..2a8d29b05b 100644 --- a/modules/codegen/src/test/scala/tests/generators/akkaHttp/server/FormFieldsTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/akkaHttp/server/FormFieldsTest.scala @@ -46,7 +46,7 @@ class FormFieldsServerTest extends FunSuite with Matchers with SwaggerSpecRunner val ( _, _, - Servers(Server(pkg, extraImports, genHandler, genResource :: Nil) :: Nil) + Servers(Server(pkg, extraImports, genHandler, genResource :: Nil) :: Nil, Nil) ) = runSwaggerSpec(swagger)(Context.empty, AkkaHttp) val handler = q""" diff --git a/modules/codegen/src/test/scala/tests/generators/http4s/BasicTest.scala b/modules/codegen/src/test/scala/tests/generators/http4s/BasicTest.scala index 8cdbdfe931..3bbf557692 100644 --- a/modules/codegen/src/test/scala/tests/generators/http4s/BasicTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/http4s/BasicTest.scala @@ -107,7 +107,7 @@ class BasicTest extends FunSuite with Matchers with SwaggerSpecRunner { test("Properly handle all methods") { val ( _, - Clients(Client(tags, className, _, staticDefns, cls, statements) :: _), + Clients(Client(tags, className, _, staticDefns, cls, statements) :: _, Nil), _ ) = runSwaggerSpec(swagger)(Context.empty, Http4s) val cmp = companionForStaticDefns(staticDefns) diff --git a/modules/codegen/src/test/scala/tests/generators/http4s/client/DefaultParametersTest.scala b/modules/codegen/src/test/scala/tests/generators/http4s/client/DefaultParametersTest.scala index d4350b01ab..3ba09060c5 100644 --- a/modules/codegen/src/test/scala/tests/generators/http4s/client/DefaultParametersTest.scala +++ b/modules/codegen/src/test/scala/tests/generators/http4s/client/DefaultParametersTest.scala @@ -123,7 +123,7 @@ class DefaultParametersTest extends FunSuite with Matchers with SwaggerSpecRunne test("Ensure responses are generated") { val ( _, - Clients(Client(tags, className, _, staticDefns, cls, statements) :: _), + Clients(Client(tags, className, _, staticDefns, cls, statements) :: _, Nil), _ ) = runSwaggerSpec(swagger)(Context.empty, Http4s) From 33d8b6780741fc4e48c9da3e73c90d1e5478b697 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Thu, 7 Mar 2019 01:45:08 -0800 Subject: [PATCH 54/86] Move AHC and ObjectMapper creation/config code to support classes --- .../Java/AsyncHttpClientClientGenerator.scala | 300 +++++++++++------- 1 file changed, 181 insertions(+), 119 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index b87e18c429..881e3e18d8 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -2,10 +2,11 @@ package com.twilio.guardrail.generators.Java import cats.data.NonEmptyList import cats.instances.list._ +import cats.instances.map import cats.syntax.traverse._ import cats.~> import com.github.javaparser.JavaParser -import com.github.javaparser.ast.NodeList +import com.github.javaparser.ast.{ImportDeclaration, NodeList} import com.github.javaparser.ast.Modifier._ import com.github.javaparser.ast.`type`.{ClassOrInterfaceType, Type, VoidType} import com.github.javaparser.ast.body._ @@ -170,6 +171,157 @@ object AsyncHttpClientClientGenerator { } } + private def generateClientExceptionClasses(): Target[List[(List[ImportDeclaration], ClassOrInterfaceDeclaration)]] = { + for { + httpErrorImports <- List( + "org.asynchttpclient.Response" + ).map(safeParseRawImport).sequence + } yield { + def addStdConstructors(cls: ClassOrInterfaceDeclaration): Unit = { + cls.addConstructor(PUBLIC) + .addParameter(new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("message"))) + .setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new MethodCallExpr("super", new NameExpr("message"))) + ))) + + cls.addConstructor(PUBLIC) + .setParameters(new NodeList( + new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("message")), + new Parameter(util.EnumSet.of(FINAL), THROWABLE_TYPE, new SimpleName("cause")) + )) + .setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new MethodCallExpr("super", new NameExpr("message"), new NameExpr("cause"))) + ))) + } + + val clientExceptionClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, ABSTRACT), false, "ClientException") + .addExtendedType("RuntimeException") + addStdConstructors(clientExceptionClass) + + val marshallingExceptionClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, "MarshallingException") + .addExtendedType("ClientException") + addStdConstructors(marshallingExceptionClass) + + val httpErrorClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, "HttpError") + .addExtendedType("ClientException") + httpErrorClass.addField(RESPONSE_TYPE, "response", PRIVATE, FINAL) + + httpErrorClass.addConstructor(PUBLIC) + .addParameter(new Parameter(util.EnumSet.of(FINAL), RESPONSE_TYPE, new SimpleName("response"))) + .setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new MethodCallExpr( + "super", + new BinaryExpr( + new StringLiteralExpr("HTTP server responded with status "), + new MethodCallExpr(new NameExpr("response"), "getStatusCode"), + BinaryExpr.Operator.PLUS + ) + )), + new ExpressionStmt(new AssignExpr( + new FieldAccessExpr(new ThisExpr, "response"), + new NameExpr("response"), + AssignExpr.Operator.ASSIGN + )) + ))) + + httpErrorClass.addMethod("getResponse", PUBLIC) + .setType(RESPONSE_TYPE) + .setBody(new BlockStmt(new NodeList( + new ReturnStmt(new FieldAccessExpr(new ThisExpr, "response")) + ))) + + List( + (List.empty, clientExceptionClass), + (List.empty, marshallingExceptionClass), + (httpErrorImports, httpErrorClass) + ) + } + } + + def generateAsyncHttpClientSupportClass(): Target[(List[ImportDeclaration], ClassOrInterfaceDeclaration)] = { + for { + imports <- List( + "java.util.concurrent.CompletionStage", + "java.util.function.Function", + "org.asynchttpclient.AsyncHttpClient", + "org.asynchttpclient.AsyncHttpClientConfig", + "org.asynchttpclient.DefaultAsyncHttpClient", + "org.asynchttpclient.DefaultAsyncHttpClientConfig", + "org.asynchttpclient.Request", + "org.asynchttpclient.Response" + ).map(safeParseRawImport).sequence + } yield { + val cls = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, ABSTRACT), false, "AsyncHttpClientSupport") + cls.addConstructor(PRIVATE) + + val ahcConfigBuilder = new ObjectCreationExpr(null, DEFAULT_ASYNC_HTTP_CLIENT_CONFIG_BUILDER_TYPE, new NodeList()) + val ahcConfig = new MethodCallExpr( + List( + ("setMaxRequestRetry", 2), + ("setConnectTimeout", 3000), + ("setRequestTimeout", 10000), + ("setReadTimeout", 3000), + ("setMaxConnections", 1024), + ("setMaxConnectionsPerHost", 1024) + ).foldLeft[Expression](ahcConfigBuilder)({ case (lastExpr, (name, arg)) => + new MethodCallExpr(lastExpr, name, new NodeList[Expression](new IntegerLiteralExpr(arg))) + }), "build", new NodeList[Expression]() + ) + + cls.addMethod("createDefaultAsyncHttpClient", PUBLIC, STATIC) + .setType(ASYNC_HTTP_CLIENT_TYPE) + .setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new VariableDeclarationExpr(new VariableDeclarator(ASYNC_HTTP_CLIENT_CONFIG_TYPE, "config", ahcConfig), FINAL)), + new ReturnStmt( + new ObjectCreationExpr(null, DEFAULT_ASYNC_HTTP_CLIENT_TYPE, new NodeList(new NameExpr("config"))) + ) + ))) + + + cls.addMethod("createHttpClient", PUBLIC, STATIC) + .setType(HTTP_CLIENT_FUNCTION_TYPE) + .addParameter(new Parameter(util.EnumSet.of(FINAL), ASYNC_HTTP_CLIENT_TYPE, new SimpleName("client"))) + .setBody(new BlockStmt(new NodeList( + new ReturnStmt(new LambdaExpr( + new NodeList(new Parameter(util.EnumSet.of(FINAL), REQUEST_TYPE, new SimpleName("request"))), + new ExpressionStmt(new MethodCallExpr(new MethodCallExpr(new NameExpr("client"), "executeRequest", new NodeList[Expression](new NameExpr("request"))), "toCompletableFuture")), + true + )) + ))) + + (imports, cls) + } + } + + def generateJacksonSupportClass(): Target[(List[ImportDeclaration], ClassOrInterfaceDeclaration)] = { + for { + imports <- List( + "com.fasterxml.jackson.databind.ObjectMapper", + "com.fasterxml.jackson.datatype.jdk8.Jdk8Module", + "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule" + ).map(safeParseRawImport).sequence + } yield { + val cls = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, ABSTRACT), false, "JacksonSupport") + cls.addConstructor(PRIVATE) + + cls.addMethod("configureObjectMapper", PUBLIC, STATIC) + .setType(OBJECT_MAPPER_TYPE) + .addParameter(new Parameter(util.EnumSet.of(FINAL), OBJECT_MAPPER_TYPE, new SimpleName("objectMapper"))) + .setBody(new BlockStmt(new NodeList( + List("JavaTimeModule", "Jdk8Module").map(name => + new ExpressionStmt(new MethodCallExpr( + new NameExpr("objectMapper"), + "registerModule", + new NodeList[Expression](new ObjectCreationExpr(null, JavaParser.parseClassOrInterfaceType(name), new NodeList())) + )) + ) :+ + new ReturnStmt(new NameExpr("objectMapper")): _* + ))) + + (imports, cls) + } + } + object ClientTermInterp extends (ClientTerm[JavaLanguage, ?] ~> Target) { def apply[T](term: ClientTerm[JavaLanguage, T]): Target[T] = term match { case GenerateClientOperation(_, RouteMeta(pathStr, httpMethod, operation), methodName, tracing, parameters, responses) => @@ -276,12 +428,6 @@ object AsyncHttpClientClientGenerator { "com.fasterxml.jackson.core.JsonProcessingException", "com.fasterxml.jackson.core.type.TypeReference", "com.fasterxml.jackson.databind.ObjectMapper", - "com.fasterxml.jackson.datatype.jdk8.Jdk8Module", - "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", - "org.asynchttpclient.AsyncHttpClient", - "org.asynchttpclient.AsyncHttpClientConfig", - "org.asynchttpclient.DefaultAsyncHttpClient", - "org.asynchttpclient.DefaultAsyncHttpClientConfig", "org.asynchttpclient.Request", "org.asynchttpclient.RequestBuilder", "org.asynchttpclient.Response", @@ -382,70 +528,18 @@ object AsyncHttpClientClientGenerator { case GenerateSupportDefinitions(tracing) => for { - httpErrorImports <- List( - "org.asynchttpclient.Response" - ).map(safeParseRawImport).sequence + exceptionClasses <- generateClientExceptionClasses() + ahcSupport <- generateAsyncHttpClientSupportClass() + (ahcSupportImports, ahcSupportClass) = ahcSupport + jacksonSupport <- generateJacksonSupportClass() + (jacksonSupportImports, jacksonSupportClass) = jacksonSupport } yield { - def addStdConstructors(cls: ClassOrInterfaceDeclaration): Unit = { - val msgConstructor = cls.addConstructor(PUBLIC) - msgConstructor.addParameter(new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("message"))) - msgConstructor.setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new MethodCallExpr("super", new NameExpr("message"))) - ))) - - val msgCauseConstructor = cls.addConstructor(PUBLIC) - msgCauseConstructor.setParameters(new NodeList( - new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("message")), - new Parameter(util.EnumSet.of(FINAL), THROWABLE_TYPE, new SimpleName("cause")) - )) - msgCauseConstructor.setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new MethodCallExpr("super", new NameExpr("message"), new NameExpr("cause"))) - ))) - } - - val clientExceptionClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, ABSTRACT), false, "ClientException") - clientExceptionClass.addExtendedType("RuntimeException") - addStdConstructors(clientExceptionClass) - - val marshallingExceptionClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, "MarshallingException") - marshallingExceptionClass.addExtendedType("ClientException") - addStdConstructors(marshallingExceptionClass) - - val httpErrorClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, "HttpError") - httpErrorClass.addExtendedType("ClientException") - httpErrorClass.addField(RESPONSE_TYPE, "response", PRIVATE, FINAL) - - val responseConstructor = httpErrorClass.addConstructor(PUBLIC) - responseConstructor.addParameter(new Parameter(util.EnumSet.of(FINAL), RESPONSE_TYPE, new SimpleName("response"))) - responseConstructor.setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new MethodCallExpr( - "super", - new BinaryExpr( - new StringLiteralExpr("HTTP server responded with status "), - new MethodCallExpr(new NameExpr("response"), "getStatusCode"), - BinaryExpr.Operator.PLUS - ) - )), - new ExpressionStmt(new AssignExpr( - new FieldAccessExpr(new ThisExpr, "response"), - new NameExpr("response"), - AssignExpr.Operator.ASSIGN - )) - ))) - - val getResponseMethod = httpErrorClass.addMethod("getResponse", PUBLIC) - getResponseMethod.setType(RESPONSE_TYPE) - getResponseMethod.setBody(new BlockStmt(new NodeList( - new ReturnStmt(new FieldAccessExpr(new ThisExpr, "response")) - ))) - - val showerClass = SHOWER_CLASS_DEF - - List( - SupportDefinition[JavaLanguage](new Name("ClientException"), List.empty, clientExceptionClass), - SupportDefinition[JavaLanguage](new Name("MarshallingException"), List.empty, marshallingExceptionClass), - SupportDefinition[JavaLanguage](new Name("HttpError"), httpErrorImports, httpErrorClass), - SupportDefinition[JavaLanguage](new Name(showerClass.getNameAsString), List.empty, SHOWER_CLASS_DEF) + exceptionClasses.map({ case (imports, cls) => + SupportDefinition[JavaLanguage](new Name(cls.getNameAsString), imports, cls) + }) ++ List( + SupportDefinition[JavaLanguage](new Name(ahcSupportClass.getNameAsString), ahcSupportImports, ahcSupportClass), + SupportDefinition[JavaLanguage](new Name(jacksonSupportClass.getNameAsString), jacksonSupportImports, jacksonSupportClass), + SupportDefinition[JavaLanguage](new Name(SHOWER_CLASS_DEF.getNameAsString), List.empty, SHOWER_CLASS_DEF) ) } @@ -563,8 +657,11 @@ object AsyncHttpClientClientGenerator { addSetter(STRING_TYPE, "clientName", nonNullInitializer) } addSetter(HTTP_CLIENT_FUNCTION_TYPE, "httpClient", optionalInitializer(new NameExpr(_))) - addSetter(OBJECT_MAPPER_TYPE, "objectMapper", - optionalInitializer(name => new MethodCallExpr("configureObjectMapper", new NameExpr(name)))) + addSetter(OBJECT_MAPPER_TYPE, "objectMapper", optionalInitializer(name => new MethodCallExpr( + new NameExpr("JacksonSupport"), + "configureObjectMapper", + new NodeList[Expression](new NameExpr(name))) + )) val buildMethod = builderClass.addMethod("build", PUBLIC) buildMethod.setType(clientType) @@ -585,53 +682,18 @@ object AsyncHttpClientClientGenerator { )) ))) } - addInternalGetter(HTTP_CLIENT_FUNCTION_TYPE, "httpClient", - new MethodCallExpr("createDefaultHttpClient")) - addInternalGetter(OBJECT_MAPPER_TYPE, "objectMapper", - new MethodCallExpr("configureObjectMapper", new ObjectCreationExpr(null, OBJECT_MAPPER_TYPE, new NodeList()))) - - val ahcConfigBuilder = new ObjectCreationExpr(null, DEFAULT_ASYNC_HTTP_CLIENT_CONFIG_BUILDER_TYPE, new NodeList()) - val ahcConfig = new MethodCallExpr( - List( - ("setMaxRequestRetry", 2), - ("setConnectTimeout", 3000), - ("setRequestTimeout", 10000), - ("setReadTimeout", 3000), - ("setMaxConnections", 1024), - ("setMaxConnectionsPerHost", 1024) - ).foldLeft[Expression](ahcConfigBuilder)({ case (lastExpr, (name, arg)) => - new MethodCallExpr(lastExpr, name, new NodeList[Expression](new IntegerLiteralExpr(arg))) - }), "build", new NodeList[Expression]()) - - val createDefaultHttpClientMethod = builderClass.addMethod("createDefaultHttpClient", PRIVATE, STATIC) - createDefaultHttpClientMethod.setType(HTTP_CLIENT_FUNCTION_TYPE) - createDefaultHttpClientMethod.setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new VariableDeclarationExpr(new VariableDeclarator(ASYNC_HTTP_CLIENT_CONFIG_TYPE, "config", ahcConfig), FINAL)), - new ExpressionStmt(new VariableDeclarationExpr(new VariableDeclarator( - ASYNC_HTTP_CLIENT_TYPE, - "client", - new ObjectCreationExpr(null, DEFAULT_ASYNC_HTTP_CLIENT_TYPE, new NodeList(new NameExpr("config"))) - ), FINAL)), - new ReturnStmt(new LambdaExpr( - new NodeList(new Parameter(util.EnumSet.of(FINAL), REQUEST_TYPE, new SimpleName("request"))), - new ExpressionStmt(new MethodCallExpr(new MethodCallExpr(new NameExpr("client"), "executeRequest", new NodeList[Expression](new NameExpr("request"))), "toCompletableFuture")), - true - )) - ))) - - val configureObjectMapperMethod = builderClass.addMethod("configureObjectMapper", PRIVATE, STATIC) - configureObjectMapperMethod.setType(OBJECT_MAPPER_TYPE) - configureObjectMapperMethod.addParameter(new Parameter(util.EnumSet.of(FINAL), OBJECT_MAPPER_TYPE, new SimpleName("objectMapper"))) - configureObjectMapperMethod.setBody(new BlockStmt(new NodeList( - List("JavaTimeModule", "Jdk8Module").map(name => - new ExpressionStmt(new MethodCallExpr( - new NameExpr("objectMapper"), - "registerModule", - new NodeList[Expression](new ObjectCreationExpr(null, JavaParser.parseClassOrInterfaceType(name), new NodeList())) - )) - ) :+ - new ReturnStmt(new NameExpr("objectMapper")): _* - ))) + addInternalGetter(HTTP_CLIENT_FUNCTION_TYPE, "httpClient", new MethodCallExpr( + new NameExpr("AsyncHttpClientSupport"), + "createHttpClient", + new NodeList[Expression]( + new MethodCallExpr(new NameExpr("AsyncHttpClientSupport"), "createDefaultAsyncHttpClient") + ) + )) + addInternalGetter(OBJECT_MAPPER_TYPE, "objectMapper", new MethodCallExpr( + new NameExpr("JacksonSupport"), + "configureObjectMapper", + new NodeList[Expression](new ObjectCreationExpr(null, OBJECT_MAPPER_TYPE, new NodeList())) + )) val clientClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, clientName) From 5db82ebc19cd0308419c40e6660ddc84ca80af0d Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Thu, 7 Mar 2019 02:41:55 -0800 Subject: [PATCH 55/86] Be sure to appropriately unbox Java types in parameters --- .../Java/AsyncHttpClientClientGenerator.scala | 6 ++---- .../generators/Java/DropwizardServerGenerator.scala | 2 ++ .../guardrail/generators/Java/JacksonGenerator.scala | 10 ++-------- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index 881e3e18d8..fc89a6783b 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -333,6 +333,7 @@ object AsyncHttpClientClientGenerator { val method = new MethodDeclaration(util.EnumSet.of(PUBLIC), new VoidType, methodName) method.setType(completionStageType.setTypeArguments(responseParentType)) + parameters.parameters.foreach(p => p.param.setType(p.param.getType.unbox)) val pathParams = parameters.pathParams.map(_.param) val qsParams = parameters.queryStringParams.map(_.param) val formParams = parameters.formParams.map(_.param) @@ -456,10 +457,7 @@ object AsyncHttpClientClientGenerator { val responseInnerClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, STATIC), false, responseName); responseInnerClass.addExtendedType(abstractClassName) valueType.foreach({ vt => - val finalValueType: Type = vt match { - case p: ClassOrInterfaceType if p.isBoxedType => p.toUnboxedType - case other => other - } + val finalValueType: Type = vt.unbox responseInnerClass.addField(finalValueType, "value", PRIVATE, FINAL) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala index 8f213b48f0..ba7be1e229 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala @@ -93,6 +93,8 @@ object DropwizardServerGenerator { val commonPathPrefix = findPathPrefix(routes.map(_._3.path)) val (routeMethods, handlerMethodSigs) = routes.map({ case (operationId, tracingFields, sr @ RouteMeta(path, httpMethod, operation), parameters, responses) => + parameters.parameters.foreach(p => p.param.setType(p.param.getType.unbox)) + val method = new MethodDeclaration(util.EnumSet.of(PUBLIC), new VoidType, operationId) val httpMethodAnnotation = httpMethod match { case HttpMethod.DELETE => new MarkerAnnotationExpr("DELETE") diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala index 6ff5cdb582..df44487bb0 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -41,19 +41,13 @@ object JacksonGenerator { }) val requiredTerms = req.map({ param => - val types = param.term.getType match { - case cls: ClassOrInterfaceType => Try(cls.toUnboxedType).getOrElse(cls) - case tpe => tpe - } + val types = param.term.getType.unbox ParameterTerm(param.name, param.term.getNameAsString, types, types) }) val optionalTerms = opt.flatMap(param => param.term.getType match { case cls: ClassOrInterfaceType => cls.getTypeArguments.asScala.flatMap({ typeArgument => - val parameterType = typeArgument.asScala.headOption.map({ - case innerCls: ClassOrInterfaceType => Try(innerCls.toUnboxedType).getOrElse(innerCls) - case tpe => tpe - }) + val parameterType = typeArgument.asScala.headOption.map(_.unbox) parameterType.map(pt => ParameterTerm(param.name, param.term.getNameAsString, param.term.getType, pt)) }) case _ => None From bc5d58907fecc7a66957a3385dcbc84f35cee6bd Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Thu, 7 Mar 2019 02:50:02 -0800 Subject: [PATCH 56/86] Use Shower for interpolating path components in Java --- .../src/main/scala/com/twilio/guardrail/SwaggerUtil.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala index a281701e7d..78e1e38f19 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala @@ -9,6 +9,7 @@ import io.swagger.v3.oas.models.responses._ import cats.{FlatMap, Foldable} import cats.free.Free import cats.implicits._ +import com.github.javaparser.ast.NodeList import com.github.javaparser.ast.expr._ import com.twilio.guardrail.terms.{ScalaTerms, SwaggerTerms} import com.twilio.guardrail.terms.framework.FrameworkTerms @@ -385,7 +386,11 @@ object SwaggerUtil { def generateUrlPathParams(path: String, pathArgs: List[ScalaParameter[JavaLanguage]]): Target[Expression] = { val term: atto.Parser[Expression] = variable.flatMap { binding => lookupName(binding, pathArgs) { param => - ok(new MethodCallExpr(new NameExpr(param.paramName.asString), "toString")) + ok(new MethodCallExpr( + new MethodCallExpr(new NameExpr("Shower"), "getInstance"), + "show", + new NodeList[Expression](new NameExpr(param.paramName.asString)) + )) } } val other: atto.Parser[String] = many1(notChar('{')).map(_.toList.mkString) From be4aa18fd01f28b0933d08d7e6544817e0f9ca91 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Fri, 8 Mar 2019 11:11:51 -0800 Subject: [PATCH 57/86] Support property default values in Jackson protocol objects --- .../generators/Java/JacksonGenerator.scala | 81 ++++++++++--------- 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala index df44487bb0..a44abfa8cf 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -15,7 +15,7 @@ import com.twilio.guardrail.protocol.terms.protocol._ import java.util.Locale import scala.collection.JavaConverters._ import com.github.javaparser.JavaParser -import com.github.javaparser.ast.{ImportDeclaration, NodeList} +import com.github.javaparser.ast.{ImportDeclaration, Node, NodeList} import com.github.javaparser.ast.stmt._ import com.github.javaparser.ast.Modifier.{ABSTRACT, FINAL, PRIVATE, PUBLIC, STATIC} import com.github.javaparser.ast.body._ @@ -25,35 +25,32 @@ import scala.language.existentials import scala.util.Try object JacksonGenerator { - import ProtocolGenerator._ - private case class ParameterTerm(propertyName: String, parameterName: String, fieldType: Type, - parameterType: Type) + parameterType: Type, + defaultValue: Option[Expression]) // returns a tuple of (requiredTerms, optionalTerms) + // note that required terms _that have a default value_ are conceptually optional. private def sortParams(params: List[ProtocolParameter[JavaLanguage]]): (List[ParameterTerm], List[ParameterTerm]) = { - // TODO: if a required field has a default specified, include it in optionalTerms instead - val (req, opt) = params.partition(_.term.getType match { - case cls: ClassOrInterfaceType => !cls.isOptional - case _ => true - }) - - val requiredTerms = req.map({ param => - val types = param.term.getType.unbox - ParameterTerm(param.name, param.term.getNameAsString, types, types) - }) - - val optionalTerms = opt.flatMap(param => param.term.getType match { - case cls: ClassOrInterfaceType => cls.getTypeArguments.asScala.flatMap({ typeArgument => - val parameterType = typeArgument.asScala.headOption.map(_.unbox) - parameterType.map(pt => ParameterTerm(param.name, param.term.getNameAsString, param.term.getType, pt)) - }) + def defaultValueToExpression(defaultValue: Option[Node]): Option[Expression] = defaultValue match { + case Some(expr: Expression) => Some(expr) case _ => None - }) + } - (requiredTerms, optionalTerms) + params.map({ case ProtocolParameter(term, name, _, _, _, selfDefaultValue) => + val parameterType = if (term.getType.isOptional) { + term.getType.containedType.unbox + } else { + term.getType + } + val defaultValue = defaultValueToExpression(selfDefaultValue) + + ParameterTerm(name, term.getNameAsString, term.getType, parameterType, defaultValue) + }).partition( + pt => !pt.fieldType.isOptional && pt.defaultValue.isEmpty + ) } private def addParents(cls: ClassOrInterfaceDeclaration, parentOpt: Option[SuperClass[JavaLanguage]]): Unit = { @@ -312,7 +309,7 @@ object JacksonGenerator { )) }) - terms.foreach({ case ParameterTerm(propertyName, parameterName, fieldType, _) => + terms.foreach({ case ParameterTerm(propertyName, parameterName, fieldType, _, _) => val field: FieldDeclaration = dtoClass.addField(fieldType, parameterName, PRIVATE, FINAL) field.addSingleMemberAnnotation("JsonProperty", new StringLiteralExpr(propertyName)) }) @@ -320,7 +317,7 @@ object JacksonGenerator { val primaryConstructor = dtoClass.addConstructor(PRIVATE) primaryConstructor.addMarkerAnnotation("JsonCreator") primaryConstructor.setParameters(new NodeList( - terms.map({ case ParameterTerm(propertyName, parameterName, fieldType, _) => + terms.map({ case ParameterTerm(propertyName, parameterName, fieldType, _, _) => new Parameter(util.EnumSet.of(FINAL), fieldType, new SimpleName(parameterName)) .addAnnotation(new SingleMemberAnnotationExpr(new Name("JsonProperty"), new StringLiteralExpr(propertyName))) }): _* @@ -328,7 +325,7 @@ object JacksonGenerator { primaryConstructor.setBody( new BlockStmt( new NodeList( - terms.map({ case ParameterTerm(_, parameterName, fieldType, _) => + terms.map({ case ParameterTerm(_, parameterName, fieldType, _, _) => new ExpressionStmt(new AssignExpr( new FieldAccessExpr(new ThisExpr, parameterName), fieldType match { @@ -343,7 +340,7 @@ object JacksonGenerator { ) // TODO: handle emptyToNull in the return for the getters - terms.foreach({ case ParameterTerm(_, parameterName, fieldType, _) => + terms.foreach({ case ParameterTerm(_, parameterName, fieldType, _, _) => val method = dtoClass.addMethod(s"get${parameterName.capitalize}", PUBLIC) method.setType(fieldType) method.setBody(new BlockStmt(new NodeList( @@ -376,7 +373,7 @@ object JacksonGenerator { ) )))) - val equalsConditions: List[Expression] = terms.map({ case ParameterTerm(_, parameterName, fieldType, _) => + val equalsConditions: List[Expression] = terms.map({ case ParameterTerm(_, parameterName, fieldType, _, _) => fieldType match { case _: PrimitiveType => new BinaryExpr( new FieldAccessExpr(new ThisExpr, parameterName), @@ -431,7 +428,7 @@ object JacksonGenerator { val builderMethod = dtoClass.addMethod("builder", PUBLIC, STATIC) builderMethod.setType("Builder") builderMethod.setParameters(new NodeList( - requiredTerms.map({ case ParameterTerm(_, parameterName, _, parameterType) => + requiredTerms.map({ case ParameterTerm(_, parameterName, _, parameterType, _) => new Parameter(util.EnumSet.of(FINAL), parameterType, new SimpleName(parameterName)) }): _* )) @@ -441,7 +438,7 @@ object JacksonGenerator { null, JavaParser.parseClassOrInterfaceType("Builder"), new NodeList(requiredTerms.map({ - case ParameterTerm(_, parameterName, _, _) => new NameExpr(parameterName) + case ParameterTerm(_, parameterName, _, _, _) => new NameExpr(parameterName) }): _*) ) ) @@ -449,22 +446,30 @@ object JacksonGenerator { val builderClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, STATIC), false, "Builder") - requiredTerms.foreach({ case ParameterTerm(_, parameterName, fieldType, _) => + requiredTerms.foreach({ case ParameterTerm(_, parameterName, fieldType, _, _) => builderClass.addField(fieldType, parameterName, PRIVATE, FINAL) }) - optionalTerms.foreach({ case ParameterTerm(_, parameterName, fieldType, _) => - // TODO: initialize with default if one is specified - builderClass.addFieldWithInitializer(fieldType, parameterName, new MethodCallExpr("java.util.Optional.empty"), PRIVATE) + optionalTerms.foreach({ case ParameterTerm(_, parameterName, fieldType, _, defaultValue) => + val initializer = defaultValue.fold[Expression]( + new MethodCallExpr(new NameExpr("java.util.Optional"), "empty") + )(dv => + if (fieldType.isOptional) { + new MethodCallExpr(new NameExpr("java.util.Optional"), "of", new NodeList[Expression](dv)) + } else { + dv + } + ) + builderClass.addFieldWithInitializer(fieldType, parameterName, initializer, PRIVATE) }) val builderConstructor = builderClass.addConstructor(PRIVATE) builderConstructor.setParameters(new NodeList( - requiredTerms.map({ case ParameterTerm(_, parameterName, _, parameterType) => + requiredTerms.map({ case ParameterTerm(_, parameterName, _, parameterType, _) => new Parameter(util.EnumSet.of(FINAL), parameterType, new SimpleName(parameterName)) }): _* )) builderConstructor.setBody(new BlockStmt(new NodeList( - requiredTerms.map({ case ParameterTerm(_, parameterName, fieldType, _) => + requiredTerms.map({ case ParameterTerm(_, parameterName, fieldType, _, _) => new ExpressionStmt( new AssignExpr( new FieldAccessExpr(new ThisExpr, parameterName), @@ -479,7 +484,7 @@ object JacksonGenerator { ))) // TODO: leave out with${name}() if readOnlyKey? - optionalTerms.foreach({ case ParameterTerm(_, parameterName, _, parameterType) => + optionalTerms.foreach({ case ParameterTerm(_, parameterName, fieldType, parameterType, _) => val setter = builderClass.addMethod(s"with${parameterName.capitalize}", PUBLIC) setter.setType("Builder") setter.addAndGetParameter(new Parameter(util.EnumSet.of(FINAL), parameterType, new SimpleName(parameterName))) @@ -487,7 +492,7 @@ object JacksonGenerator { new ExpressionStmt( new AssignExpr( new FieldAccessExpr(new ThisExpr, parameterName), - new MethodCallExpr("java.util.Optional.of", new NameExpr(parameterName)), + if (fieldType.isOptional) new MethodCallExpr("java.util.Optional.of", new NameExpr(parameterName)) else new NameExpr(parameterName), AssignExpr.Operator.ASSIGN ) ), @@ -645,7 +650,7 @@ object JacksonGenerator { addParents(abstractClass, parentOpt) - terms.foreach({ case ParameterTerm(_, parameterName, fieldType, _) => + terms.foreach({ case ParameterTerm(_, parameterName, fieldType, _, _) => val method: MethodDeclaration = abstractClass.addMethod(s"get${parameterName.capitalize}", PUBLIC, ABSTRACT) method.setType(fieldType) method.setBody(null) From 91558a0aace473756f4233af66ec4742f7edffda Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Sat, 9 Mar 2019 03:24:10 -0800 Subject: [PATCH 58/86] Use a CallBuilder pattern for AsyncHttpClient operations The main method call takes only required parameters and returns a ${operationName}CallBuilder instance, which allows the user to optionally add optional parameters, additional headers, etc., and then call .call() to execute the request. --- .../Java/AsyncHttpClientClientGenerator.scala | 236 ++++++++++-------- .../guardrail/generators/syntax/Java.scala | 16 +- .../Dropwizard/DropwizardRoundTripTest.scala | 2 +- 3 files changed, 154 insertions(+), 100 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index fc89a6783b..29c4c8a42b 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -2,7 +2,6 @@ package com.twilio.guardrail.generators.Java import cats.data.NonEmptyList import cats.instances.list._ -import cats.instances.map import cats.syntax.traverse._ import cats.~> import com.github.javaparser.JavaParser @@ -71,95 +70,73 @@ object AsyncHttpClientClientGenerator { doShow(param.argType) } - private def optionIfPresent(optionVarType: Type, optionVarName: String, innerStatement: Statement): Statement = { - new ExpressionStmt(new MethodCallExpr(new NameExpr(optionVarName), "ifPresent", new NodeList[Expression]( - new LambdaExpr(new NodeList(new Parameter(util.EnumSet.of(FINAL), optionVarType, new SimpleName("arg"))), - innerStatement, - true - )) - )) - } - - private def generateBuilderMethodCalls(params: List[ScalaParameter[JavaLanguage]], builderMethodName: String): List[Statement] = { - val needsMultipart = params.exists(_.isFile) - params.map({ param => - val finalMethodName = if (needsMultipart) "addBodyPart" else builderMethodName - val argName = if (param.required) param.paramName.asString else "arg" - val containedType = param.argType.containedType - val isList = if (param.required) param.argType.isNamed("List") else containedType.isNamed("List") - val listType = if (param.required) containedType else containedType.containedType - - val makeArgList: String => NodeList[Expression] = name => - if (param.isFile) { - new NodeList[Expression](new ObjectCreationExpr(null, FILE_PART_TYPE, new NodeList( - new StringLiteralExpr(param.argName.value), - new NameExpr(name) - ))) - } else if (needsMultipart) { - new NodeList[Expression](new ObjectCreationExpr(null, STRING_PART_TYPE, new NodeList( - new StringLiteralExpr(param.argName.value), - showParam(param, Some(name)) - ))) - } else { - new NodeList[Expression](new StringLiteralExpr(param.argName.value), showParam(param, Some(name))) - } + private def generateBuilderMethodCall(param: ScalaParameter[JavaLanguage], builderMethodName: String, needsMultipart: Boolean): Statement = { + val finalMethodName = if (needsMultipart) "addBodyPart" else builderMethodName + val argName = param.paramName.asString + val isList = param.argType.isNamed("List") - val builderStatement: Statement = if (isList) { - new ForEachStmt( - new VariableDeclarationExpr(listType, "member", FINAL), - new NameExpr(argName), - new BlockStmt(new NodeList( - new ExpressionStmt(new MethodCallExpr(new NameExpr("builder"), finalMethodName, makeArgList("member"))) - )) - ) + val makeArgList: String => NodeList[Expression] = name => + if (param.isFile) { + new NodeList[Expression](new ObjectCreationExpr(null, FILE_PART_TYPE, new NodeList( + new StringLiteralExpr(param.argName.value), + new NameExpr(name) + ))) + } else if (needsMultipart) { + new NodeList[Expression](new ObjectCreationExpr(null, STRING_PART_TYPE, new NodeList( + new StringLiteralExpr(param.argName.value), + showParam(param, Some(name)) + ))) } else { - new ExpressionStmt(new MethodCallExpr(new NameExpr("builder"), finalMethodName, makeArgList(argName))) + new NodeList[Expression](new StringLiteralExpr(param.argName.value), showParam(param, Some(name))) } - if (param.required) { - builderStatement - } else { - optionIfPresent(containedType, param.paramName.asString, builderStatement) - } - }) + if (isList) { + new ForEachStmt( + new VariableDeclarationExpr(param.argType.containedType, "member", FINAL), + new NameExpr(argName), + new BlockStmt(new NodeList( + new ExpressionStmt(new MethodCallExpr(new NameExpr("builder"), finalMethodName, makeArgList("member"))) + )) + ) + } else { + new ExpressionStmt(new MethodCallExpr(new NameExpr("builder"), finalMethodName, makeArgList(argName))) + } } - private def generateBodyMethodCall(param: Option[ScalaParameter[JavaLanguage]]): Option[Statement] = { + private def generateBodyMethodCall(param: ScalaParameter[JavaLanguage]): Statement = { def wrapSetBody(expr: Expression): MethodCallExpr = { new MethodCallExpr(new NameExpr("builder"), "setBody", new NodeList[Expression](expr)) } - param.map({ param => - if (param.isFile) { - new ExpressionStmt(wrapSetBody(new ObjectCreationExpr(null, FILE_PART_TYPE, new NodeList( - new StringLiteralExpr(param.argName.value), - new NameExpr(param.paramName.asString) - )))) - } else { - new TryStmt( - new BlockStmt(new NodeList( - new ExpressionStmt(wrapSetBody(new MethodCallExpr( - new FieldAccessExpr(new ThisExpr, "objectMapper"), - "writeValueAsString", - new NodeList[Expression](new NameExpr(param.paramName.asString)) - ))) - )), - new NodeList( - new CatchClause( - new Parameter(util.EnumSet.of(FINAL), JSON_PROCESSING_EXCEPTION_TYPE, new SimpleName("e")), - new BlockStmt(new NodeList( - new ThrowStmt(new ObjectCreationExpr( - null, - ILLEGAL_ARGUMENT_EXCEPTION_TYPE, - new NodeList(new MethodCallExpr(new NameExpr("e"), "getMessage"), new NameExpr("e")) - )) + if (param.isFile) { + new ExpressionStmt(wrapSetBody(new ObjectCreationExpr(null, FILE_PART_TYPE, new NodeList( + new StringLiteralExpr(param.argName.value), + new NameExpr(param.paramName.asString) + )))) + } else { + new TryStmt( + new BlockStmt(new NodeList( + new ExpressionStmt(wrapSetBody(new MethodCallExpr( + new FieldAccessExpr(new ThisExpr, "objectMapper"), + "writeValueAsString", + new NodeList[Expression](new NameExpr(param.paramName.asString)) + ))) + )), + new NodeList( + new CatchClause( + new Parameter(util.EnumSet.of(FINAL), JSON_PROCESSING_EXCEPTION_TYPE, new SimpleName("e")), + new BlockStmt(new NodeList( + new ThrowStmt(new ObjectCreationExpr( + null, + MARSHALLING_EXCEPTION_TYPE, + new NodeList(new MethodCallExpr(new NameExpr("e"), "getMessage"), new NameExpr("e")) )) - ) - ), - null - ) - } - }) + )) + ) + ), + null + ) + } } private def jacksonTypeReferenceFor(tpe: Type): Expression = { @@ -326,20 +303,23 @@ object AsyncHttpClientClientGenerator { def apply[T](term: ClientTerm[JavaLanguage, T]): Target[T] = term match { case GenerateClientOperation(_, RouteMeta(pathStr, httpMethod, operation), methodName, tracing, parameters, responses) => val responseParentName = s"${operation.getOperationId.capitalize}Response" + val callBuilderName = s"${operation.getOperationId.capitalize}CallBuilder" for { responseParentType <- safeParseClassOrInterfaceType(responseParentName) + callBuilderType <- safeParseClassOrInterfaceType(callBuilderName) pathExpr <- jpaths.generateUrlPathParams(pathStr, parameters.pathParams) } yield { val method = new MethodDeclaration(util.EnumSet.of(PUBLIC), new VoidType, methodName) - method.setType(completionStageType.setTypeArguments(responseParentType)) + .setType(callBuilderType) parameters.parameters.foreach(p => p.param.setType(p.param.getType.unbox)) - val pathParams = parameters.pathParams.map(_.param) - val qsParams = parameters.queryStringParams.map(_.param) - val formParams = parameters.formParams.map(_.param) - val headerParams = parameters.headerParams.map(_.param) - val bodyParams = parameters.bodyParams.map(_.param).toList - (pathParams ++ qsParams ++ formParams ++ headerParams ++ bodyParams).foreach(method.addParameter) + ( + parameters.pathParams ++ + parameters.queryStringParams ++ + parameters.formParams ++ + parameters.headerParams ++ + parameters.bodyParams + ).filter(_.required).map(_.param).foreach(method.addParameter) val requestBuilder = new MethodCallExpr( new AssignExpr(new VariableDeclarationExpr( @@ -352,12 +332,73 @@ object AsyncHttpClientClientGenerator { "setUrl", new NodeList[Expression](pathExpr) ) - val builderMethodCalls: List[Statement] = List( - generateBuilderMethodCalls(parameters.queryStringParams, "addQueryParam"), - generateBuilderMethodCalls(parameters.formParams, "addFormParam"), - generateBuilderMethodCalls(parameters.headerParams, "addHeader"), - generateBodyMethodCall(parameters.bodyParams).toList - ).flatten + val builderParamsMethodNames = List( + (parameters.queryStringParams, "addQueryParam", false), + (parameters.formParams, "addFormParam", parameters.formParams.exists(_.isFile)), + (parameters.headerParams, "addHeader", false) + ) + + val builderMethodCalls: List[(ScalaParameter[JavaLanguage], Statement)] = builderParamsMethodNames + .flatMap({ case (params, name, needsMultipart) => + params.map(param => (param, generateBuilderMethodCall(param, name, needsMultipart))) + }) ++ + parameters.bodyParams.map(param => (param, generateBodyMethodCall(param))) + + val callBuilderCreation = new ObjectCreationExpr(null, callBuilderType, new NodeList( + new NameExpr("builder"), + new FieldAccessExpr(new ThisExpr, "httpClient"), + new FieldAccessExpr(new ThisExpr, "objectMapper") + )) + + method.setBody(new BlockStmt(new NodeList( + new ExpressionStmt(requestBuilder) +: + builderMethodCalls.filter(_._1.required).map(_._2) :+ + new ReturnStmt(callBuilderCreation): _* + ))) + + val callBuilderFinalFields = List( + (REQUEST_BUILDER_TYPE, "builder"), + (HTTP_CLIENT_FUNCTION_TYPE, "httpClient"), + (OBJECT_MAPPER_TYPE, "objectMapper") + ) + + val callBuilderCls = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, STATIC), false, callBuilderName) + callBuilderFinalFields.foreach({ case (tpe, name) => callBuilderCls.addField(tpe, name, FINAL) }) + + callBuilderCls.addConstructor(PRIVATE) + .setParameters(callBuilderFinalFields.map({ case (tpe, name) => new Parameter(util.EnumSet.of(FINAL), tpe, new SimpleName(name)) }).toNodeList) + .setBody(new BlockStmt( + callBuilderFinalFields.map[Statement, List[Statement]]({ case (_, name) => + new ExpressionStmt(new AssignExpr(new FieldAccessExpr(new ThisExpr, name), new NameExpr(name), AssignExpr.Operator.ASSIGN)) + }).toNodeList + )) + + val optionalParamMethods = builderMethodCalls + .filterNot(_._1.required) + .map({ case (ScalaParameter(_, param, _, _, argType), methodCall) => + new MethodDeclaration(util.EnumSet.of(PUBLIC), s"with${param.getNameAsString.unescapeReservedWord.capitalize}", callBuilderType, List( + new Parameter(util.EnumSet.of(FINAL), argType.containedType.unbox, new SimpleName(param.getNameAsString)) + ).toNodeList).setBody(new BlockStmt(List( + methodCall, + new ReturnStmt(new ThisExpr) + ).toNodeList)) + }) + optionalParamMethods.foreach(callBuilderCls.addMember) + + callBuilderCls.addMethod("withHeader", PUBLIC) + .setParameters(List( + new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("name")), + new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("value")), + ).toNodeList) + .setType(callBuilderType) + .setBody(new BlockStmt(List( + new ExpressionStmt(new MethodCallExpr( + new FieldAccessExpr(new ThisExpr, "builder"), + "addHeader", + List[Expression](new NameExpr("name"), new NameExpr("value")).toNodeList + )), + new ReturnStmt(new ThisExpr) + ).toNodeList)) val httpMethodCallExpr = new MethodCallExpr( new FieldAccessExpr(new ThisExpr, "httpClient"), @@ -408,13 +449,11 @@ object AsyncHttpClientClientGenerator { )), true) )) - method.setBody(new BlockStmt(new NodeList( - new ExpressionStmt(requestBuilder) +: - builderMethodCalls :+ - new ReturnStmt(requestCall): _* - ))) + callBuilderCls.addMethod("call", PUBLIC) + .setType(completionStageType.setTypeArguments(responseParentType)) + .setBody(new BlockStmt(List[Statement](new ReturnStmt(requestCall)).toNodeList)) - RenderedClientOperation[JavaLanguage](method, List.empty) + RenderedClientOperation[JavaLanguage](method, callBuilderCls :: Nil) } case GetImports(tracing) => @@ -723,6 +762,7 @@ object AsyncHttpClientClientGenerator { clientClass.addMember(builderClass) clientCalls.foreach(clientClass.addMember) + supportDefinitions.foreach(clientClass.addMember) Target.pure(NonEmptyList(Right(clientClass), Nil)) } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index 8c1542344e..abe7f1f2f7 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -4,7 +4,7 @@ import com.github.javaparser.JavaParser import com.github.javaparser.ast.`type`.{ClassOrInterfaceType, Type} import com.github.javaparser.ast.body.{ClassOrInterfaceDeclaration, Parameter} import com.github.javaparser.ast.expr.{Expression, Name, SimpleName} -import com.github.javaparser.ast.{CompilationUnit, ImportDeclaration} +import com.github.javaparser.ast.{CompilationUnit, ImportDeclaration, Node, NodeList} import com.github.javaparser.printer.PrettyPrinterConfiguration import com.github.javaparser.printer.PrettyPrinterConfiguration.IndentType import com.twilio.guardrail.Target @@ -55,6 +55,10 @@ object Java { } } + implicit class RichListOfNode[T <: Node](val l: List[T]) extends AnyVal { + def toNodeList: NodeList[T] = new NodeList[T](l: _*) + } + private[this] def safeParse[T](log: String)(parser: String => T, s: String)(implicit cls: ClassTag[T]): Target[T] = { Target.log.function(s"${log}: ${s}") { Try(parser(s)).toEither.fold(t => Target.raiseError(s"Unable to parse '${s}' to a ${cls.runtimeClass.getName}: ${t.getMessage}"), Target.pure) @@ -102,6 +106,16 @@ object Java { implicit class RichJavaString(val s: String) extends AnyVal { def escapeReservedWord: String = if (reservedWords.contains(s)) s + "_" else s + def unescapeReservedWord: String = if (s.endsWith("_")) { + val prefix = s.substring(0, s.length - 1) + if (reservedWords.contains(prefix)) { + prefix + } else { + s + } + } else { + s + } } lazy val SHOWER_CLASS_DEF: ClassOrInterfaceDeclaration = { diff --git a/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardRoundTripTest.scala b/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardRoundTripTest.scala index 363d14dfdd..82eec4a950 100644 --- a/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardRoundTripTest.scala +++ b/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardRoundTripTest.scala @@ -72,7 +72,7 @@ class DropwizardRoundTripTest extends FreeSpec with Matchers with Waiters with M .build() val w = new Waiter - client.getUserByName(USERNAME).whenComplete({ (response, t) => + client.getUserByName(USERNAME).call().whenComplete({ (response, t) => w { t shouldBe null } response match { case r: UserClient.GetUserByNameResponse.Ok => From 86874d6909ecdf752a6887398e8942196f45fa5a Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Tue, 12 Mar 2019 18:15:06 -0700 Subject: [PATCH 59/86] Simplify Java cmdline import parser --- .../codegen/src/main/scala/com/twilio/guardrail/CLI.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala index 72ed4a0a70..708439d47f 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala @@ -167,12 +167,8 @@ object CLI extends CLICommon { case "dropwizard" => Java.Dropwizard }, { str => import com.github.javaparser.JavaParser - import scala.collection.JavaConverters._ import scala.util.Try - (for { - imports <- Try(JavaParser.parse(s"import ${str};")).toOption.toRight(s"Unable to parse ${str} as an import") - stat <- imports.getImports().asScala.headOption.toRight(s"${str} was not an import") - } yield stat).leftMap(UnparseableArgument("import", _)) + Try(JavaParser.parseImport(s"import ${str};")).toEither.leftMap(t => UnparseableArgument("import", t.getMessage)) } ) } From 1a3ee62098e71ec985dbabfbe72385a71021a8a3 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Wed, 13 Mar 2019 11:32:31 -0700 Subject: [PATCH 60/86] map+sequence -> traverse where possible --- .../generators/Java/AsyncHttpClientClientGenerator.scala | 6 +++--- .../generators/Java/DropwizardServerGenerator.scala | 2 +- .../twilio/guardrail/generators/Java/JacksonGenerator.scala | 4 ++-- .../com/twilio/guardrail/generators/JavaGenerator.scala | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index 29c4c8a42b..51554a1497 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -152,7 +152,7 @@ object AsyncHttpClientClientGenerator { for { httpErrorImports <- List( "org.asynchttpclient.Response" - ).map(safeParseRawImport).sequence + ).traverse(safeParseRawImport) } yield { def addStdConstructors(cls: ClassOrInterfaceDeclaration): Unit = { cls.addConstructor(PUBLIC) @@ -226,7 +226,7 @@ object AsyncHttpClientClientGenerator { "org.asynchttpclient.DefaultAsyncHttpClientConfig", "org.asynchttpclient.Request", "org.asynchttpclient.Response" - ).map(safeParseRawImport).sequence + ).traverse(safeParseRawImport) } yield { val cls = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, ABSTRACT), false, "AsyncHttpClientSupport") cls.addConstructor(PRIVATE) @@ -276,7 +276,7 @@ object AsyncHttpClientClientGenerator { "com.fasterxml.jackson.databind.ObjectMapper", "com.fasterxml.jackson.datatype.jdk8.Jdk8Module", "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule" - ).map(safeParseRawImport).sequence + ).traverse(safeParseRawImport) } yield { val cls = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, ABSTRACT), false, "JacksonSupport") cls.addConstructor(PRIVATE) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala index ba7be1e229..db7b23835f 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala @@ -74,7 +74,7 @@ object DropwizardServerGenerator { "java.util.concurrent.CompletionStage", "org.slf4j.Logger", "org.slf4j.LoggerFactory" - ).map(safeParseRawImport).sequence + ).traverse(safeParseRawImport) case BuildTracingFields(operation, resourceName, tracing) => if (tracing) { diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala index a44abfa8cf..a77d24c1e8 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -193,7 +193,7 @@ object JacksonGenerator { extraImports <- List( "com.fasterxml.jackson.annotation.JsonCreator", "com.fasterxml.jackson.annotation.JsonValue" - ).map(safeParseRawImport).sequence + ).traverse(safeParseRawImport) } yield StaticDefns[JavaLanguage]( className = clsName, extraImports = extraImports, @@ -591,7 +591,7 @@ object JacksonGenerator { "com.fasterxml.jackson.annotation.JsonIgnoreProperties", "com.fasterxml.jackson.annotation.JsonSubTypes", "com.fasterxml.jackson.annotation.JsonTypeInfo" - ).map(safeParseRawImport).sequence + ).traverse(safeParseRawImport) } yield StaticDefns[JavaLanguage]( clsName, extraImports, diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala index 4e0a7c91b3..d01dca3d9f 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala @@ -77,7 +77,7 @@ object JavaGenerator { None } case ParseTypeName(tpe) => - Option(tpe).map(_.trim).filterNot(_.isEmpty).map(safeParseName).sequence + Option(tpe).map(_.trim).filterNot(_.isEmpty).traverse(safeParseName) case PureTermName(tpe) => Option(tpe).map(_.trim).filterNot(_.isEmpty).map(_.escapeReservedWord).map(safeParseName).getOrElse(Target.raiseError("A structure's name is empty")) @@ -288,7 +288,7 @@ object JavaGenerator { allExtraImports = extraImports ++ List(commonImport, dtoComponentsImport) handlerTree <- writeDefinition(pkgDecl, allExtraImports, handlerDefinition) - serverTrees <- serverDefinitions.map(writeDefinition(pkgDecl, allExtraImports, _)).sequence + serverTrees <- serverDefinitions.traverse(writeDefinition(pkgDecl, allExtraImports, _)) } yield handlerTree +: serverTrees } } From f3bde925d17742545d0696738dff8bab99d8dc8a Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Thu, 14 Mar 2019 13:34:46 -0700 Subject: [PATCH 61/86] Add UrlencodedFormData to RouteMeta.ContentType --- .../com/twilio/guardrail/terms/SwaggerTerm.scala | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/SwaggerTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/SwaggerTerm.scala index 3d685e87ec..365f85e3f0 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/SwaggerTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/SwaggerTerm.scala @@ -20,15 +20,17 @@ import io.swagger.v3.oas.models.responses.ApiResponse object RouteMeta { sealed abstract class ContentType(value: String) - case object ApplicationJson extends ContentType("application/json") - case object MultipartFormData extends ContentType("multipart/form-data") - case object TextPlain extends ContentType("text/plain") + case object ApplicationJson extends ContentType("application/json") + case object MultipartFormData extends ContentType("multipart/form-data") + case object UrlencodedFormData extends ContentType("application/x-www-form-urlencoded") + case object TextPlain extends ContentType("text/plain") object ContentType { def unapply(value: String): Option[ContentType] = value match { - case "application/json" => Some(ApplicationJson) - case "multipart/form-data" => Some(MultipartFormData) - case "text/plain" => Some(TextPlain) - case _ => None + case "application/json" => Some(ApplicationJson) + case "multipart/form-data" => Some(MultipartFormData) + case "application/x-www-form-urlencoded" => Some(UrlencodedFormData) + case "text/plain" => Some(TextPlain) + case _ => None } } } From 314665342bccd27490409144e896e8100fdd0c8f Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Thu, 14 Mar 2019 13:34:57 -0700 Subject: [PATCH 62/86] Better consumes/produces handling for Dropwizard Picks the 'best' content-type to use, and also handles the difference between urlencoded and form-data correctly. Should probably handle all possible combinations of consumes+produces and emit multiple resource methods, but this will do for now. --- build.sbt | 12 +- .../Java/DropwizardServerGenerator.scala | 103 ++++++++++++++++-- 2 files changed, 99 insertions(+), 16 deletions(-) diff --git a/build.sbt b/build.sbt index 362782d588..6256c8cbd5 100644 --- a/build.sbt +++ b/build.sbt @@ -21,6 +21,7 @@ val javaparserVersion = "3.7.1" val endpointsVersion = "0.8.0" val ahcVersion = "2.8.1" val dropwizardVersion = "1.3.9" +val jerseyVersion = "2.25.1" mainClass in assembly := Some("com.twilio.guardrail.CLI") @@ -284,11 +285,12 @@ lazy val dropwizardSample = (project in file("modules/sample-dropwizard")) .settings( codegenSettings, libraryDependencies ++= Seq( - "io.dropwizard" % "dropwizard-core" % dropwizardVersion, - "org.asynchttpclient" % "async-http-client" % ahcVersion, - "org.scala-lang.modules" %% "scala-java8-compat" % "0.9.0" % Test, - "org.scalatest" %% "scalatest" % scalatestVersion % Test, - "org.mockito" %% "mockito-scala" % "1.2.0" % Test, + "io.dropwizard" % "dropwizard-core" % dropwizardVersion, + "org.glassfish.jersey.media" % "jersey-media-multipart" % jerseyVersion, + "org.asynchttpclient" % "async-http-client" % ahcVersion, + "org.scala-lang.modules" %% "scala-java8-compat" % "0.9.0" % Test, + "org.scalatest" %% "scalatest" % scalatestVersion % Test, + "org.mockito" %% "mockito-scala" % "1.2.0" % Test, ), skip in publish := true, scalafmtOnCompile := false diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala index db7b23835f..54bc1dcb9b 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala @@ -12,16 +12,29 @@ import com.github.javaparser.ast.`type`.{PrimitiveType, Type, VoidType} import com.github.javaparser.ast.body._ import com.github.javaparser.ast.expr._ import com.github.javaparser.ast.stmt._ -import com.twilio.guardrail.generators.Response -import com.twilio.guardrail.{RenderedRoutes, SupportDefinition, Target} +import com.twilio.guardrail.generators.{Response, ScalaParameter, ScalaParameters} +import com.twilio.guardrail.{ADT, ClassDefinition, EnumDefinition, RandomType, RenderedRoutes, StrictProtocolElems, SupportDefinition, Target} import com.twilio.guardrail.generators.syntax.Java._ import com.twilio.guardrail.languages.JavaLanguage import com.twilio.guardrail.protocol.terms.server._ +import com.twilio.guardrail.shims.OperationExt import com.twilio.guardrail.terms.RouteMeta import io.swagger.v3.oas.models.PathItem.HttpMethod +import io.swagger.v3.oas.models.responses.ApiResponse import java.util +import scala.collection.JavaConverters._ +import scala.util.Try object DropwizardServerGenerator { + private implicit class ContentTypeExt(val ct: RouteMeta.ContentType) extends AnyVal { + def toJaxRsAnnotationName: String = ct match { + case RouteMeta.ApplicationJson => "APPLICATION_JSON" + case RouteMeta.UrlencodedFormData => "APPLICATION_FORM_URLENCODED" + case RouteMeta.MultipartFormData => "MULTIPART_FORM_DATA" + case RouteMeta.TextPlain => "TEXT_PLAIN" + } + } + private val ASYNC_RESPONSE_TYPE = JavaParser.parseClassOrInterfaceType("AsyncResponse") private val RESPONSE_BUILDER_TYPE = JavaParser.parseClassOrInterfaceType("Response.ResponseBuilder") private val LOGGER_TYPE = JavaParser.parseClassOrInterfaceType("Logger") @@ -49,6 +62,57 @@ object DropwizardServerGenerator { checkMatch(List.empty, initialHeads, initialRest) } + // FIXME: does this handle includes from other files? + private def definitionName(refName: Option[String]): Option[String] = { + refName.flatMap({ rn => + rn.split("/").toList match { + case "#" :: _ :: name :: Nil => Some(name) + case _ => None + } + }) + } + + def getBestConsumes(contentTypes: List[RouteMeta.ContentType], + parameters: ScalaParameters[JavaLanguage]): Option[RouteMeta.ContentType] = + { + val priorityOrder = NonEmptyList.of( + RouteMeta.UrlencodedFormData, + RouteMeta.ApplicationJson, + RouteMeta.MultipartFormData, + RouteMeta.TextPlain + ) + + priorityOrder.foldLeft[Option[RouteMeta.ContentType]](None)({ + case (s @ Some(_), _) => s + case (None, next) => contentTypes.find(_ == next) + }) + .orElse(parameters.formParams.headOption.map(_ => RouteMeta.UrlencodedFormData)) + .orElse(parameters.bodyParams.map(_ => RouteMeta.ApplicationJson)) + } + + private def getBestProduces(contentTypes: List[RouteMeta.ContentType], + responses: List[ApiResponse], + protocolElems: List[StrictProtocolElems[JavaLanguage]]): Option[RouteMeta.ContentType] = + { + val priorityOrder = NonEmptyList.of( + RouteMeta.ApplicationJson, + RouteMeta.TextPlain + ) + + priorityOrder.foldLeft[Option[RouteMeta.ContentType]](None)({ + case (s @ Some(_), _) => s + case (None, next) => contentTypes.find(_ == next) + }) + .orElse(responses.map({ resp => + protocolElems.find(pe => definitionName(Option(resp.get$ref())).contains(pe.name)).flatMap({ + case _: ClassDefinition[_] => Some(RouteMeta.ApplicationJson) + case RandomType(_, tpe) if tpe.isPrimitiveType || tpe.isNamed("String") => Some(RouteMeta.TextPlain) + case _: ADT[_] | _: EnumDefinition[_] => Some(RouteMeta.TextPlain) + case _ => None + }) + }).headOption.flatten) + } + object ServerTermInterp extends (ServerTerm[JavaLanguage, ?] ~> Target) { def apply[T](term: ServerTerm[JavaLanguage, T]): Target[T] = term match { case GetExtraImports(tracing) => @@ -72,6 +136,7 @@ object DropwizardServerGenerator { "javax.ws.rs.core.MediaType", "javax.ws.rs.core.Response", "java.util.concurrent.CompletionStage", + "org.glassfish.jersey.media.multipart.FormDataParam", "org.slf4j.Logger", "org.slf4j.LoggerFactory" ).traverse(safeParseRawImport) @@ -110,14 +175,30 @@ object DropwizardServerGenerator { val pathSuffix = splitPathComponents(path).drop(commonPathPrefix.length).mkString("/", "/", "") method.addAnnotation(new SingleMemberAnnotationExpr(new Name("Path"), new StringLiteralExpr(pathSuffix))) - parameters.formParams.headOption.map(_ => "APPLICATION_FORM_URLENCODED") - .orElse(parameters.bodyParams.map(_ => "APPLICATION_JSON")) - .foreach(produces => - method.addAnnotation(new SingleMemberAnnotationExpr(new Name("Consumes"), new FieldAccessExpr(new NameExpr("MediaType"), produces))) - ) - if (responses.value.exists(_.value.isDefined)) { - method.addAnnotation(new SingleMemberAnnotationExpr(new Name("Produces"), new FieldAccessExpr(new NameExpr("MediaType"), "APPLICATION_JSON"))) - } + + val consumes = getBestConsumes(operation.consumes.flatMap(RouteMeta.ContentType.unapply).toList, parameters) + .orElse({ + if (parameters.formParams.nonEmpty) { + if (parameters.formParams.exists(_.isFile)) { + Some(RouteMeta.MultipartFormData) + } else { + Some(RouteMeta.UrlencodedFormData) + } + } else if (parameters.bodyParams.nonEmpty) { + Some(RouteMeta.ApplicationJson) + } else { + None + } + }) + consumes + .map(c => new SingleMemberAnnotationExpr(new Name("Consumes"), new FieldAccessExpr(new NameExpr("MediaType"), c.toJaxRsAnnotationName))) + .foreach(method.addAnnotation) + + val successResponses = operation.getResponses.entrySet.asScala.filter(entry => Try(entry.getKey.toInt / 100 == 2).getOrElse(false)).map(_.getValue).toList + val produces = getBestProduces(operation.produces.flatMap(RouteMeta.ContentType.unapply).toList, successResponses, protocolElems) + produces + .map(p => new SingleMemberAnnotationExpr(new Name("Produces"), new FieldAccessExpr(new NameExpr("MediaType"), p.toJaxRsAnnotationName))) + .foreach(method.addAnnotation) def addParamAnnotation(template: Parameter, annotationName: String, paramName: Name): Parameter = { val parameter = template.clone() @@ -129,7 +210,7 @@ object DropwizardServerGenerator { (parameters.pathParams, "PathParam"), (parameters.headerParams, "HeaderParam"), (parameters.queryStringParams, "QueryParam"), - (parameters.formParams, "FormParam") + (parameters.formParams, if (parameters.formParams.exists(_.isFile)) "FormDataParam" else "FormParam") ).flatMap({ case (params, annotationName) => params.map(param => addParamAnnotation(param.param, annotationName, param.paramName)) }) ++ parameters.bodyParams.map(_.param) From ad0faa54f3adfbfd03294b0db81e7b7f8f685e3c Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Thu, 14 Mar 2019 15:14:38 -0700 Subject: [PATCH 63/86] Move toDashedCase to common code --- .../generators/AkkaHttpClientGenerator.scala | 12 +++--------- .../generators/EndpointsClientGenerator.scala | 12 +++--------- .../guardrail/generators/Http4sClientGenerator.scala | 12 +++--------- .../twilio/guardrail/generators/syntax/package.scala | 7 +++++++ 4 files changed, 16 insertions(+), 27 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpClientGenerator.scala index e45826455e..a7a5a2ce89 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpClientGenerator.scala @@ -8,6 +8,7 @@ import cats.arrow.FunctionK import cats.data.NonEmptyList import cats.syntax.flatMap._ import com.twilio.guardrail.generators.syntax.Scala._ +import com.twilio.guardrail.generators.syntax._ import com.twilio.guardrail.protocol.terms.client._ import com.twilio.guardrail.shims._ import com.twilio.guardrail.terms.RouteMeta @@ -26,17 +27,10 @@ object AkkaHttpClientGenerator { (parts.drop(1).toList, parts.last) } - private[this] def toDashedCase(s: String): String = { - val lowercased = - "^([A-Z])".r.replaceAllIn(s, m => m.group(1).toLowerCase(Locale.US)) - "([A-Z])".r - .replaceAllIn(lowercased, m => '-' +: m.group(1).toLowerCase(Locale.US)) - } - private[this] def formatClientName(clientName: Option[String]): Term.Param = clientName.fold( param"clientName: String" - )(name => param"clientName: String = ${Lit.String(toDashedCase(name))}") + )(name => param"clientName: String = ${Lit.String(name.toDashedCase)}") private[this] def formatHost(serverUrls: Option[NonEmptyList[URI]]): Term.Param = serverUrls @@ -286,7 +280,7 @@ object AkkaHttpClientGenerator { List(ScalaParameter.fromParam(param"traceBuilder: TraceBuilder")) else List.empty tracingArgsPost = if (tracing) - List(ScalaParameter.fromParam(param"methodName: String = ${Lit.String(toDashedCase(methodName))}")) + List(ScalaParameter.fromParam(param"methodName: String = ${Lit.String(methodName.toDashedCase)}")) else List.empty extraImplicits = List.empty renderedClientOperation = build(methodName, httpMethod, urlWithParams, formDataParams, headerParams, responses, produces, consumes, tracing)( diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsClientGenerator.scala index d3b58d2834..ae420435e7 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsClientGenerator.scala @@ -8,6 +8,7 @@ import cats.arrow.FunctionK import cats.data.{ Ior, NonEmptyList } import cats.implicits._ import com.twilio.guardrail.generators.syntax.Scala._ +import com.twilio.guardrail.generators.syntax._ import com.twilio.guardrail.protocol.terms.client._ import com.twilio.guardrail.terms.RouteMeta import com.twilio.guardrail.languages.ScalaLanguage @@ -20,17 +21,10 @@ import scala.meta._ object EndpointsClientGenerator { object ClientTermInterp extends FunctionK[ClientTerm[ScalaLanguage, ?], Target] { - private[this] def toDashedCase(s: String): String = { - val lowercased = - "^([A-Z])".r.replaceAllIn(s, m => m.group(1).toLowerCase(Locale.US)) - "([A-Z])".r - .replaceAllIn(lowercased, m => '-' +: m.group(1).toLowerCase(Locale.US)) - } - private[this] def formatClientName(clientName: Option[String]): Term.Param = clientName.fold( param"clientName: String" - )(name => param"clientName: String = ${Lit.String(toDashedCase(name))}") + )(name => param"clientName: String = ${Lit.String(name.toDashedCase)}") private[this] def formatHost(serverUrls: Option[NonEmptyList[URI]]): Term.Param = serverUrls @@ -370,7 +364,7 @@ object EndpointsClientGenerator { List(ScalaParameter.fromParam(param"traceBuilder: TraceBuilder")) else List.empty tracingArgsPost = if (tracing) - List(ScalaParameter.fromParam(param"methodName: String = ${Lit.String(toDashedCase(methodName))}")) + List(ScalaParameter.fromParam(param"methodName: String = ${Lit.String(methodName.toDashedCase)}")) else List.empty extraImplicits = List.empty renderedClientOperation = build(methodName, diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sClientGenerator.scala index a777a4c743..3da72bdab5 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sClientGenerator.scala @@ -10,6 +10,7 @@ import cats.syntax.functor._ import cats.syntax.traverse._ import com.twilio.guardrail.extract.ScalaPackage import com.twilio.guardrail.generators.syntax.Scala._ +import com.twilio.guardrail.generators.syntax._ import com.twilio.guardrail.languages.ScalaLanguage import com.twilio.guardrail.protocol.terms.client._ import com.twilio.guardrail.shims._ @@ -28,17 +29,10 @@ object Http4sClientGenerator { (parts.drop(1).toList, parts.last) } - private[this] def toDashedCase(s: String): String = { - val lowercased = - "^([A-Z])".r.replaceAllIn(s, m => m.group(1).toLowerCase(Locale.US)) - "([A-Z])".r - .replaceAllIn(lowercased, m => '-' +: m.group(1).toLowerCase(Locale.US)) - } - private[this] def formatClientName(clientName: Option[String]): Term.Param = clientName.fold( param"clientName: String" - )(name => param"clientName: String = ${Lit.String(toDashedCase(name))}") + )(name => param"clientName: String = ${Lit.String(name.toDashedCase)}") private[this] def formatHost(serverUrls: Option[NonEmptyList[URI]]): Term.Param = serverUrls @@ -281,7 +275,7 @@ object Http4sClientGenerator { List(ScalaParameter.fromParam(param"traceBuilder: TraceBuilder[F]")) else List.empty tracingArgsPost = if (tracing) - List(ScalaParameter.fromParam(param"methodName: String = ${Lit.String(toDashedCase(methodName))}")) + List(ScalaParameter.fromParam(param"methodName: String = ${Lit.String(methodName.toDashedCase)}")) else List.empty extraImplicits = List.empty diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/package.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/package.scala index deae48013f..69430995ad 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/package.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/package.scala @@ -27,5 +27,12 @@ package object syntax { val fromCamel = "[A-Z]".r.replaceAllIn(noPascal, "_" + _.group(0)) fromCamel.replaceAllLiterally("-", "_") } + + def toDashedCase: String = { + val lowercased = + "^([A-Z])".r.replaceAllIn(s, m => m.group(1).toLowerCase(Locale.US)) + "([A-Z])".r + .replaceAllIn(lowercased, m => '-' +: m.group(1).toLowerCase(Locale.US)) + } } } From 5132747ece8b2d131a750e0a130f4c6fa55d369b Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Thu, 14 Mar 2019 17:21:11 -0700 Subject: [PATCH 64/86] Disable --debug when running Java example --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 6256c8cbd5..d100138990 100644 --- a/build.sbt +++ b/build.sbt @@ -48,7 +48,7 @@ val exampleCases: List[(java.io.File, String, Boolean, List[String])] = List( ) val exampleJavaArgs: List[List[String]] = exampleCases - .foldLeft(List[List[String]](List("java", "--debug")))({ + .foldLeft(List[List[String]](List("java")))({ case (acc, (path, prefix, tracing, extra)) => acc ++ (for { kind <- List("client", "server") From 4646d3d6620d4839c52b6a87699351372c5f86d4 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Thu, 14 Mar 2019 17:24:30 -0700 Subject: [PATCH 65/86] Remove stub Java akka-http generator --- .../main/scala/com/twilio/guardrail/CLI.scala | 3 +- .../guardrail/generators/Java/AkkaHttp.scala | 43 ------------------- 2 files changed, 1 insertion(+), 45 deletions(-) delete mode 100644 modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala index 708439d47f..19f3090438 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala @@ -162,8 +162,7 @@ object CLI extends CLICommon { ) val javaInterpreter = CoreTermInterp[JavaLanguage]( - "akka-http", { - case "akka-http" => Java.AkkaHttp + "dropwizard", { case "dropwizard" => Java.Dropwizard }, { str => import com.github.javaparser.JavaParser diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala deleted file mode 100644 index 27f859bac1..0000000000 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AkkaHttp.scala +++ /dev/null @@ -1,43 +0,0 @@ -package com.twilio.guardrail -package generators -package Java - -import com.twilio.guardrail.languages.JavaLanguage -import com.twilio.guardrail.protocol.terms.client.ClientTerm -import com.twilio.guardrail.protocol.terms.server.ServerTerm -import com.twilio.guardrail.terms.framework.FrameworkTerm -import cats.~> - -import JacksonGenerator._ -import JavaGenerator.JavaInterp - -object AkkaHttp extends (CodegenApplication[JavaLanguage, ?] ~> Target) { - object ClientTermInterp extends (ClientTerm[JavaLanguage, ?] ~> Target) { - def apply[T](term: ClientTerm[JavaLanguage, T]): Target[T] = Target.raiseError(s"interpFramework: ${term.toString()}") - } - object FrameworkInterp extends (FrameworkTerm[JavaLanguage, ?] ~> Target) { - def apply[T](term: FrameworkTerm[JavaLanguage, T]): Target[T] = Target.raiseError(s"interpFramework: ${term.toString()}") - } - object ServerTermInterp extends (ServerTerm[JavaLanguage, ?] ~> Target) { - def apply[T](term: ServerTerm[JavaLanguage, T]): Target[T] = Target.raiseError(s"interpFramework: ${term.toString()}") - } - - val interpDefinitionPM: DefinitionPM[JavaLanguage, ?] ~> Target = ProtocolSupportTermInterp or ModelProtocolTermInterp - val interpDefinitionPME: DefinitionPME[JavaLanguage, ?] ~> Target = EnumProtocolTermInterp or interpDefinitionPM - val interpDefinitionPMEA: DefinitionPMEA[JavaLanguage, ?] ~> Target = ArrayProtocolTermInterp or interpDefinitionPME - val interpDefinitionPMEAP: DefinitionPMEAP[JavaLanguage, ?] ~> Target = PolyProtocolTermInterp or interpDefinitionPMEA - - val interpModel: ModelInterpreters[JavaLanguage, ?] ~> Target = interpDefinitionPMEAP - - val interpFrameworkC: FrameworkC[JavaLanguage, ?] ~> Target = ClientTermInterp or interpModel - val interpFrameworkCS: FrameworkCS[JavaLanguage, ?] ~> Target = ServerTermInterp or interpFrameworkC - val interpFrameworkCSF: FrameworkCSF[JavaLanguage, ?] ~> Target = FrameworkInterp or interpFrameworkCS - - val interpFramework: ClientServerTerms[JavaLanguage, ?] ~> Target = interpFrameworkCSF - - val parser: Parser[JavaLanguage, ?] ~> Target = SwaggerGenerator[JavaLanguage] or interpFramework - - val codegenApplication: CodegenApplication[JavaLanguage, ?] ~> Target = JavaInterp or parser - - def apply[T](x: CodegenApplication[JavaLanguage, T]): Target[T] = codegenApplication.apply(x) -} From 66baec403b35de200740824594446c613bc98b74 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Thu, 14 Mar 2019 17:30:04 -0700 Subject: [PATCH 66/86] Add ClientException as thrown type for AsyncHttpClient call methods --- .../generators/Java/AsyncHttpClientClientGenerator.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index 51554a1497..dce472d102 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -37,8 +37,8 @@ object AsyncHttpClientClientGenerator { private val MARSHALLING_EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("MarshallingException") private val HTTP_ERROR_TYPE = JavaParser.parseClassOrInterfaceType("HttpError") private val EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("Exception") - private val ILLEGAL_ARGUMENT_EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("IllegalArgumentException") private val JSON_PROCESSING_EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("JsonProcessingException") + private val CLIENT_EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("ClientException") private val HTTP_CLIENT_FUNCTION_TYPE = functionType.setTypeArguments(new NodeList[Type]( REQUEST_TYPE, @@ -451,6 +451,7 @@ object AsyncHttpClientClientGenerator { callBuilderCls.addMethod("call", PUBLIC) .setType(completionStageType.setTypeArguments(responseParentType)) + .addThrownException(CLIENT_EXCEPTION_TYPE) .setBody(new BlockStmt(List[Statement](new ReturnStmt(requestCall)).toNodeList)) RenderedClientOperation[JavaLanguage](method, callBuilderCls :: Nil) From 56fcc162b0c31dca77275bf897f2e78c52efb2e2 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Thu, 14 Mar 2019 17:50:35 -0700 Subject: [PATCH 67/86] Simplify default value transformation in Jackson generator --- .../guardrail/generators/Java/JacksonGenerator.scala | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala index a77d24c1e8..b4af3155ed 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -269,7 +269,17 @@ object JacksonGenerator { _declDefaultPair <- Option(isRequired) .filterNot(_ == false) .fold[Target[(Type, Option[Expression])]]( - (safeParseType(s"java.util.Optional<${tpe}>"), Option(defaultValue.fold[Target[Expression]](safeParseExpression[Expression](s"java.util.Optional.empty()"))(t => safeParseExpression[Expression](s"java.util.Optional.of($t)"))).sequence).mapN((_, _)) + ( + safeParseType(s"java.util.Optional<${tpe}>"), + Target.pure(Option( + defaultValue + .fold( + new MethodCallExpr(new NameExpr(s"java.util.Optional"), "empty") + )(t => + new MethodCallExpr("java.util.Optional.of", t) + ) + )) + ).mapN((_, _)) )(Function.const(Target.pure((tpe, defaultValue))) _) (finalDeclType, finalDefaultValue) = _declDefaultPair term <- safeParseParameter(s"final ${finalDeclType} ${argName.escapeReservedWord}") From 6b70dcc40157eea759b9256a9cc3c24c8261e085 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Thu, 14 Mar 2019 17:51:57 -0700 Subject: [PATCH 68/86] Remove commented out code in Java syntax helpers --- .../twilio/guardrail/generators/syntax/Java.scala | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index abe7f1f2f7..e7bc970d55 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -126,19 +126,4 @@ object Java { throw new AssertionError("Shower.java in class resources is not valid") ) } - -/* - implicit class PrintStructure(value: Node) { - def toAST: String = { - @scala.annotation.tailrec - def walk(chunks: List[(String, Int, List[Node], String)]) = { - chunks.flatMap { case (pre, level, nodes, post) => - - } - } - - walk(List(("", 0, List(value), ""))) - } - } -*/ } From a076ed6a49dc2e5886c5c7ac1e9fa918c016ff87 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Thu, 14 Mar 2019 17:53:39 -0700 Subject: [PATCH 69/86] Remove unneeded FIXME comment --- .../scala/com/twilio/guardrail/generators/JavaGenerator.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala index d01dca3d9f..866faa7513 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala @@ -86,7 +86,6 @@ object JavaGenerator { Option(tpe).map(_.trim).filterNot(_.isEmpty).map(safeParseName).getOrElse(Target.raiseError("A structure's name is empty")) case PureMethodParameter(nameStr, tpe, default) => - // FIXME: java methods do not support default param values -- what should we do here? safeParseSimpleName(nameStr.asString.escapeReservedWord).map(name => new Parameter(util.EnumSet.of(Modifier.FINAL), tpe, name)) case TypeNamesEqual(a, b) => From cb17aee2da9dfdf7042485e44d3f89ecaaab49d7 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Thu, 14 Mar 2019 17:59:51 -0700 Subject: [PATCH 70/86] Simplify map+sequence to traverse in Jackson generator --- .../generators/Java/JacksonGenerator.scala | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala index b4af3155ed..5f7c1e7108 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -220,26 +220,26 @@ object JacksonGenerator { case TransformProperty(clsName, name, property, meta, needCamelSnakeConversion, concreteTypes, isRequired) => Target.log.function("transformProperty") { for { - defaultValue <- ((property match { + defaultValue <- property match { case _: MapSchema => - Option(Target.pure(new ObjectCreationExpr(null, HASH_MAP_TYPE, new NodeList())).map(x => x: Expression)) + Target.pure(Option(new ObjectCreationExpr(null, HASH_MAP_TYPE, new NodeList())).map(x => x: Expression)) case _: ArraySchema => - Option(Target.pure(new ObjectCreationExpr(null, ARRAY_LIST_TYPE, new NodeList())).map(x => x: Expression)) + Target.pure(Option(new ObjectCreationExpr(null, ARRAY_LIST_TYPE, new NodeList())).map(x => x: Expression)) case p: BooleanSchema => - Default(p).extract[Boolean].map(x => Target.pure(new BooleanLiteralExpr(x))) + Default(p).extract[Boolean].traverse(x => Target.pure(new BooleanLiteralExpr(x))) case p: NumberSchema if p.getFormat == "double" => - Default(p).extract[Double].map(x => Target.pure(new DoubleLiteralExpr(x))) + Default(p).extract[Double].traverse(x => Target.pure(new DoubleLiteralExpr(x))) case p: NumberSchema if p.getFormat == "float" => - Default(p).extract[Float].map(x => Target.pure(new DoubleLiteralExpr(x))) + Default(p).extract[Float].traverse(x => Target.pure(new DoubleLiteralExpr(x))) case p: IntegerSchema if p.getFormat == "int32" => - Default(p).extract[Int].map(x => Target.pure(new IntegerLiteralExpr(x))) + Default(p).extract[Int].traverse(x => Target.pure(new IntegerLiteralExpr(x))) case p: IntegerSchema if p.getFormat == "int64" => - Default(p).extract[Long].map(x => Target.pure(new LongLiteralExpr(x))) + Default(p).extract[Long].traverse(x => Target.pure(new LongLiteralExpr(x))) case p: StringSchema => - Default(p).extract[String].map(x => Target.fromOption(Try(new StringLiteralExpr(x)).toOption, s"Default string literal for '${p.getTitle}' is null")) + Default(p).extract[String].traverse(x => Target.fromOption(Try(new StringLiteralExpr(x)).toOption, s"Default string literal for '${p.getTitle}' is null")) case _ => - None - }): Option[Target[Expression]]).sequence + Target.pure(None) + } readOnlyKey = Option(name).filter(_ => Option(property.getReadOnly).contains(true)) emptyToNull = (property match { From d9217186091c21433ec68e4fd2ae646c3640f767 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Thu, 14 Mar 2019 18:01:07 -0700 Subject: [PATCH 71/86] Simplify ComposedSchema transformation in Jackson generator --- .../com/twilio/guardrail/generators/Java/JacksonGenerator.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala index 5f7c1e7108..2dc49f8beb 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -211,7 +211,7 @@ object JacksonGenerator { (swagger match { case m: ObjectSchema => Target.pure(Option(m.getProperties)) case comp: ComposedSchema => - Target.pure(Option(comp.getAllOf()).toList.flatMap(_.asScala.toList).lastOption.flatMap(prop => Option(prop.getProperties))) + Target.pure(Option(comp.getAllOf).flatMap(_.asScala.toList.lastOption).flatMap(prop => Option(prop.getProperties))) case comp: Schema[_] if Option(comp.get$ref).isDefined => Target.raiseError(s"Attempted to extractProperties for a ${comp.getClass()}, unsure what to do here") case _ => Target.pure(None) From 1dafc40991fdf1c125c4dd324958cb60b038f166 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Fri, 15 Mar 2019 00:57:53 -0700 Subject: [PATCH 72/86] Have Java fold method lambdas take the response body, not response type --- .../Java/AsyncHttpClientClientGenerator.scala | 19 +++++++++++-------- .../guardrail/generators/syntax/Java.scala | 3 ++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index dce472d102..d2ac80e25c 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -40,10 +40,7 @@ object AsyncHttpClientClientGenerator { private val JSON_PROCESSING_EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("JsonProcessingException") private val CLIENT_EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("ClientException") - private val HTTP_CLIENT_FUNCTION_TYPE = functionType.setTypeArguments(new NodeList[Type]( - REQUEST_TYPE, - completionStageType.setTypeArguments(RESPONSE_TYPE) - )) + private val HTTP_CLIENT_FUNCTION_TYPE = functionType(REQUEST_TYPE, completionStageType.setTypeArguments(RESPONSE_TYPE)) private def typeReferenceType(typeArg: Type): ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("TypeReference").setTypeArguments(typeArg) @@ -466,6 +463,7 @@ object AsyncHttpClientClientGenerator { "java.util.Optional", "java.util.concurrent.CompletionStage", "java.util.function.Function", + "java.util.function.Supplier", "com.fasterxml.jackson.core.JsonProcessingException", "com.fasterxml.jackson.core.type.TypeReference", "com.fasterxml.jackson.databind.ObjectMapper", @@ -496,7 +494,9 @@ object AsyncHttpClientClientGenerator { val responseInnerClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, STATIC), false, responseName); responseInnerClass.addExtendedType(abstractClassName) - valueType.foreach({ vt => + val (foldMethodParamType, foldMethodApplier, foldMethodArgs) = valueType.fold( + (supplierType(genericTypeParam), "get", new NodeList[Expression]()) + )({ vt => val finalValueType: Type = vt.unbox responseInnerClass.addField(finalValueType, "value", PRIVATE, FINAL) @@ -516,9 +516,12 @@ object AsyncHttpClientClientGenerator { getValueMethod.setBody(new BlockStmt(new NodeList( new ReturnStmt(new FieldAccessExpr(new ThisExpr, "value")) ))) + + (functionType(vt, genericTypeParam), "apply", new NodeList[Expression]( + new MethodCallExpr(new EnclosedExpr(new CastExpr(responseType, new ThisExpr)), "getValue") + )) }) - val foldMethodParamType = functionType.setTypeArguments(responseType, genericTypeParam) val foldMethodParameter = new Parameter(util.EnumSet.of(FINAL), foldMethodParamType, new SimpleName(responseLambdaName)) val foldMethodBranch = new IfStmt( @@ -526,8 +529,8 @@ object AsyncHttpClientClientGenerator { new BlockStmt(new NodeList( new ReturnStmt(new MethodCallExpr( new NameExpr(responseLambdaName), - "apply", - new NodeList[Expression](new CastExpr(responseType, new ThisExpr)) + foldMethodApplier, + foldMethodArgs )) )), null diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index e7bc970d55..e1832dbad5 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -78,7 +78,8 @@ object Java { def completionStageType: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("CompletionStage" ) def optionalType: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Optional") - def functionType: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Function") + def functionType(in: Type, out: Type): ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Function").setTypeArguments(in, out) + def supplierType(of: Type): ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Supplier").setTypeArguments(of) val OBJECT_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Object") val STRING_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("String") From 35f1b428a6c2bda20b1571d225e053d30b6c92bf Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Fri, 15 Mar 2019 01:00:34 -0700 Subject: [PATCH 73/86] Clean up Java Type creators for common parameterizeable types --- .../generators/Java/AsyncHttpClientClientGenerator.scala | 8 ++++---- .../generators/Java/DropwizardServerGenerator.scala | 2 +- .../com/twilio/guardrail/generators/syntax/Java.scala | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index d2ac80e25c..81de8b00f2 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -40,7 +40,7 @@ object AsyncHttpClientClientGenerator { private val JSON_PROCESSING_EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("JsonProcessingException") private val CLIENT_EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("ClientException") - private val HTTP_CLIENT_FUNCTION_TYPE = functionType(REQUEST_TYPE, completionStageType.setTypeArguments(RESPONSE_TYPE)) + private val HTTP_CLIENT_FUNCTION_TYPE = functionType(REQUEST_TYPE, completionStageType(RESPONSE_TYPE)) private def typeReferenceType(typeArg: Type): ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("TypeReference").setTypeArguments(typeArg) @@ -447,7 +447,7 @@ object AsyncHttpClientClientGenerator { )) callBuilderCls.addMethod("call", PUBLIC) - .setType(completionStageType.setTypeArguments(responseParentType)) + .setType(completionStageType(responseParentType)) .addThrownException(CLIENT_EXCEPTION_TYPE) .setBody(new BlockStmt(List[Statement](new ReturnStmt(requestCall)).toNodeList)) @@ -628,12 +628,12 @@ object AsyncHttpClientClientGenerator { } builderClass.addFieldWithInitializer( - optionalType.setTypeArguments(HTTP_CLIENT_FUNCTION_TYPE), "httpClient", + optionalType(HTTP_CLIENT_FUNCTION_TYPE), "httpClient", new MethodCallExpr(new NameExpr("Optional"), "empty"), PRIVATE ) builderClass.addFieldWithInitializer( - optionalType.setTypeArguments(OBJECT_MAPPER_TYPE), "objectMapper", + optionalType(OBJECT_MAPPER_TYPE), "objectMapper", new MethodCallExpr(new NameExpr("Optional"), "empty"), PRIVATE ) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala index 54bc1dcb9b..6161a361c0 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala @@ -291,7 +291,7 @@ object DropwizardServerGenerator { new ExpressionStmt(new MethodCallExpr(handlerCall, "whenComplete", new NodeList[Expression](whenCompleteLambda))) ))) - val futureResponseType = completionStageType.setTypeArguments(responseType) + val futureResponseType = completionStageType(responseType) val handlerMethodSig = new MethodDeclaration(util.EnumSet.noneOf(classOf[Modifier]), futureResponseType, operationId) (parameters.pathParams ++ parameters.headerParams ++ parameters.queryStringParams ++ parameters.formParams ++ parameters.bodyParams).foreach({ parameter => handlerMethodSig.addParameter(parameter.param.clone()) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index e1832dbad5..d8584978ee 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -76,8 +76,8 @@ object Java { def safeParseRawImport(s: String): Target[ImportDeclaration] = safeParse("safeParseRawImport")(JavaParser.parseImport, s"import ${s};") def safeParseRawStaticImport(s: String): Target[ImportDeclaration] = safeParse("safeParseStaticImport")(JavaParser.parseImport, s"import static ${s};") - def completionStageType: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("CompletionStage" ) - def optionalType: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Optional") + def completionStageType(of: Type): ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("CompletionStage" ).setTypeArguments(of) + def optionalType(of: Type): ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Optional").setTypeArguments(of) def functionType(in: Type, out: Type): ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Function").setTypeArguments(in, out) def supplierType(of: Type): ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Supplier").setTypeArguments(of) From 991c7467f04614f2c3cc6ef0a82bba4e1237093c Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Fri, 15 Mar 2019 01:07:05 -0700 Subject: [PATCH 74/86] Remove need for temp var in DW server resource in lieu of EnclosedExpr --- .../Java/DropwizardServerGenerator.scala | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala index 6161a361c0..815aed2b81 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala @@ -230,12 +230,18 @@ object DropwizardServerGenerator { new IfStmt( new InstanceOfExpr(new NameExpr("result"), responseType), new BlockStmt(new NodeList( - new ExpressionStmt(new VariableDeclarationExpr(new VariableDeclarator(responseType, s"result${statusCodeName}", new CastExpr(responseType, new NameExpr("result"))), FINAL)), new ExpressionStmt( - new MethodCallExpr(new NameExpr("builder"), "entity", new NodeList[Expression]( - new MethodCallExpr(new NameExpr(s"result${statusCodeName}"), "getValue") - )) - ))), + new MethodCallExpr( + new NameExpr("builder"), + "entity", + new NodeList[Expression]( + new MethodCallExpr( + new EnclosedExpr(new CastExpr(responseType, new NameExpr("result"))), + "getValue") + ) + ) + ) + )), null ) }) From b2c98c281ad0cedb42559ceaa4304532e921cb98 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Fri, 15 Mar 2019 02:03:02 -0700 Subject: [PATCH 75/86] Make Java output a little prettier --- .../scala/com/twilio/guardrail/generators/syntax/Java.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index d8584978ee..ac5de287a2 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -87,8 +87,8 @@ object Java { val ASSERTION_ERROR_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("AssertionError") val printer: PrettyPrinterConfiguration = new PrettyPrinterConfiguration() - .setColumnAlignFirstMethodChain(true) - .setColumnAlignParameters(true) + .setColumnAlignFirstMethodChain(false) + .setColumnAlignParameters(false) .setIndentSize(4) .setIndentType(IndentType.SPACES) .setOrderImports(true) From d6c552ef184efb2a90517dbe63da3f2b874fbd8d Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Fri, 15 Mar 2019 02:04:24 -0700 Subject: [PATCH 76/86] Fix missing private modifier for fields in AHC call builders --- .../generators/Java/AsyncHttpClientClientGenerator.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index 81de8b00f2..fadd283a34 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -360,7 +360,7 @@ object AsyncHttpClientClientGenerator { ) val callBuilderCls = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, STATIC), false, callBuilderName) - callBuilderFinalFields.foreach({ case (tpe, name) => callBuilderCls.addField(tpe, name, FINAL) }) + callBuilderFinalFields.foreach({ case (tpe, name) => callBuilderCls.addField(tpe, name, PRIVATE, FINAL) }) callBuilderCls.addConstructor(PRIVATE) .setParameters(callBuilderFinalFields.map({ case (tpe, name) => new Parameter(util.EnumSet.of(FINAL), tpe, new SimpleName(name)) }).toNodeList) From 5986771a455355332202edf3fe8c98d30e724f70 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Fri, 15 Mar 2019 02:13:38 -0700 Subject: [PATCH 77/86] Clean up Java linter warnings in generated code --- build.sbt | 3 +++ .../Java/AsyncHttpClientClientGenerator.scala | 6 ++++++ .../guardrail/generators/Java/JacksonGenerator.scala | 10 ++++++---- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index d100138990..6a2423d41a 100644 --- a/build.sbt +++ b/build.sbt @@ -284,6 +284,9 @@ lazy val endpointsSample = (project in file("modules/sample-endpoints")) lazy val dropwizardSample = (project in file("modules/sample-dropwizard")) .settings( codegenSettings, + javacOptions ++= Seq( + "-Xlint:all" + ), libraryDependencies ++= Seq( "io.dropwizard" % "dropwizard-core" % dropwizardVersion, "org.glassfish.jersey.media" % "jersey-media-multipart" % jerseyVersion, diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index fadd283a34..4ed6b9409a 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -167,17 +167,23 @@ object AsyncHttpClientClientGenerator { new ExpressionStmt(new MethodCallExpr("super", new NameExpr("message"), new NameExpr("cause"))) ))) } + def addNoSerialVersionUuid(cls: ClassOrInterfaceDeclaration): Unit = { + cls.addSingleMemberAnnotation("SuppressWarnings", new StringLiteralExpr("serial")) + } val clientExceptionClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, ABSTRACT), false, "ClientException") .addExtendedType("RuntimeException") addStdConstructors(clientExceptionClass) + addNoSerialVersionUuid(clientExceptionClass) val marshallingExceptionClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, "MarshallingException") .addExtendedType("ClientException") addStdConstructors(marshallingExceptionClass) + addNoSerialVersionUuid(marshallingExceptionClass) val httpErrorClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, "HttpError") .addExtendedType("ClientException") + addNoSerialVersionUuid(httpErrorClass) httpErrorClass.addField(RESPONSE_TYPE, "response", PRIVATE, FINAL) httpErrorClass.addConstructor(PUBLIC) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala index 2dc49f8beb..62794ee6cf 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -69,8 +69,10 @@ object JacksonGenerator { .map(_.tpe) .map(f) - private val HASH_MAP_TYPE = JavaParser.parseClassOrInterfaceType("java.util.HashMap") - private val ARRAY_LIST_TYPE = JavaParser.parseClassOrInterfaceType("java.util.ArrayList") + private val HASH_MAP_TYPE_DIAMONDED = JavaParser.parseClassOrInterfaceType("java.util.HashMap") + .setTypeArguments(new NodeList[Type]) + private val ARRAY_LIST_TYPE_DIAMONDED = JavaParser.parseClassOrInterfaceType("java.util.ArrayList") + .setTypeArguments(new NodeList[Type]) object EnumProtocolTermInterp extends (EnumProtocolTerm[JavaLanguage, ?] ~> Target) { def apply[T](term: EnumProtocolTerm[JavaLanguage, T]): Target[T] = term match { @@ -222,9 +224,9 @@ object JacksonGenerator { for { defaultValue <- property match { case _: MapSchema => - Target.pure(Option(new ObjectCreationExpr(null, HASH_MAP_TYPE, new NodeList())).map(x => x: Expression)) + Target.pure(Option(new ObjectCreationExpr(null, HASH_MAP_TYPE_DIAMONDED, new NodeList())).map(x => x: Expression)) case _: ArraySchema => - Target.pure(Option(new ObjectCreationExpr(null, ARRAY_LIST_TYPE, new NodeList())).map(x => x: Expression)) + Target.pure(Option(new ObjectCreationExpr(null, ARRAY_LIST_TYPE_DIAMONDED, new NodeList())).map(x => x: Expression)) case p: BooleanSchema => Default(p).extract[Boolean].traverse(x => Target.pure(new BooleanLiteralExpr(x))) case p: NumberSchema if p.getFormat == "double" => From 9fe9a8dbd4d2ab53a07fae85ec4536b072fc643f Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Fri, 15 Mar 2019 20:27:08 -0700 Subject: [PATCH 78/86] Make custom type vendor extensions configurable by the language impl --- .../twilio/guardrail/ProtocolGenerator.scala | 17 +++--- .../com/twilio/guardrail/SwaggerUtil.scala | 45 ++++++++++++---- .../twilio/guardrail/extract/package.scala | 5 ++ .../generators/CirceProtocolGenerator.scala | 3 +- .../guardrail/generators/JavaGenerator.scala | 2 + .../guardrail/generators/ScalaGenerator.scala | 2 + .../guardrail/generators/ScalaParameter.scala | 53 +++++++------------ .../twilio/guardrail/terms/ScalaTerm.scala | 3 ++ .../twilio/guardrail/terms/ScalaTerms.scala | 5 +- 9 files changed, 83 insertions(+), 52 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala index 8317d3fa59..4bdd3c9659 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala @@ -4,7 +4,7 @@ import _root_.io.swagger.v3.oas.models._ import _root_.io.swagger.v3.oas.models.media._ import cats.free.Free import cats.implicits._ -import com.twilio.guardrail.extract.ScalaType +import com.twilio.guardrail.extract.VendorExtension.VendorExtensible._ import com.twilio.guardrail.generators.syntax.RichString import com.twilio.guardrail.languages.LA import com.twilio.guardrail.protocol.terms.protocol._ @@ -91,7 +91,8 @@ object ProtocolGenerator { for { enum <- extractEnum(swagger) - tpe <- SwaggerUtil.typeName(tpeName, Option(swagger.getFormat()), ScalaType(swagger)) + customTpeName <- SwaggerUtil.customTypeName(swagger) + tpe <- SwaggerUtil.typeName(tpeName, Option(swagger.getFormat()), customTpeName) res <- enum.traverse(validProg(_, tpe)) } yield res } @@ -275,8 +276,12 @@ object ProtocolGenerator { for { tpe <- model .flatMap(model => Option(model.getType)) - .fold[Free[F, L#Type]](objectType(None))( - raw => SwaggerUtil.typeName[L, F](raw, model.flatMap(f => Option(f.getFormat)), model.flatMap(ScalaType(_))) + .fold[Free[F, L#Type]](objectType(None))(raw => + model + .flatTraverse(SwaggerUtil.customTypeName[L, F, ObjectSchema]) + .flatMap(customTypeName => + SwaggerUtil.typeName[L, F](raw, model.flatMap(f => Option(f.getFormat)), customTypeName) + ) ) res <- typeAlias[L, F](clsName, tpe) } yield res @@ -410,8 +415,8 @@ object ProtocolGenerator { case x => for { tpeName <- getType(x) - - tpe <- SwaggerUtil.typeName[L, F](tpeName, Option(x.getFormat()), ScalaType(x)) + customTypeName <- SwaggerUtil.customTypeName(x) + tpe <- SwaggerUtil.typeName[L, F](tpeName, Option(x.getFormat()), customTypeName) res <- typeAlias(clsName, tpe) } yield res } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala index 78e1e38f19..c41007576a 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala @@ -13,7 +13,8 @@ import com.github.javaparser.ast.NodeList import com.github.javaparser.ast.expr._ import com.twilio.guardrail.terms.{ScalaTerms, SwaggerTerms} import com.twilio.guardrail.terms.framework.FrameworkTerms -import com.twilio.guardrail.extract.{Default, ScalaType} +import com.twilio.guardrail.extract.{Default, VendorExtension, extractFromNames} +import com.twilio.guardrail.extract.VendorExtension.VendorExtensible._ import com.twilio.guardrail.generators.{Responses, ScalaParameter} import com.twilio.guardrail.languages.{JavaLanguage, LA, ScalaLanguage} import com.twilio.guardrail.shims._ @@ -136,6 +137,12 @@ object SwaggerUtil { } } + def customTypeName[L <: LA, F[_], A: VendorExtension.VendorExtensible](v: A)(implicit S: ScalaTerms[L, F]): Free[F, Option[String]] = { + for { + prefixes <- S.customTypePrefixes() + } yield extractFromNames[String, A](prefixes.map(_ + "-type"), v) + } + sealed class ModelMetaTypePartiallyApplied[L <: LA, F[_]](val dummy: Boolean = true) { def apply[T <: Schema[_]](model: T)(implicit Sc: ScalaTerms[L, F], Sw: SwaggerTerms[L, F], F: FrameworkTerms[L, F]): Free[F, ResolvedType[L]] = Sw.log.function("modelMetaType") { @@ -163,7 +170,8 @@ object SwaggerUtil { case impl: Schema[_] => for { tpeName <- getType(impl) - tpe <- typeName[L, F](tpeName, Option(impl.getFormat()), ScalaType(impl)) + customTpeName <- customTypeName(impl) + tpe <- typeName[L, F](tpeName, Option(impl.getFormat()), customTpeName) } yield Resolved[L](tpe, None, None) }) } @@ -257,6 +265,7 @@ object SwaggerUtil { import F._ import Sc._ import Sw._ + log.debug(s"property:\n${log.schemaToString(property)}") >> (property match { case p: ArraySchema => for { @@ -291,23 +300,41 @@ object SwaggerUtil { getSimpleRef(ref).map(Deferred[L]) case b: BooleanSchema => - (typeName[L, F]("boolean", None, ScalaType(b)), Default(b).extract[Boolean].traverse(litBoolean(_))).mapN(Resolved[L](_, None, _)) + for { + customTpeName <- customTypeName(b) + res <- (typeName[L, F]("boolean", None, customTpeName), Default(b).extract[Boolean].traverse(litBoolean(_))).mapN(Resolved[L](_, None, _)) + } yield res case s: StringSchema => - (typeName[L, F]("string", Option(s.getFormat()), ScalaType(s)), Default(s).extract[String].traverse(litString(_))) - .mapN(Resolved[L](_, None, _)) + for { + customTpeName <- customTypeName(s) + res <- (typeName[L, F]("string", Option(s.getFormat()), customTpeName), Default(s).extract[String].traverse(litString(_))) + .mapN(Resolved[L](_, None, _)) + } yield res case d: DateSchema => - typeName[L, F]("string", Some("date"), ScalaType(d)).map(Resolved[L](_, None, None)) + for { + customTpeName <- customTypeName(d) + res <- typeName[L, F]("string", Some("date"), customTpeName).map(Resolved[L](_, None, None)) + } yield res case d: DateTimeSchema => - typeName[L, F]("string", Some("date-time"), ScalaType(d)).map(Resolved[L](_, None, None)) + for { + customTpeName <- customTypeName(d) + res <- typeName[L, F]("string", Some("date-time"), customTpeName).map(Resolved[L](_, None, None)) + } yield res case i: IntegerSchema => - typeName[L, F]("integer", Option(i.getFormat), ScalaType(i)).map(Resolved[L](_, None, None)) + for { + customTpeName <- customTypeName(i) + res <- typeName[L, F]("integer", Option(i.getFormat), customTpeName).map(Resolved[L](_, None, None)) + } yield res case d: NumberSchema => - typeName[L, F]("number", Option(d.getFormat), ScalaType(d)).map(Resolved[L](_, None, None)) + for { + customTpeName <- customTypeName(d) + res <- typeName[L, F]("number", Option(d.getFormat), customTpeName).map(Resolved[L](_, None, None)) + } yield res case x => fallbackPropertyTypeHandler(x).map(Resolved[L](_, None, None)) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/extract/package.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/extract/package.scala index 9684065d1e..0ae835af98 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/extract/package.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/extract/package.scala @@ -14,4 +14,9 @@ package object extract { VendorExtension(v).extract[EmptyToNullBehaviour]("x-scala-empty-is-null") def ScalaFileHashAlgorithm[F: VendorExtension.VendorExtensible](v: F): Option[String] = VendorExtension(v).extract[String]("x-scala-file-hash") + + def extractFromNames[T: Extractable, F: VendorExtension.VendorExtensible](names: List[String], v: F): Option[T] = + names.foldLeft(Option.empty[T])( + (accum, name) => accum.orElse(VendorExtension(v).extract[T](name)) + ) } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala index 0d48d13637..68486594b2 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/CirceProtocolGenerator.scala @@ -5,7 +5,7 @@ import _root_.io.swagger.v3.oas.models.media._ import cats.implicits._ import cats.~> import cats.data.NonEmptyList -import com.twilio.guardrail.extract.{ Default, ScalaEmptyIsNull, ScalaType } +import com.twilio.guardrail.extract.{ Default, ScalaEmptyIsNull } import com.twilio.guardrail.generators.syntax.RichString import com.twilio.guardrail.shims._ import com.twilio.guardrail.terms @@ -14,6 +14,7 @@ import com.twilio.guardrail.languages.{ LA, ScalaLanguage } import com.twilio.guardrail.protocol.terms.protocol._ import scala.collection.JavaConverters._ import scala.meta._ +import scala.language.existentials object CirceProtocolGenerator { import ProtocolGenerator._ diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala index 866faa7513..599f5cd9a2 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala @@ -28,6 +28,8 @@ object JavaGenerator { } def apply[T](term: ScalaTerm[JavaLanguage, T]): Target[T] = term match { + case CustomTypePrefixes() => Target.pure(List("x-java", "x-jvm")) + case LitString(value) => Target.pure(new StringLiteralExpr(value)) case LitFloat(value) => Target.pure(new DoubleLiteralExpr(value)) case LitDouble(value) => Target.pure(new DoubleLiteralExpr(value)) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala index d3e6ff9f06..454931dc30 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala @@ -21,7 +21,9 @@ object ScalaGenerator { val buildPkgTerm: List[String] => Term.Ref = _.map(Term.Name.apply _).reduceLeft(Term.Select.apply _) + def apply[T](term: ScalaTerm[ScalaLanguage, T]): Target[T] = term match { + case CustomTypePrefixes() => Target.pure(List("x-scala", "x-jvm")) case LitString(value) => Target.pure(Lit.String(value)) case LitFloat(value) => Target.pure(Lit.Float(value)) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaParameter.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaParameter.scala index 6c85d51793..2385f92500 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaParameter.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaParameter.scala @@ -3,21 +3,22 @@ package generators import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.parameters._ -import com.twilio.guardrail.extract.{ Default, ScalaFileHashAlgorithm, ScalaType } +import com.twilio.guardrail.extract.{Default, ScalaFileHashAlgorithm} import com.twilio.guardrail.generators.syntax.RichString import com.twilio.guardrail.languages.LA import com.twilio.guardrail.languages.ScalaLanguage import com.twilio.guardrail.shims._ -import com.twilio.guardrail.terms.{ ScalaTerm, ScalaTerms, SwaggerTerm, SwaggerTerms } -import com.twilio.guardrail.terms.framework.{ FrameworkTerm, FrameworkTerms } +import com.twilio.guardrail.terms.{ScalaTerm, ScalaTerms, SwaggerTerm, SwaggerTerms} +import com.twilio.guardrail.terms.framework.{FrameworkTerm, FrameworkTerms} import java.util.Locale - import scala.meta._ import cats.MonadError import cats.implicits._ import cats.arrow.FunctionK import cats.free.Free -import cats.data.{ EitherK, EitherT } +import cats.data.{EitherK, EitherT} +import com.twilio.guardrail.SwaggerUtil.ResolvedType +import com.twilio.guardrail.extract.VendorExtension.VendorExtensible case class RawParameterName private[generators] (value: String) class ScalaParameters[L <: LA](val parameters: List[ScalaParameter[L]]) { @@ -77,6 +78,14 @@ object ScalaParameter { } }) + def resolveParam(param: Parameter, typeFetcher: Parameter => Free[F, String]): Free[F, ResolvedType[L]] = + for { + tpeName <- typeFetcher(param) + customTypeName <- SwaggerUtil.customTypeName(param) + res <- (SwaggerUtil.typeName[L, F](tpeName, Option(param.format()), customTypeName), getDefault(param)) + .mapN(SwaggerUtil.Resolved[L](_, None, _)) + } yield res + param match { case r: Parameter if r.isRef => getRefParameterRef(r) @@ -86,43 +95,19 @@ object ScalaParameter { getBodyParameterSchema(x).flatMap(x => SwaggerUtil.modelMetaType[L, F](x)) case x: Parameter if x.isInHeader => - getHeaderParameterType(x).flatMap( - tpeName => - (SwaggerUtil.typeName[L, F](tpeName, Option(x.format()), ScalaType(x)), getDefault(x)) - .mapN(SwaggerUtil.Resolved[L](_, None, _)) - ) + resolveParam(x, getHeaderParameterType) case x: Parameter if x.isInPath => - getPathParameterType(x) - .flatMap( - tpeName => - (SwaggerUtil.typeName[L, F](tpeName, Option(x.format()), ScalaType(x)), getDefault(x)) - .mapN(SwaggerUtil.Resolved[L](_, None, _)) - ) + resolveParam(x, getPathParameterType) case x: Parameter if x.isInQuery => - getQueryParameterType(x) - .flatMap( - tpeName => - (SwaggerUtil.typeName[L, F](tpeName, Option(x.format()), ScalaType(x)), getDefault(x)) - .mapN(SwaggerUtil.Resolved[L](_, None, _)) - ) + resolveParam(x, getQueryParameterType) case x: Parameter if x.isInCookies => - getCookieParameterType(x) - .flatMap( - tpeName => - (SwaggerUtil.typeName[L, F](tpeName, Option(x.format()), ScalaType(x)), getDefault(x)) - .mapN(SwaggerUtil.Resolved[L](_, None, _)) - ) + resolveParam(x, getCookieParameterType) case x: Parameter if x.isInFormData => - getFormParameterType(x) - .flatMap( - tpeName => - (SwaggerUtil.typeName[L, F](tpeName, Option(x.format()), ScalaType(x)), getDefault(x)) - .mapN(SwaggerUtil.Resolved[L](_, None, _)) - ) + resolveParam(x, getFormParameterType) case x => fallbackParameterHandler(x) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala index a787925507..84911987e5 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerm.scala @@ -6,6 +6,9 @@ import com.twilio.guardrail.languages.LA import java.nio.file.Path sealed trait ScalaTerm[L <: LA, T] + +case class CustomTypePrefixes[L <: LA]() extends ScalaTerm[L, List[String]] + case class LitString[L <: LA](value: String) extends ScalaTerm[L, L#Term] case class LitFloat[L <: LA](value: Float) extends ScalaTerm[L, L#Term] case class LitDouble[L <: LA](value: Double) extends ScalaTerm[L, L#Term] diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala index 66f5454a91..269370ab7d 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/ScalaTerms.scala @@ -2,13 +2,14 @@ package com.twilio.guardrail package terms import cats.InjectK -import cats.data.NonEmptyList import cats.free.Free -import com.twilio.guardrail.languages.LA import com.twilio.guardrail.SwaggerUtil.LazyResolvedType +import com.twilio.guardrail.languages.LA import java.nio.file.Path class ScalaTerms[L <: LA, F[_]](implicit I: InjectK[ScalaTerm[L, ?], F]) { + def customTypePrefixes(): Free[F, List[String]] = Free.inject[ScalaTerm[L, ?], F](CustomTypePrefixes[L]()) + def litString(value: String): Free[F, L#Term] = Free.inject[ScalaTerm[L, ?], F](LitString(value)) def litFloat(value: Float): Free[F, L#Term] = Free.inject[ScalaTerm[L, ?], F](LitFloat(value)) def litDouble(value: Double): Free[F, L#Term] = Free.inject[ScalaTerm[L, ?], F](LitDouble(value)) From 8b8b1375dc72524674111cc1514eb3251df57061 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Fri, 15 Mar 2019 21:22:29 -0700 Subject: [PATCH 79/86] Split java & scala test suites into their own sbt commands --- build.sbt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 6a2423d41a..2e5ab24876 100644 --- a/build.sbt +++ b/build.sbt @@ -129,10 +129,11 @@ addCommandAlias("cli", "runMain com.twilio.guardrail.CLI") addCommandAlias("runtimeScalaSuite", "; resetSample ; runScalaExample ; " + scalaFrameworks.map(x => s"${x}Sample/test").mkString("; ")) addCommandAlias("runtimeJavaSuite", "; resetSample ; runJavaExample ; " + javaFrameworks.map(x => s"${x}Sample/test").mkString("; ")) addCommandAlias("runtimeSuite", "runtimeScalaSuite ; runtimeJavaSuite") -addCommandAlias("scalaTestSuite", "; codegen/test ; runtimeSuite") +addCommandAlias("scalaTestSuite", "; codegen/test ; runtimeScalaSuite") +addCommandAlias("javaTestSuite", "; codegen/test ; runtimeJavaSuite") addCommandAlias("format", "; codegen/scalafmt ; codegen/test:scalafmt ; " + scalaFrameworks.map(x => s"${x}Sample/scalafmt ; ${x}Sample/test:scalafmt").mkString("; ")) addCommandAlias("checkFormatting", "; codegen/scalafmtCheck ; " + scalaFrameworks.map(x => s"${x}Sample/scalafmtCheck ; ${x}Sample/test:scalafmtCheck").mkString("; ")) -addCommandAlias("testSuite", "; scalaTestSuite") +addCommandAlias("testSuite", "; scalaTestSuite ; javaTestSuite") addCommandAlias( "publishBintray", From cbcaeb244ac9407f538ecf7eac67a2e7a84e2f02 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Fri, 15 Mar 2019 21:22:58 -0700 Subject: [PATCH 80/86] Aid discoverability in DW response types with static creator methods --- .../Java/DropwizardServerGenerator.scala | 37 ++++++++++++++----- .../Dropwizard/DropwizardRoundTripTest.scala | 6 +-- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala index 815aed2b81..75c46ca228 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala @@ -368,34 +368,51 @@ object DropwizardServerGenerator { val responseClasses = responses.value.map { response => val clsName: String = response.statusCodeName.asString + val clsType = JavaParser.parseClassOrInterfaceType(clsName) val cls = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, STATIC), false, clsName) cls.setExtendedTypes(new NodeList(abstractResponseClassType)) - val (fields, constructor, methods) = response.value.fold({ - val constructor = new ConstructorDeclaration(util.EnumSet.of(PUBLIC), clsName) + val (fields, constructor, creator, methods) = response.value.fold[(List[FieldDeclaration], ConstructorDeclaration, BodyDeclaration[_], List[MethodDeclaration])]({ + val constructor = new ConstructorDeclaration(util.EnumSet.of(PRIVATE), clsName) constructor.setBody(new BlockStmt(new NodeList( new ExpressionStmt(new MethodCallExpr("super", new IntegerLiteralExpr(response.statusCode))) ))) - (List.empty[FieldDeclaration], constructor, List.empty[MethodDeclaration]) + + val creator = new FieldDeclaration( + util.EnumSet.of(PUBLIC, STATIC, FINAL), + new VariableDeclarator(clsType, clsName, new ObjectCreationExpr(null, clsType, new NodeList)) + ) + + (List.empty[FieldDeclaration], constructor, creator, List.empty[MethodDeclaration]) })({ case (valueType, _) => val unboxedValueType: Type = valueType.unbox val valueField = new FieldDeclaration(util.EnumSet.of(PRIVATE, FINAL), new VariableDeclarator(unboxedValueType, "value")) - val constructor = new ConstructorDeclaration(util.EnumSet.of(PUBLIC), clsName) - constructor.addParameter(new Parameter(util.EnumSet.of(FINAL), unboxedValueType, new SimpleName("value"))) - constructor.setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new MethodCallExpr("super", new IntegerLiteralExpr(response.statusCode))), - new ExpressionStmt(new AssignExpr(new FieldAccessExpr(new ThisExpr, "value"), new NameExpr("value"), AssignExpr.Operator.ASSIGN)) - ))) + val constructParam = new Parameter(util.EnumSet.of(FINAL), unboxedValueType, new SimpleName("value")) + + val constructor = new ConstructorDeclaration(util.EnumSet.of(PRIVATE), clsName) + .addParameter(constructParam) + .setBody(new BlockStmt(new NodeList( + new ExpressionStmt(new MethodCallExpr("super", new IntegerLiteralExpr(response.statusCode))), + new ExpressionStmt(new AssignExpr(new FieldAccessExpr(new ThisExpr, "value"), new NameExpr("value"), AssignExpr.Operator.ASSIGN)) + ))) + + val creator = new MethodDeclaration(util.EnumSet.of(PUBLIC, STATIC), clsType, clsName) + .addParameter(constructParam) + .setBody(new BlockStmt(new NodeList( + new ReturnStmt(new ObjectCreationExpr(null, clsType, new NodeList(new NameExpr("value")))) + ))) val getValueMethod = new MethodDeclaration(util.EnumSet.of(PUBLIC), unboxedValueType, "getValue") getValueMethod.setBody(new BlockStmt(new NodeList( new ReturnStmt(new FieldAccessExpr(new ThisExpr, "value")) ))) - (valueField :: Nil, constructor, getValueMethod :: Nil) + (valueField :: Nil, constructor, creator, getValueMethod :: Nil) }) + (fields ++ Option(constructor) ++ methods).foreach(cls.addMember) + abstractResponseClass.addMember(creator) cls } diff --git a/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardRoundTripTest.scala b/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardRoundTripTest.scala index 82eec4a950..bf69f238f7 100644 --- a/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardRoundTripTest.scala +++ b/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardRoundTripTest.scala @@ -34,7 +34,7 @@ class DropwizardRoundTripTest extends FreeSpec with Matchers with Waiters with M override def getUserByName(username: String): CompletionStage[GetUserByNameResponse] = { username match { case USERNAME => - serverFuture.complete(new GetUserByNameResponse.Ok(User.builder() + serverFuture.complete(GetUserByNameResponse.Ok(User.builder() .withEmail("foo@bar.com") .withFirstName("Foo") .withLastName("Bar") @@ -43,9 +43,9 @@ class DropwizardRoundTripTest extends FreeSpec with Matchers with Waiters with M .build() )) case "" => - serverFuture.complete(new GetUserByNameResponse.BadRequest) + serverFuture.complete(GetUserByNameResponse.BadRequest) case _ => - serverFuture.complete(new GetUserByNameResponse.NotFound) + serverFuture.complete(GetUserByNameResponse.NotFound) } serverFuture } From c9eda6ebb9cd1eab3656985eacce20b5ee614bd1 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Fri, 15 Mar 2019 21:54:37 -0700 Subject: [PATCH 81/86] Add simple DW server test using the dropwizard-testing framework --- build.sbt | 6 ++ .../Dropwizard/DropwizardResourceTest.java | 102 ++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 modules/sample-dropwizard/src/test/java/core/Dropwizard/DropwizardResourceTest.java diff --git a/build.sbt b/build.sbt index 2e5ab24876..bd07377574 100644 --- a/build.sbt +++ b/build.sbt @@ -288,14 +288,20 @@ lazy val dropwizardSample = (project in file("modules/sample-dropwizard")) javacOptions ++= Seq( "-Xlint:all" ), + testOptions in Test += Tests.Argument(TestFrameworks.JUnit, "-a", "-v"), libraryDependencies ++= Seq( "io.dropwizard" % "dropwizard-core" % dropwizardVersion, "org.glassfish.jersey.media" % "jersey-media-multipart" % jerseyVersion, "org.asynchttpclient" % "async-http-client" % ahcVersion, "org.scala-lang.modules" %% "scala-java8-compat" % "0.9.0" % Test, "org.scalatest" %% "scalatest" % scalatestVersion % Test, + "junit" % "junit" % "4.12" % Test, + "com.novocode" % "junit-interface" % "0.11" % Test, "org.mockito" %% "mockito-scala" % "1.2.0" % Test, + "io.dropwizard" % "dropwizard-testing" % dropwizardVersion % Test, + "org.glassfish.jersey.test-framework.providers" % "jersey-test-framework-provider-grizzly2" % jerseyVersion % Test ), + crossPaths := false, // strangely needed to get the JUnit tests to run at all skip in publish := true, scalafmtOnCompile := false ) diff --git a/modules/sample-dropwizard/src/test/java/core/Dropwizard/DropwizardResourceTest.java b/modules/sample-dropwizard/src/test/java/core/Dropwizard/DropwizardResourceTest.java new file mode 100644 index 0000000000..bda1258996 --- /dev/null +++ b/modules/sample-dropwizard/src/test/java/core/Dropwizard/DropwizardResourceTest.java @@ -0,0 +1,102 @@ +package core.Dropwizard; + +import examples.server.dropwizard.definitions.User; +import examples.server.dropwizard.user.CreateUserResponse; +import examples.server.dropwizard.user.GetUserByNameResponse; +import examples.server.dropwizard.user.LoginUserResponse; +import examples.server.dropwizard.user.UserHandler; +import examples.server.dropwizard.user.UserResource; +import io.dropwizard.testing.junit.ResourceTestRule; +import org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; + +import javax.ws.rs.BadRequestException; +import javax.ws.rs.NotFoundException; +import javax.ws.rs.client.Entity; + +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class DropwizardResourceTest { + private static final String USERNAME = "foobar"; + private static final String PASSWORD = "sekrit"; + private static final String TOKEN = "abc123"; + private static final User USER = User.builder() + .withId(1) + .withUsername(USERNAME) + .withPassword(PASSWORD) + .withFirstName("Blast") + .withLastName("Hardcheese") + .build(); + + private static final UserHandler userHandler = mock(UserHandler.class); + + @ClassRule + public static final ResourceTestRule resources = ResourceTestRule.builder() + .setTestContainerFactory(new GrizzlyTestContainerFactory()) + .addResource(new UserResource(userHandler)) + .build(); + + @Before + public void setup() { + when(userHandler.getUserByName(anyString())).thenReturn(completedFuture(GetUserByNameResponse.NotFound)); + when(userHandler.getUserByName(eq(" "))).thenReturn(completedFuture(GetUserByNameResponse.BadRequest)); + when(userHandler.getUserByName(eq(USERNAME))).thenReturn(completedFuture(GetUserByNameResponse.Ok(USER))); + + when(userHandler.createUser(eq(USER))).thenReturn(completedFuture(CreateUserResponse.Ok)); + + when(userHandler.loginUser(anyString(), anyString())).thenReturn(completedFuture(LoginUserResponse.BadRequest)); + when(userHandler.loginUser(eq(USERNAME), eq(PASSWORD))).thenReturn(completedFuture(LoginUserResponse.Ok(TOKEN))); + } + + @Test + public void testGetUserByName() { + assertThat(resources.target("/v2/user/" + USERNAME).request().get(User.class)).isEqualTo(USER); + verify(userHandler).getUserByName(USERNAME); + } + + @Test(expected = NotFoundException.class) + public void testUnknownGetUserByName() { + resources.target("/v2/user/nope").request().get(User.class); + } + + @Test(expected = BadRequestException.class) + public void testInvalidGetUserByName() { + resources.target("/v2/user/ ").request().get(User.class); + } + + @Test + public void testCreateUser() { + assertThat(resources.target("/v2/user").request().post(Entity.json(USER)).getStatus()).isEqualTo(200); + verify(userHandler).createUser(USER); + } + + @Test + public void testLoginUser() { + assertThat(resources + .target("/v2/user/login") + .queryParam("username", USERNAME) + .queryParam("password", PASSWORD) + .request() + .get(String.class) + ).isEqualTo(TOKEN); + verify(userHandler).loginUser(USERNAME, PASSWORD); + } + + @Test(expected = BadRequestException.class) + public void testInvalidLoginUser() { + resources + .target("/v2/user/login") + .queryParam("username", "moo") + .queryParam("password", "") + .request() + .get(String.class); + } +} From 16b9c2c9dbe3d40593277f36a18b718073626b3c Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Sat, 16 Mar 2019 00:15:31 -0700 Subject: [PATCH 82/86] Don't add a DW @Path annotation to methods if the path is empty It's not broken with empty paths, but Jersey warns about it on startup. --- .../guardrail/generators/Java/DropwizardServerGenerator.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala index 75c46ca228..d0fbdb9cb4 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala @@ -174,7 +174,9 @@ object DropwizardServerGenerator { method.addAnnotation(httpMethodAnnotation) val pathSuffix = splitPathComponents(path).drop(commonPathPrefix.length).mkString("/", "/", "") - method.addAnnotation(new SingleMemberAnnotationExpr(new Name("Path"), new StringLiteralExpr(pathSuffix))) + if (pathSuffix.nonEmpty && pathSuffix != "/") { + method.addAnnotation(new SingleMemberAnnotationExpr(new Name("Path"), new StringLiteralExpr(pathSuffix))) + } val consumes = getBestConsumes(operation.consumes.flatMap(RouteMeta.ContentType.unapply).toList, parameters) .orElse({ From 54ddfb1bd484952c7a447c4ebd40343cf0f5d6d6 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Sat, 16 Mar 2019 00:57:39 -0700 Subject: [PATCH 83/86] Make SwaggerUtil.generateUrlPathParams() language-agnostic --- .../com/twilio/guardrail/SwaggerUtil.scala | 74 +++++-------------- .../generators/AkkaHttpClientGenerator.scala | 2 +- .../generators/Http4sClientGenerator.scala | 2 +- .../Java/AsyncHttpClientClientGenerator.scala | 25 ++++++- .../guardrail/generators/syntax/Scala.scala | 14 +++- .../scala/tests/core/PathParserSpec.scala | 2 +- 6 files changed, 56 insertions(+), 63 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala index c41007576a..e40b0dc3d6 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala @@ -395,33 +395,35 @@ object SwaggerUtil { Resolved[L](ignoredType, None, None) } - // FIXME: this is mostly copy-paste from the scala impl; can almost certainly generic-ify it - object jpaths { + object paths { import atto._, Atto._ - private[this] def lookupName[T](bindingName: String, - pathArgs: List[ScalaParameter[JavaLanguage]])(f: ScalaParameter[JavaLanguage] => atto.Parser[T]): atto.Parser[T] = + private[this] def lookupName[L <: LA, T](bindingName: String, + pathArgs: List[ScalaParameter[L]]) + (f: ScalaParameter[L] => atto.Parser[T]): atto.Parser[T] = pathArgs .find(_.argName.value == bindingName) .fold[atto.Parser[T]]( - err(s"Unable to find argument ${bindingName}") - )(param => f(param)) + err(s"Unable to find argument ${bindingName}") + )(param => f(param)) private[this] val variable: atto.Parser[String] = char('{') ~> many(notChar('}')) .map(_.mkString("")) <~ char('}') - def generateUrlPathParams(path: String, pathArgs: List[ScalaParameter[JavaLanguage]]): Target[Expression] = { - val term: atto.Parser[Expression] = variable.flatMap { binding => + def generateUrlPathParams[L <: LA](path: String, + pathArgs: List[ScalaParameter[L]], + showLiteralPathComponent: String => L#Term, + showInterpolatedPathComponent: L#TermName => L#Term, + initialPathTerm: L#Term, + combinePathTerms: (L#Term, L#Term) => L#Term + ): Target[L#Term] = { + val term: atto.Parser[L#Term] = variable.flatMap { binding => lookupName(binding, pathArgs) { param => - ok(new MethodCallExpr( - new MethodCallExpr(new NameExpr("Shower"), "getInstance"), - "show", - new NodeList[Expression](new NameExpr(param.paramName.asString)) - )) + ok(showInterpolatedPathComponent(param.paramName)) } } val other: atto.Parser[String] = many1(notChar('{')).map(_.toList.mkString) - val pattern: atto.Parser[List[Either[String, Expression]]] = many(either(term, other).map(_.swap: Either[String, Expression])) + val pattern: atto.Parser[List[Either[String, L#Term]]] = many(either(term, other).map(_.swap: Either[String, L#Term])) for { parts <- pattern @@ -430,48 +432,10 @@ object SwaggerUtil { .fold(Target.raiseError, Target.pure) result = parts .map({ - case Left(part) => new StringLiteralExpr(part) - case Right(term) => term - }) - .foldLeft[Expression](new MethodCallExpr(new FieldAccessExpr(new ThisExpr, "baseUrl"), "toString"))({ case (a, b) => new BinaryExpr(a, b, BinaryExpr.Operator.PLUS) }) - } yield result - } - } - - object paths { - import atto._, Atto._ - - private[this] def lookupName[T](bindingName: String, - pathArgs: List[ScalaParameter[ScalaLanguage]])(f: ScalaParameter[ScalaLanguage] => Parser[T]): Parser[T] = - pathArgs - .find(_.argName.value == bindingName) - .fold[Parser[T]]( - err(s"Unable to find argument ${bindingName}") - )(param => f(param)) - - private[this] val variable: Parser[String] = char('{') ~> many(notChar('}')) - .map(_.mkString("")) <~ char('}') - - def generateUrlPathParams(path: String, pathArgs: List[ScalaParameter[ScalaLanguage]]): Target[Term] = { - val term: Parser[Term.Apply] = variable.flatMap { binding => - lookupName(binding, pathArgs) { param => - ok(q"Formatter.addPath(${param.paramName})") - } - } - val other: Parser[String] = many1(notChar('{')).map(_.toList.mkString) - val pattern: Parser[List[Either[String, Term.Apply]]] = many(either(term, other).map(_.swap: Either[String, Term.Apply])) - - for { - parts <- pattern - .parseOnly(path) - .either - .fold(Target.raiseError(_), Target.pure(_)) - result = parts - .map({ - case Left(part) => Lit.String(part) + case Left(part) => showLiteralPathComponent(part) case Right(term) => term }) - .foldLeft[Term](q"host + basePath")({ case (a, b) => q"${a} + ${b}" }) + .foldLeft[L#Term](initialPathTerm)((a, b) => combinePathTerms(a, b)) } yield result } @@ -493,7 +457,7 @@ object SwaggerUtil { def regexSegment(implicit pathArgs: List[ScalaParameter[ScalaLanguage]]): P = (plainString ~ variable ~ plainString).flatMap { case ((before, binding), after) => - lookupName(binding, pathArgs) { + lookupName[ScalaLanguage, (Option[TN], T)](binding, pathArgs) { case param @ ScalaParameter(_, _, paramName, argName, _) => val value = if (before.nonEmpty || after.nonEmpty) { pathSegmentConverter(param, Some(litRegex(before.mkString, paramName, after.mkString))) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpClientGenerator.scala index a7a5a2ce89..ba40a533df 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpClientGenerator.scala @@ -42,7 +42,7 @@ object AkkaHttpClientGenerator { Target.log.function("generateUrlWithParams") { for { _ <- Target.log.debug("generateClientOperation", "generateUrlWithParams")(s"Using $path and ${pathArgs.map(_.argName)}") - base <- SwaggerUtil.paths.generateUrlPathParams(path, pathArgs) + base <- generateUrlPathParams(path, pathArgs) _ <- Target.log.debug("generateClientOperation", "generateUrlWithParams")(s"QS: $qsArgs") diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sClientGenerator.scala index 3da72bdab5..f101a0f45b 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sClientGenerator.scala @@ -43,7 +43,7 @@ object Http4sClientGenerator { def generateUrlWithParams(path: String, pathArgs: List[ScalaParameter[ScalaLanguage]], qsArgs: List[ScalaParameter[ScalaLanguage]]): Target[Term] = for { _ <- Target.log.debug("generateClientOperation", "generateUrlWithParams")(s"Using ${path} and ${pathArgs.map(_.argName)}") - base <- SwaggerUtil.paths.generateUrlPathParams(path, pathArgs) + base <- generateUrlPathParams(path, pathArgs) _ <- Target.log.debug("generateClientOperation", "generateUrlWithParams")(s"QS: ${qsArgs}") diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index 4ed6b9409a..3625bfe3fe 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -11,13 +11,12 @@ import com.github.javaparser.ast.`type`.{ClassOrInterfaceType, Type, VoidType} import com.github.javaparser.ast.body._ import com.github.javaparser.ast.expr.{MethodCallExpr, NameExpr, _} import com.github.javaparser.ast.stmt._ -import com.twilio.guardrail.SwaggerUtil.jpaths import com.twilio.guardrail.generators.{Response, ScalaParameter} import com.twilio.guardrail.generators.syntax.Java._ import com.twilio.guardrail.languages.JavaLanguage import com.twilio.guardrail.protocol.terms.client._ import com.twilio.guardrail.terms.RouteMeta -import com.twilio.guardrail.{RenderedClientOperation, StaticDefns, SupportDefinition, Target} +import com.twilio.guardrail.{RenderedClientOperation, StaticDefns, SupportDefinition, SwaggerUtil, Target} import java.net.URI import java.util @@ -310,7 +309,27 @@ object AsyncHttpClientClientGenerator { for { responseParentType <- safeParseClassOrInterfaceType(responseParentName) callBuilderType <- safeParseClassOrInterfaceType(callBuilderName) - pathExpr <- jpaths.generateUrlPathParams(pathStr, parameters.pathParams) + + pathExprNode <- SwaggerUtil.paths.generateUrlPathParams[JavaLanguage]( + pathStr, + parameters.pathParams, + new StringLiteralExpr(_), + name => new MethodCallExpr( + new MethodCallExpr(new NameExpr("Shower"), "getInstance"), + "show", + new NodeList[Expression](new NameExpr(name.asString)) + ), + new MethodCallExpr(new FieldAccessExpr(new ThisExpr, "baseUrl"), "toString"), + (a, b) => (a, b) match { + case (ae: Expression, be: Expression) => new BinaryExpr(ae, be, BinaryExpr.Operator.PLUS) + case _ => a + } + ) + + pathExpr <- pathExprNode match { + case e: Expression => Target.pure(e) + case x => Target.raiseError[Expression](s"BUG: Returned node from generateUrlPathParams() was a ${x.getClass.getName}, not an Expression as expected") + } } yield { val method = new MethodDeclaration(util.EnumSet.of(PUBLIC), new VoidType, methodName) .setType(callBuilderType) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Scala.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Scala.scala index b37b7525e4..b1fa41e595 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Scala.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Scala.scala @@ -1,8 +1,8 @@ package com.twilio.guardrail.generators.syntax import cats.data.NonEmptyList -import com.twilio.guardrail.StaticDefns -import com.twilio.guardrail.generators.{ RawParameterName, ScalaParameter } +import com.twilio.guardrail.{StaticDefns, SwaggerUtil, Target} +import com.twilio.guardrail.generators.{RawParameterName, ScalaParameter} import com.twilio.guardrail.languages.ScalaLanguage import scala.meta._ @@ -46,4 +46,14 @@ object Scala { ..${staticDefns.definitions} } """ + + def generateUrlPathParams(path: String, pathArgs: List[ScalaParameter[ScalaLanguage]]): Target[Term] = + SwaggerUtil.paths.generateUrlPathParams[ScalaLanguage]( + path, + pathArgs, + Lit.String(_), + name => q"Formatter.addPath(${name})", + q"host + basePath", + (a, b) => q"${a} + ${b}" + ) } diff --git a/modules/codegen/src/test/scala/tests/core/PathParserSpec.scala b/modules/codegen/src/test/scala/tests/core/PathParserSpec.scala index 5ef00d6580..0c26501a1a 100644 --- a/modules/codegen/src/test/scala/tests/core/PathParserSpec.scala +++ b/modules/codegen/src/test/scala/tests/core/PathParserSpec.scala @@ -29,7 +29,7 @@ class PathParserSpec extends FunSuite with Matchers with EitherValues with Optio ).foreach { case (str, expected) => test(s"Client $str") { - val gen = Target.unsafeExtract(SwaggerUtil.paths.generateUrlPathParams(str, args)) + val gen = Target.unsafeExtract(generateUrlPathParams(str, args)) gen.toString shouldBe expected.toString } } From 060430d93fccf502f2b597b6a53ec13a871f878b Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Mon, 18 Mar 2019 12:11:26 -0700 Subject: [PATCH 84/86] Run sbt format --- .../main/scala/com/twilio/guardrail/CLI.scala | 4 +- .../scala/com/twilio/guardrail/Common.scala | 24 +- .../twilio/guardrail/ProtocolGenerator.scala | 27 +- .../twilio/guardrail/ServerGenerator.scala | 22 +- .../com/twilio/guardrail/SwaggerUtil.scala | 38 +- .../generators/EndpointsServerGenerator.scala | 14 +- .../Java/AsyncHttpClientClientGenerator.scala | 986 +++++++++++------- .../generators/Java/Dropwizard.scala | 15 +- .../generators/Java/DropwizardGenerator.scala | 8 +- .../Java/DropwizardServerGenerator.scala | 561 +++++----- .../generators/Java/JacksonGenerator.scala | 726 +++++++------ .../guardrail/generators/JavaGenerator.scala | 67 +- .../guardrail/generators/ScalaGenerator.scala | 29 +- .../guardrail/generators/ScalaParameter.scala | 10 +- .../guardrail/generators/syntax/Java.scala | 133 ++- .../guardrail/generators/syntax/Scala.scala | 4 +- .../guardrail/generators/syntax/package.scala | 2 +- .../protocol/terms/client/ClientTerm.scala | 2 +- .../protocol/terms/client/ClientTerms.scala | 2 +- .../terms/protocol/EnumProtocolTerm.scala | 13 +- .../terms/protocol/ModelProtocolTerms.scala | 5 +- .../terms/protocol/PolyProtocolTerm.scala | 10 +- .../protocol/terms/server/ServerTerm.scala | 2 +- .../protocol/terms/server/ServerTerms.scala | 8 +- 24 files changed, 1639 insertions(+), 1073 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala index 19f3090438..81f4aba6ae 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala @@ -7,7 +7,7 @@ import cats.~> import com.twilio.guardrail.core.CoreTermInterp import com.twilio.guardrail.terms.CoreTerm import com.twilio.swagger.core.{ LogLevel, LogLevels, StructuredLogger } -import com.twilio.guardrail.languages.{ LA, JavaLanguage, ScalaLanguage } +import com.twilio.guardrail.languages.{ JavaLanguage, LA, ScalaLanguage } import scala.io.AnsiColor @@ -137,7 +137,7 @@ trait CLICommon { val javaInterpreter: CoreTerm[JavaLanguage, ?] ~> CoreTarget val handleLanguage: PartialFunction[String, Array[String] => Unit] = { - case "java" => CLICommon.run(_)(javaInterpreter) + case "java" => CLICommon.run(_)(javaInterpreter) case "scala" => CLICommon.run(_)(scalaInterpreter) } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala index b35ec0b478..11e94f63d6 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/Common.scala @@ -6,13 +6,13 @@ import cats.free.Free import cats.implicits._ import cats.~> import com.twilio.guardrail.languages.LA -import com.twilio.guardrail.protocol.terms.protocol.{ArrayProtocolTerms, EnumProtocolTerms, ModelProtocolTerms, PolyProtocolTerms, ProtocolSupportTerms} +import com.twilio.guardrail.protocol.terms.protocol.{ ArrayProtocolTerms, EnumProtocolTerms, ModelProtocolTerms, PolyProtocolTerms, ProtocolSupportTerms } import com.twilio.guardrail.terms.framework.FrameworkTerms import com.twilio.guardrail.protocol.terms.client.ClientTerms import com.twilio.guardrail.protocol.terms.server.ServerTerms import com.twilio.guardrail.shims._ -import com.twilio.guardrail.terms.{CoreTerms, ScalaTerms, SwaggerTerms} -import java.nio.file.{Path, Paths} +import com.twilio.guardrail.terms.{ CoreTerms, ScalaTerms, SwaggerTerms } +import java.nio.file.{ Path, Paths } import java.util.Locale import scala.collection.JavaConverters._ import scala.io.AnsiColor @@ -116,7 +116,7 @@ object Common { extraTypes ) - frameworkImports <- getFrameworkImports(context.tracing) + frameworkImports <- getFrameworkImports(context.tracing) frameworkImplicits <- getFrameworkImplicits() frameworkImplicitName = frameworkImplicits.map(_._1) frameworkDefinitions <- getFrameworkDefinitions() @@ -124,11 +124,17 @@ object Common { files <- (clients.traverse(writeClient(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, _)), servers.flatTraverse(writeServer(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, _))).mapN(_ ++ _) - implicits <- renderImplicits(pkgPath, pkgName, frameworkImports, protocolImports, customImports) - frameworkImplicitsFile <- frameworkImplicits.fold(Free.pure[F, Option[WriteTree]](None))({ case (name, defn) => renderFrameworkImplicits(pkgPath, pkgName, frameworkImports, protocolImports, defn, name).map(Option.apply) }) - - frameworkDefinitionsFiles <- frameworkDefinitions.traverse({ case (name, defn) => renderFrameworkDefinitions(pkgPath, pkgName, frameworkImports, defn, name) }) - supportDefinitionsFiles <- supportDefinitions.traverse({ case SupportDefinition(name, imports, defn) => renderFrameworkDefinitions(pkgPath, pkgName, imports, defn, name) }) + implicits <- renderImplicits(pkgPath, pkgName, frameworkImports, protocolImports, customImports) + frameworkImplicitsFile <- frameworkImplicits.fold(Free.pure[F, Option[WriteTree]](None))({ + case (name, defn) => renderFrameworkImplicits(pkgPath, pkgName, frameworkImports, protocolImports, defn, name).map(Option.apply) + }) + + frameworkDefinitionsFiles <- frameworkDefinitions.traverse({ + case (name, defn) => renderFrameworkDefinitions(pkgPath, pkgName, frameworkImports, defn, name) + }) + supportDefinitionsFiles <- supportDefinitions.traverse({ + case SupportDefinition(name, imports, defn) => renderFrameworkDefinitions(pkgPath, pkgName, imports, defn, name) + }) } yield ( protocolDefinitions ++ diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala index 4bdd3c9659..1c4df9d8da 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala @@ -9,10 +9,10 @@ import com.twilio.guardrail.generators.syntax.RichString import com.twilio.guardrail.languages.LA import com.twilio.guardrail.protocol.terms.protocol._ import com.twilio.guardrail.terms.framework.FrameworkTerms -import com.twilio.guardrail.terms.{ScalaTerms, SwaggerTerms} +import com.twilio.guardrail.terms.{ ScalaTerms, SwaggerTerms } import java.util.Locale import scala.collection.JavaConverters._ -import scala.language.{higherKinds, postfixOps, reflectiveCalls} +import scala.language.{ higherKinds, postfixOps, reflectiveCalls } case class ProtocolDefinitions[L <: LA](elems: List[StrictProtocolElems[L]], protocolImports: List[L#Import], @@ -90,10 +90,10 @@ object ProtocolGenerator { val tpeName = Option(swagger.getType).filterNot(_ == "object").getOrElse("string") for { - enum <- extractEnum(swagger) + enum <- extractEnum(swagger) customTpeName <- SwaggerUtil.customTypeName(swagger) - tpe <- SwaggerUtil.typeName(tpeName, Option(swagger.getFormat()), customTpeName) - res <- enum.traverse(validProg(_, tpe)) + tpe <- SwaggerUtil.typeName(tpeName, Option(swagger.getFormat()), customTpeName) + res <- enum.traverse(validProg(_, tpe)) } yield res } @@ -276,12 +276,11 @@ object ProtocolGenerator { for { tpe <- model .flatMap(model => Option(model.getType)) - .fold[Free[F, L#Type]](objectType(None))(raw => - model - .flatTraverse(SwaggerUtil.customTypeName[L, F, ObjectSchema]) - .flatMap(customTypeName => - SwaggerUtil.typeName[L, F](raw, model.flatMap(f => Option(f.getFormat)), customTypeName) - ) + .fold[Free[F, L#Type]](objectType(None))( + raw => + model + .flatTraverse(SwaggerUtil.customTypeName[L, F, ObjectSchema]) + .flatMap(customTypeName => SwaggerUtil.typeName[L, F](raw, model.flatMap(f => Option(f.getFormat)), customTypeName)) ) res <- typeAlias[L, F](clsName, tpe) } yield res @@ -414,10 +413,10 @@ object ProtocolGenerator { case x => for { - tpeName <- getType(x) + tpeName <- getType(x) customTypeName <- SwaggerUtil.customTypeName(x) - tpe <- SwaggerUtil.typeName[L, F](tpeName, Option(x.getFormat()), customTypeName) - res <- typeAlias(clsName, tpe) + tpe <- SwaggerUtil.typeName[L, F](tpeName, Option(x.getFormat()), customTypeName) + res <- typeAlias(clsName, tpe) } yield res } } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala index 3b62fa9f43..3b81a5634e 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala @@ -4,12 +4,12 @@ import _root_.io.swagger.v3.oas.models._ import cats.free.Free import cats.instances.all._ import cats.syntax.all._ -import com.twilio.guardrail.generators.{Http4sHelper, ScalaParameter} +import com.twilio.guardrail.generators.{ Http4sHelper, ScalaParameter } import com.twilio.guardrail.languages.LA import com.twilio.guardrail.protocol.terms.server.ServerTerms import com.twilio.guardrail.shims._ import com.twilio.guardrail.terms.framework.FrameworkTerms -import com.twilio.guardrail.terms.{RouteMeta, ScalaTerms, SwaggerTerms} +import com.twilio.guardrail.terms.{ RouteMeta, ScalaTerms, SwaggerTerms } case class Servers[L <: LA](servers: List[Server[L]], supportDefinitions: List[SupportDefinition[L]]) case class Server[L <: LA](pkg: List[String], extraImports: List[L#Import], handlerDefinition: L#Definition, serverDefinitions: List[L#Definition]) @@ -44,7 +44,7 @@ object ServerGenerator { .groupBy(_._1) .mapValues(_.map(_._2)) .toList - extraImports <- getExtraImports(context.tracing) + extraImports <- getExtraImports(context.tracing) supportDefinitions <- generateSupportDefinitions(context.tracing) servers <- groupedRoutes.traverse { case (className, unsortedRoutes) => @@ -67,13 +67,15 @@ object ServerGenerator { renderedRoutes <- generateRoutes(resourceName, basePath, serverOperations, protocolElems) handlerSrc <- renderHandler(handlerName, renderedRoutes.methodSigs, renderedRoutes.handlerDefinitions) extraRouteParams <- getExtraRouteParams(context.tracing) - classSrc <- renderClass(resourceName, - handlerName, - renderedRoutes.classAnnotations, - renderedRoutes.routes, - extraRouteParams, - responseDefinitions.flatten, - renderedRoutes.supportDefinitions) + classSrc <- renderClass( + resourceName, + handlerName, + renderedRoutes.classAnnotations, + renderedRoutes.routes, + extraRouteParams, + responseDefinitions.flatten, + renderedRoutes.supportDefinitions + ) } yield { Server(className, frameworkImports ++ extraImports, handlerSrc, classSrc) } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala index e40b0dc3d6..1b2b575d7f 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/SwaggerUtil.scala @@ -6,19 +6,19 @@ import io.swagger.v3.oas.models.PathItem._ import io.swagger.v3.oas.models.media._ import io.swagger.v3.oas.models.parameters._ import io.swagger.v3.oas.models.responses._ -import cats.{FlatMap, Foldable} +import cats.{ FlatMap, Foldable } import cats.free.Free import cats.implicits._ import com.github.javaparser.ast.NodeList import com.github.javaparser.ast.expr._ -import com.twilio.guardrail.terms.{ScalaTerms, SwaggerTerms} +import com.twilio.guardrail.terms.{ ScalaTerms, SwaggerTerms } import com.twilio.guardrail.terms.framework.FrameworkTerms -import com.twilio.guardrail.extract.{Default, VendorExtension, extractFromNames} +import com.twilio.guardrail.extract.{ Default, VendorExtension, extractFromNames } import com.twilio.guardrail.extract.VendorExtension.VendorExtensible._ -import com.twilio.guardrail.generators.{Responses, ScalaParameter} -import com.twilio.guardrail.languages.{JavaLanguage, LA, ScalaLanguage} +import com.twilio.guardrail.generators.{ Responses, ScalaParameter } +import com.twilio.guardrail.languages.{ JavaLanguage, LA, ScalaLanguage } import com.twilio.guardrail.shims._ -import java.util.{Map => JMap} +import java.util.{ Map => JMap } import scala.language.reflectiveCalls import scala.meta._ import com.twilio.guardrail.protocol.terms.protocol.PropMeta @@ -137,11 +137,10 @@ object SwaggerUtil { } } - def customTypeName[L <: LA, F[_], A: VendorExtension.VendorExtensible](v: A)(implicit S: ScalaTerms[L, F]): Free[F, Option[String]] = { + def customTypeName[L <: LA, F[_], A: VendorExtension.VendorExtensible](v: A)(implicit S: ScalaTerms[L, F]): Free[F, Option[String]] = for { prefixes <- S.customTypePrefixes() } yield extractFromNames[String, A](prefixes.map(_ + "-type"), v) - } sealed class ModelMetaTypePartiallyApplied[L <: LA, F[_]](val dummy: Boolean = true) { def apply[T <: Schema[_]](model: T)(implicit Sc: ScalaTerms[L, F], Sw: SwaggerTerms[L, F], F: FrameworkTerms[L, F]): Free[F, ResolvedType[L]] = @@ -169,9 +168,9 @@ object SwaggerUtil { } yield res case impl: Schema[_] => for { - tpeName <- getType(impl) + tpeName <- getType(impl) customTpeName <- customTypeName(impl) - tpe <- typeName[L, F](tpeName, Option(impl.getFormat()), customTpeName) + tpe <- typeName[L, F](tpeName, Option(impl.getFormat()), customTpeName) } yield Resolved[L](tpe, None, None) }) } @@ -302,7 +301,7 @@ object SwaggerUtil { case b: BooleanSchema => for { customTpeName <- customTypeName(b) - res <- (typeName[L, F]("boolean", None, customTpeName), Default(b).extract[Boolean].traverse(litBoolean(_))).mapN(Resolved[L](_, None, _)) + res <- (typeName[L, F]("boolean", None, customTpeName), Default(b).extract[Boolean].traverse(litBoolean(_))).mapN(Resolved[L](_, None, _)) } yield res case s: StringSchema => @@ -315,25 +314,25 @@ object SwaggerUtil { case d: DateSchema => for { customTpeName <- customTypeName(d) - res <- typeName[L, F]("string", Some("date"), customTpeName).map(Resolved[L](_, None, None)) + res <- typeName[L, F]("string", Some("date"), customTpeName).map(Resolved[L](_, None, None)) } yield res case d: DateTimeSchema => for { customTpeName <- customTypeName(d) - res <- typeName[L, F]("string", Some("date-time"), customTpeName).map(Resolved[L](_, None, None)) + res <- typeName[L, F]("string", Some("date-time"), customTpeName).map(Resolved[L](_, None, None)) } yield res case i: IntegerSchema => for { customTpeName <- customTypeName(i) - res <- typeName[L, F]("integer", Option(i.getFormat), customTpeName).map(Resolved[L](_, None, None)) + res <- typeName[L, F]("integer", Option(i.getFormat), customTpeName).map(Resolved[L](_, None, None)) } yield res case d: NumberSchema => for { customTpeName <- customTypeName(d) - res <- typeName[L, F]("number", Option(d.getFormat), customTpeName).map(Resolved[L](_, None, None)) + res <- typeName[L, F]("number", Option(d.getFormat), customTpeName).map(Resolved[L](_, None, None)) } yield res case x => @@ -398,9 +397,7 @@ object SwaggerUtil { object paths { import atto._, Atto._ - private[this] def lookupName[L <: LA, T](bindingName: String, - pathArgs: List[ScalaParameter[L]]) - (f: ScalaParameter[L] => atto.Parser[T]): atto.Parser[T] = + private[this] def lookupName[L <: LA, T](bindingName: String, pathArgs: List[ScalaParameter[L]])(f: ScalaParameter[L] => atto.Parser[T]): atto.Parser[T] = pathArgs .find(_.argName.value == bindingName) .fold[atto.Parser[T]]( @@ -415,14 +412,13 @@ object SwaggerUtil { showLiteralPathComponent: String => L#Term, showInterpolatedPathComponent: L#TermName => L#Term, initialPathTerm: L#Term, - combinePathTerms: (L#Term, L#Term) => L#Term - ): Target[L#Term] = { + combinePathTerms: (L#Term, L#Term) => L#Term): Target[L#Term] = { val term: atto.Parser[L#Term] = variable.flatMap { binding => lookupName(binding, pathArgs) { param => ok(showInterpolatedPathComponent(param.paramName)) } } - val other: atto.Parser[String] = many1(notChar('{')).map(_.toList.mkString) + val other: atto.Parser[String] = many1(notChar('{')).map(_.toList.mkString) val pattern: atto.Parser[List[Either[String, L#Term]]] = many(either(term, other).map(_.swap: Either[String, L#Term])) for { diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsServerGenerator.scala index fe9be5de43..a161bf0fae 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsServerGenerator.scala @@ -8,14 +8,14 @@ import com.twilio.guardrail.protocol.terms.server._ object EndpointsServerGenerator { object ServerTermInterp extends FunctionK[ServerTerm[ScalaLanguage, ?], Target] { def apply[T](term: ServerTerm[ScalaLanguage, T]): Target[T] = term match { - case GenerateResponseDefinitions(operationId, responses, protocolElems) => ??? - case BuildTracingFields(operation, resourceName, tracing) => ??? - case GenerateRoutes(resourceName, basePath, routes, protocolElems) => ??? - case RenderHandler(handlerName, methodSigs, handlerDefinitions) => ??? - case GetExtraRouteParams(tracing) => ??? - case GenerateSupportDefinitions(tracing) => ??? + case GenerateResponseDefinitions(operationId, responses, protocolElems) => ??? + case BuildTracingFields(operation, resourceName, tracing) => ??? + case GenerateRoutes(resourceName, basePath, routes, protocolElems) => ??? + case RenderHandler(handlerName, methodSigs, handlerDefinitions) => ??? + case GetExtraRouteParams(tracing) => ??? + case GenerateSupportDefinitions(tracing) => ??? case RenderClass(resourceName, handlerName, annotations, combinedRouteTerms, extraRouteParams, responseDefinitions, supportDefinitions) => ??? - case GetExtraImports(tracing) => ??? + case GetExtraImports(tracing) => ??? } } } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index 3625bfe3fe..d189726d29 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -5,39 +5,39 @@ import cats.instances.list._ import cats.syntax.traverse._ import cats.~> import com.github.javaparser.JavaParser -import com.github.javaparser.ast.{ImportDeclaration, NodeList} +import com.github.javaparser.ast.{ ImportDeclaration, NodeList } import com.github.javaparser.ast.Modifier._ -import com.github.javaparser.ast.`type`.{ClassOrInterfaceType, Type, VoidType} +import com.github.javaparser.ast.`type`.{ ClassOrInterfaceType, Type, VoidType } import com.github.javaparser.ast.body._ -import com.github.javaparser.ast.expr.{MethodCallExpr, NameExpr, _} +import com.github.javaparser.ast.expr.{ MethodCallExpr, NameExpr, _ } import com.github.javaparser.ast.stmt._ -import com.twilio.guardrail.generators.{Response, ScalaParameter} +import com.twilio.guardrail.generators.{ Response, ScalaParameter } import com.twilio.guardrail.generators.syntax.Java._ import com.twilio.guardrail.languages.JavaLanguage import com.twilio.guardrail.protocol.terms.client._ import com.twilio.guardrail.terms.RouteMeta -import com.twilio.guardrail.{RenderedClientOperation, StaticDefns, SupportDefinition, SwaggerUtil, Target} +import com.twilio.guardrail.{ RenderedClientOperation, StaticDefns, SupportDefinition, SwaggerUtil, Target } import java.net.URI import java.util object AsyncHttpClientClientGenerator { - private val URI_TYPE = JavaParser.parseClassOrInterfaceType("URI") + private val URI_TYPE = JavaParser.parseClassOrInterfaceType("URI") private val DEFAULT_ASYNC_HTTP_CLIENT_CONFIG_BUILDER_TYPE = JavaParser.parseClassOrInterfaceType("DefaultAsyncHttpClientConfig.Builder") - private val DEFAULT_ASYNC_HTTP_CLIENT_TYPE = JavaParser.parseClassOrInterfaceType("DefaultAsyncHttpClient") - private val ASYNC_HTTP_CLIENT_TYPE = JavaParser.parseClassOrInterfaceType("AsyncHttpClient") - private val ASYNC_HTTP_CLIENT_CONFIG_TYPE = JavaParser.parseClassOrInterfaceType("AsyncHttpClientConfig") - private val REQUEST_BUILDER_TYPE = JavaParser.parseClassOrInterfaceType("RequestBuilder") - private val REQUEST_TYPE = JavaParser.parseClassOrInterfaceType("Request") - private val RESPONSE_TYPE = JavaParser.parseClassOrInterfaceType("Response") - private val FILE_PART_TYPE = JavaParser.parseClassOrInterfaceType("FilePart") - private val STRING_PART_TYPE = JavaParser.parseClassOrInterfaceType("StringPart") - private val OBJECT_MAPPER_TYPE = JavaParser.parseClassOrInterfaceType("ObjectMapper") - private val BUILDER_TYPE = JavaParser.parseClassOrInterfaceType("Builder") - private val MARSHALLING_EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("MarshallingException") - private val HTTP_ERROR_TYPE = JavaParser.parseClassOrInterfaceType("HttpError") - private val EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("Exception") - private val JSON_PROCESSING_EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("JsonProcessingException") - private val CLIENT_EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("ClientException") + private val DEFAULT_ASYNC_HTTP_CLIENT_TYPE = JavaParser.parseClassOrInterfaceType("DefaultAsyncHttpClient") + private val ASYNC_HTTP_CLIENT_TYPE = JavaParser.parseClassOrInterfaceType("AsyncHttpClient") + private val ASYNC_HTTP_CLIENT_CONFIG_TYPE = JavaParser.parseClassOrInterfaceType("AsyncHttpClientConfig") + private val REQUEST_BUILDER_TYPE = JavaParser.parseClassOrInterfaceType("RequestBuilder") + private val REQUEST_TYPE = JavaParser.parseClassOrInterfaceType("Request") + private val RESPONSE_TYPE = JavaParser.parseClassOrInterfaceType("Response") + private val FILE_PART_TYPE = JavaParser.parseClassOrInterfaceType("FilePart") + private val STRING_PART_TYPE = JavaParser.parseClassOrInterfaceType("StringPart") + private val OBJECT_MAPPER_TYPE = JavaParser.parseClassOrInterfaceType("ObjectMapper") + private val BUILDER_TYPE = JavaParser.parseClassOrInterfaceType("Builder") + private val MARSHALLING_EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("MarshallingException") + private val HTTP_ERROR_TYPE = JavaParser.parseClassOrInterfaceType("HttpError") + private val EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("Exception") + private val JSON_PROCESSING_EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("JsonProcessingException") + private val CLIENT_EXCEPTION_TYPE = JavaParser.parseClassOrInterfaceType("ClientException") private val HTTP_CLIENT_FUNCTION_TYPE = functionType(REQUEST_TYPE, completionStageType(RESPONSE_TYPE)) @@ -68,31 +68,41 @@ object AsyncHttpClientClientGenerator { private def generateBuilderMethodCall(param: ScalaParameter[JavaLanguage], builderMethodName: String, needsMultipart: Boolean): Statement = { val finalMethodName = if (needsMultipart) "addBodyPart" else builderMethodName - val argName = param.paramName.asString - val isList = param.argType.isNamed("List") + val argName = param.paramName.asString + val isList = param.argType.isNamed("List") val makeArgList: String => NodeList[Expression] = name => if (param.isFile) { - new NodeList[Expression](new ObjectCreationExpr(null, FILE_PART_TYPE, new NodeList( - new StringLiteralExpr(param.argName.value), - new NameExpr(name) - ))) + new NodeList[Expression]( + new ObjectCreationExpr(null, + FILE_PART_TYPE, + new NodeList( + new StringLiteralExpr(param.argName.value), + new NameExpr(name) + )) + ) } else if (needsMultipart) { - new NodeList[Expression](new ObjectCreationExpr(null, STRING_PART_TYPE, new NodeList( - new StringLiteralExpr(param.argName.value), - showParam(param, Some(name)) - ))) + new NodeList[Expression]( + new ObjectCreationExpr(null, + STRING_PART_TYPE, + new NodeList( + new StringLiteralExpr(param.argName.value), + showParam(param, Some(name)) + )) + ) } else { new NodeList[Expression](new StringLiteralExpr(param.argName.value), showParam(param, Some(name))) - } + } if (isList) { new ForEachStmt( new VariableDeclarationExpr(param.argType.containedType, "member", FINAL), new NameExpr(argName), - new BlockStmt(new NodeList( - new ExpressionStmt(new MethodCallExpr(new NameExpr("builder"), finalMethodName, makeArgList("member"))) - )) + new BlockStmt( + new NodeList( + new ExpressionStmt(new MethodCallExpr(new NameExpr("builder"), finalMethodName, makeArgList("member"))) + ) + ) ) } else { new ExpressionStmt(new MethodCallExpr(new NameExpr("builder"), finalMethodName, makeArgList(argName))) @@ -100,34 +110,49 @@ object AsyncHttpClientClientGenerator { } private def generateBodyMethodCall(param: ScalaParameter[JavaLanguage]): Statement = { - def wrapSetBody(expr: Expression): MethodCallExpr = { + def wrapSetBody(expr: Expression): MethodCallExpr = new MethodCallExpr(new NameExpr("builder"), "setBody", new NodeList[Expression](expr)) - } if (param.isFile) { - new ExpressionStmt(wrapSetBody(new ObjectCreationExpr(null, FILE_PART_TYPE, new NodeList( - new StringLiteralExpr(param.argName.value), - new NameExpr(param.paramName.asString) - )))) + new ExpressionStmt( + wrapSetBody( + new ObjectCreationExpr(null, + FILE_PART_TYPE, + new NodeList( + new StringLiteralExpr(param.argName.value), + new NameExpr(param.paramName.asString) + )) + ) + ) } else { new TryStmt( - new BlockStmt(new NodeList( - new ExpressionStmt(wrapSetBody(new MethodCallExpr( - new FieldAccessExpr(new ThisExpr, "objectMapper"), - "writeValueAsString", - new NodeList[Expression](new NameExpr(param.paramName.asString)) - ))) - )), + new BlockStmt( + new NodeList( + new ExpressionStmt( + wrapSetBody( + new MethodCallExpr( + new FieldAccessExpr(new ThisExpr, "objectMapper"), + "writeValueAsString", + new NodeList[Expression](new NameExpr(param.paramName.asString)) + ) + ) + ) + ) + ), new NodeList( new CatchClause( new Parameter(util.EnumSet.of(FINAL), JSON_PROCESSING_EXCEPTION_TYPE, new SimpleName("e")), - new BlockStmt(new NodeList( - new ThrowStmt(new ObjectCreationExpr( - null, - MARSHALLING_EXCEPTION_TYPE, - new NodeList(new MethodCallExpr(new NameExpr("e"), "getMessage"), new NameExpr("e")) - )) - )) + new BlockStmt( + new NodeList( + new ThrowStmt( + new ObjectCreationExpr( + null, + MARSHALLING_EXCEPTION_TYPE, + new NodeList(new MethodCallExpr(new NameExpr("e"), "getMessage"), new NameExpr("e")) + ) + ) + ) + ) ) ), null @@ -135,40 +160,50 @@ object AsyncHttpClientClientGenerator { } } - private def jacksonTypeReferenceFor(tpe: Type): Expression = { + private def jacksonTypeReferenceFor(tpe: Type): Expression = tpe match { case cls: ClassOrInterfaceType if cls.getTypeArguments.isPresent => new ObjectCreationExpr(null, typeReferenceType(cls), new NodeList).setAnonymousClassBody(new NodeList) case other => new ClassExpr(other) } - } - private def generateClientExceptionClasses(): Target[List[(List[ImportDeclaration], ClassOrInterfaceDeclaration)]] = { + private def generateClientExceptionClasses(): Target[List[(List[ImportDeclaration], ClassOrInterfaceDeclaration)]] = for { httpErrorImports <- List( "org.asynchttpclient.Response" ).traverse(safeParseRawImport) } yield { def addStdConstructors(cls: ClassOrInterfaceDeclaration): Unit = { - cls.addConstructor(PUBLIC) + cls + .addConstructor(PUBLIC) .addParameter(new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("message"))) - .setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new MethodCallExpr("super", new NameExpr("message"))) - ))) - - cls.addConstructor(PUBLIC) - .setParameters(new NodeList( - new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("message")), - new Parameter(util.EnumSet.of(FINAL), THROWABLE_TYPE, new SimpleName("cause")) - )) - .setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new MethodCallExpr("super", new NameExpr("message"), new NameExpr("cause"))) - ))) + .setBody( + new BlockStmt( + new NodeList( + new ExpressionStmt(new MethodCallExpr("super", new NameExpr("message"))) + ) + ) + ) + + cls + .addConstructor(PUBLIC) + .setParameters( + new NodeList( + new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("message")), + new Parameter(util.EnumSet.of(FINAL), THROWABLE_TYPE, new SimpleName("cause")) + ) + ) + .setBody( + new BlockStmt( + new NodeList( + new ExpressionStmt(new MethodCallExpr("super", new NameExpr("message"), new NameExpr("cause"))) + ) + ) + ) } - def addNoSerialVersionUuid(cls: ClassOrInterfaceDeclaration): Unit = { + def addNoSerialVersionUuid(cls: ClassOrInterfaceDeclaration): Unit = cls.addSingleMemberAnnotation("SuppressWarnings", new StringLiteralExpr("serial")) - } val clientExceptionClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, ABSTRACT), false, "ClientException") .addExtendedType("RuntimeException") @@ -185,29 +220,43 @@ object AsyncHttpClientClientGenerator { addNoSerialVersionUuid(httpErrorClass) httpErrorClass.addField(RESPONSE_TYPE, "response", PRIVATE, FINAL) - httpErrorClass.addConstructor(PUBLIC) + httpErrorClass + .addConstructor(PUBLIC) .addParameter(new Parameter(util.EnumSet.of(FINAL), RESPONSE_TYPE, new SimpleName("response"))) - .setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new MethodCallExpr( - "super", - new BinaryExpr( - new StringLiteralExpr("HTTP server responded with status "), - new MethodCallExpr(new NameExpr("response"), "getStatusCode"), - BinaryExpr.Operator.PLUS + .setBody( + new BlockStmt( + new NodeList( + new ExpressionStmt( + new MethodCallExpr( + "super", + new BinaryExpr( + new StringLiteralExpr("HTTP server responded with status "), + new MethodCallExpr(new NameExpr("response"), "getStatusCode"), + BinaryExpr.Operator.PLUS + ) + ) + ), + new ExpressionStmt( + new AssignExpr( + new FieldAccessExpr(new ThisExpr, "response"), + new NameExpr("response"), + AssignExpr.Operator.ASSIGN + ) + ) ) - )), - new ExpressionStmt(new AssignExpr( - new FieldAccessExpr(new ThisExpr, "response"), - new NameExpr("response"), - AssignExpr.Operator.ASSIGN - )) - ))) - - httpErrorClass.addMethod("getResponse", PUBLIC) + ) + ) + + httpErrorClass + .addMethod("getResponse", PUBLIC) .setType(RESPONSE_TYPE) - .setBody(new BlockStmt(new NodeList( - new ReturnStmt(new FieldAccessExpr(new ThisExpr, "response")) - ))) + .setBody( + new BlockStmt( + new NodeList( + new ReturnStmt(new FieldAccessExpr(new ThisExpr, "response")) + ) + ) + ) List( (List.empty, clientExceptionClass), @@ -215,9 +264,8 @@ object AsyncHttpClientClientGenerator { (httpErrorImports, httpErrorClass) ) } - } - def generateAsyncHttpClientSupportClass(): Target[(List[ImportDeclaration], ClassOrInterfaceDeclaration)] = { + def generateAsyncHttpClientSupportClass(): Target[(List[ImportDeclaration], ClassOrInterfaceDeclaration)] = for { imports <- List( "java.util.concurrent.CompletionStage", @@ -242,37 +290,53 @@ object AsyncHttpClientClientGenerator { ("setReadTimeout", 3000), ("setMaxConnections", 1024), ("setMaxConnectionsPerHost", 1024) - ).foldLeft[Expression](ahcConfigBuilder)({ case (lastExpr, (name, arg)) => - new MethodCallExpr(lastExpr, name, new NodeList[Expression](new IntegerLiteralExpr(arg))) - }), "build", new NodeList[Expression]() + ).foldLeft[Expression](ahcConfigBuilder)({ + case (lastExpr, (name, arg)) => + new MethodCallExpr(lastExpr, name, new NodeList[Expression](new IntegerLiteralExpr(arg))) + }), + "build", + new NodeList[Expression]() ) - cls.addMethod("createDefaultAsyncHttpClient", PUBLIC, STATIC) + cls + .addMethod("createDefaultAsyncHttpClient", PUBLIC, STATIC) .setType(ASYNC_HTTP_CLIENT_TYPE) - .setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new VariableDeclarationExpr(new VariableDeclarator(ASYNC_HTTP_CLIENT_CONFIG_TYPE, "config", ahcConfig), FINAL)), - new ReturnStmt( - new ObjectCreationExpr(null, DEFAULT_ASYNC_HTTP_CLIENT_TYPE, new NodeList(new NameExpr("config"))) + .setBody( + new BlockStmt( + new NodeList( + new ExpressionStmt(new VariableDeclarationExpr(new VariableDeclarator(ASYNC_HTTP_CLIENT_CONFIG_TYPE, "config", ahcConfig), FINAL)), + new ReturnStmt( + new ObjectCreationExpr(null, DEFAULT_ASYNC_HTTP_CLIENT_TYPE, new NodeList(new NameExpr("config"))) + ) + ) ) - ))) - + ) - cls.addMethod("createHttpClient", PUBLIC, STATIC) + cls + .addMethod("createHttpClient", PUBLIC, STATIC) .setType(HTTP_CLIENT_FUNCTION_TYPE) .addParameter(new Parameter(util.EnumSet.of(FINAL), ASYNC_HTTP_CLIENT_TYPE, new SimpleName("client"))) - .setBody(new BlockStmt(new NodeList( - new ReturnStmt(new LambdaExpr( - new NodeList(new Parameter(util.EnumSet.of(FINAL), REQUEST_TYPE, new SimpleName("request"))), - new ExpressionStmt(new MethodCallExpr(new MethodCallExpr(new NameExpr("client"), "executeRequest", new NodeList[Expression](new NameExpr("request"))), "toCompletableFuture")), - true - )) - ))) + .setBody( + new BlockStmt( + new NodeList( + new ReturnStmt( + new LambdaExpr( + new NodeList(new Parameter(util.EnumSet.of(FINAL), REQUEST_TYPE, new SimpleName("request"))), + new ExpressionStmt( + new MethodCallExpr(new MethodCallExpr(new NameExpr("client"), "executeRequest", new NodeList[Expression](new NameExpr("request"))), + "toCompletableFuture") + ), + true + ) + ) + ) + ) + ) (imports, cls) } - } - def generateJacksonSupportClass(): Target[(List[ImportDeclaration], ClassOrInterfaceDeclaration)] = { + def generateJacksonSupportClass(): Target[(List[ImportDeclaration], ClassOrInterfaceDeclaration)] = for { imports <- List( "com.fasterxml.jackson.databind.ObjectMapper", @@ -283,52 +347,62 @@ object AsyncHttpClientClientGenerator { val cls = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, ABSTRACT), false, "JacksonSupport") cls.addConstructor(PRIVATE) - cls.addMethod("configureObjectMapper", PUBLIC, STATIC) + cls + .addMethod("configureObjectMapper", PUBLIC, STATIC) .setType(OBJECT_MAPPER_TYPE) .addParameter(new Parameter(util.EnumSet.of(FINAL), OBJECT_MAPPER_TYPE, new SimpleName("objectMapper"))) - .setBody(new BlockStmt(new NodeList( - List("JavaTimeModule", "Jdk8Module").map(name => - new ExpressionStmt(new MethodCallExpr( - new NameExpr("objectMapper"), - "registerModule", - new NodeList[Expression](new ObjectCreationExpr(null, JavaParser.parseClassOrInterfaceType(name), new NodeList())) - )) - ) :+ - new ReturnStmt(new NameExpr("objectMapper")): _* - ))) + .setBody( + new BlockStmt( + new NodeList( + List("JavaTimeModule", "Jdk8Module").map( + name => + new ExpressionStmt( + new MethodCallExpr( + new NameExpr("objectMapper"), + "registerModule", + new NodeList[Expression](new ObjectCreationExpr(null, JavaParser.parseClassOrInterfaceType(name), new NodeList())) + ) + ) + ) :+ + new ReturnStmt(new NameExpr("objectMapper")): _* + ) + ) + ) (imports, cls) } - } object ClientTermInterp extends (ClientTerm[JavaLanguage, ?] ~> Target) { def apply[T](term: ClientTerm[JavaLanguage, T]): Target[T] = term match { case GenerateClientOperation(_, RouteMeta(pathStr, httpMethod, operation), methodName, tracing, parameters, responses) => val responseParentName = s"${operation.getOperationId.capitalize}Response" - val callBuilderName = s"${operation.getOperationId.capitalize}CallBuilder" + val callBuilderName = s"${operation.getOperationId.capitalize}CallBuilder" for { responseParentType <- safeParseClassOrInterfaceType(responseParentName) - callBuilderType <- safeParseClassOrInterfaceType(callBuilderName) + callBuilderType <- safeParseClassOrInterfaceType(callBuilderName) pathExprNode <- SwaggerUtil.paths.generateUrlPathParams[JavaLanguage]( pathStr, parameters.pathParams, new StringLiteralExpr(_), - name => new MethodCallExpr( - new MethodCallExpr(new NameExpr("Shower"), "getInstance"), - "show", - new NodeList[Expression](new NameExpr(name.asString)) + name => + new MethodCallExpr( + new MethodCallExpr(new NameExpr("Shower"), "getInstance"), + "show", + new NodeList[Expression](new NameExpr(name.asString)) ), new MethodCallExpr(new FieldAccessExpr(new ThisExpr, "baseUrl"), "toString"), - (a, b) => (a, b) match { - case (ae: Expression, be: Expression) => new BinaryExpr(ae, be, BinaryExpr.Operator.PLUS) - case _ => a + (a, b) => + (a, b) match { + case (ae: Expression, be: Expression) => new BinaryExpr(ae, be, BinaryExpr.Operator.PLUS) + case _ => a } ) pathExpr <- pathExprNode match { case e: Expression => Target.pure(e) - case x => Target.raiseError[Expression](s"BUG: Returned node from generateUrlPathParams() was a ${x.getClass.getName}, not an Expression as expected") + case x => + Target.raiseError[Expression](s"BUG: Returned node from generateUrlPathParams() was a ${x.getClass.getName}, not an Expression as expected") } } yield { val method = new MethodDeclaration(util.EnumSet.of(PUBLIC), new VoidType, methodName) @@ -344,14 +418,17 @@ object AsyncHttpClientClientGenerator { ).filter(_.required).map(_.param).foreach(method.addParameter) val requestBuilder = new MethodCallExpr( - new AssignExpr(new VariableDeclarationExpr( - new VariableDeclarator(REQUEST_BUILDER_TYPE, "builder"), FINAL), - new ObjectCreationExpr(null, REQUEST_BUILDER_TYPE, new NodeList[Expression]( - new StringLiteralExpr(httpMethod.toString) - )), + new AssignExpr( + new VariableDeclarationExpr(new VariableDeclarator(REQUEST_BUILDER_TYPE, "builder"), FINAL), + new ObjectCreationExpr(null, + REQUEST_BUILDER_TYPE, + new NodeList[Expression]( + new StringLiteralExpr(httpMethod.toString) + )), AssignExpr.Operator.ASSIGN ), - "setUrl", new NodeList[Expression](pathExpr) + "setUrl", + new NodeList[Expression](pathExpr) ) val builderParamsMethodNames = List( @@ -361,22 +438,29 @@ object AsyncHttpClientClientGenerator { ) val builderMethodCalls: List[(ScalaParameter[JavaLanguage], Statement)] = builderParamsMethodNames - .flatMap({ case (params, name, needsMultipart) => - params.map(param => (param, generateBuilderMethodCall(param, name, needsMultipart))) + .flatMap({ + case (params, name, needsMultipart) => + params.map(param => (param, generateBuilderMethodCall(param, name, needsMultipart))) }) ++ parameters.bodyParams.map(param => (param, generateBodyMethodCall(param))) - val callBuilderCreation = new ObjectCreationExpr(null, callBuilderType, new NodeList( - new NameExpr("builder"), - new FieldAccessExpr(new ThisExpr, "httpClient"), - new FieldAccessExpr(new ThisExpr, "objectMapper") - )) - - method.setBody(new BlockStmt(new NodeList( - new ExpressionStmt(requestBuilder) +: - builderMethodCalls.filter(_._1.required).map(_._2) :+ - new ReturnStmt(callBuilderCreation): _* - ))) + val callBuilderCreation = new ObjectCreationExpr(null, + callBuilderType, + new NodeList( + new NameExpr("builder"), + new FieldAccessExpr(new ThisExpr, "httpClient"), + new FieldAccessExpr(new ThisExpr, "objectMapper") + )) + + method.setBody( + new BlockStmt( + new NodeList( + new ExpressionStmt(requestBuilder) +: + builderMethodCalls.filter(_._1.required).map(_._2) :+ + new ReturnStmt(callBuilderCreation): _* + ) + ) + ) val callBuilderFinalFields = List( (REQUEST_BUILDER_TYPE, "builder"), @@ -387,91 +471,177 @@ object AsyncHttpClientClientGenerator { val callBuilderCls = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, STATIC), false, callBuilderName) callBuilderFinalFields.foreach({ case (tpe, name) => callBuilderCls.addField(tpe, name, PRIVATE, FINAL) }) - callBuilderCls.addConstructor(PRIVATE) + callBuilderCls + .addConstructor(PRIVATE) .setParameters(callBuilderFinalFields.map({ case (tpe, name) => new Parameter(util.EnumSet.of(FINAL), tpe, new SimpleName(name)) }).toNodeList) - .setBody(new BlockStmt( - callBuilderFinalFields.map[Statement, List[Statement]]({ case (_, name) => - new ExpressionStmt(new AssignExpr(new FieldAccessExpr(new ThisExpr, name), new NameExpr(name), AssignExpr.Operator.ASSIGN)) - }).toNodeList - )) + .setBody( + new BlockStmt( + callBuilderFinalFields + .map[Statement, List[Statement]]({ + case (_, name) => + new ExpressionStmt(new AssignExpr(new FieldAccessExpr(new ThisExpr, name), new NameExpr(name), AssignExpr.Operator.ASSIGN)) + }) + .toNodeList + ) + ) val optionalParamMethods = builderMethodCalls .filterNot(_._1.required) - .map({ case (ScalaParameter(_, param, _, _, argType), methodCall) => - new MethodDeclaration(util.EnumSet.of(PUBLIC), s"with${param.getNameAsString.unescapeReservedWord.capitalize}", callBuilderType, List( - new Parameter(util.EnumSet.of(FINAL), argType.containedType.unbox, new SimpleName(param.getNameAsString)) - ).toNodeList).setBody(new BlockStmt(List( - methodCall, - new ReturnStmt(new ThisExpr) - ).toNodeList)) + .map({ + case (ScalaParameter(_, param, _, _, argType), methodCall) => + new MethodDeclaration( + util.EnumSet.of(PUBLIC), + s"with${param.getNameAsString.unescapeReservedWord.capitalize}", + callBuilderType, + List( + new Parameter(util.EnumSet.of(FINAL), argType.containedType.unbox, new SimpleName(param.getNameAsString)) + ).toNodeList + ).setBody( + new BlockStmt( + List( + methodCall, + new ReturnStmt(new ThisExpr) + ).toNodeList + ) + ) }) optionalParamMethods.foreach(callBuilderCls.addMember) - callBuilderCls.addMethod("withHeader", PUBLIC) - .setParameters(List( - new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("name")), - new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("value")), - ).toNodeList) + callBuilderCls + .addMethod("withHeader", PUBLIC) + .setParameters( + List( + new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("name")), + new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("value")), + ).toNodeList + ) .setType(callBuilderType) - .setBody(new BlockStmt(List( - new ExpressionStmt(new MethodCallExpr( - new FieldAccessExpr(new ThisExpr, "builder"), - "addHeader", - List[Expression](new NameExpr("name"), new NameExpr("value")).toNodeList - )), - new ReturnStmt(new ThisExpr) - ).toNodeList)) + .setBody( + new BlockStmt( + List( + new ExpressionStmt( + new MethodCallExpr( + new FieldAccessExpr(new ThisExpr, "builder"), + "addHeader", + List[Expression](new NameExpr("name"), new NameExpr("value")).toNodeList + ) + ), + new ReturnStmt(new ThisExpr) + ).toNodeList + ) + ) val httpMethodCallExpr = new MethodCallExpr( new FieldAccessExpr(new ThisExpr, "httpClient"), "apply", new NodeList[Expression](new MethodCallExpr(new NameExpr("builder"), "build")) ) - val requestCall = new MethodCallExpr(httpMethodCallExpr, "thenApply", new NodeList[Expression]( - new LambdaExpr(new NodeList(new Parameter(util.EnumSet.of(FINAL), RESPONSE_TYPE, new SimpleName("response"))), new BlockStmt(new NodeList( - new SwitchStmt(new MethodCallExpr(new NameExpr("response"), "getStatusCode"), new NodeList( - responses.value.map(response => new SwitchEntryStmt(new IntegerLiteralExpr(response.statusCode), new NodeList(response.value match { - case None => new ReturnStmt(new ObjectCreationExpr(null, JavaParser.parseClassOrInterfaceType(s"${responseParentName}.${response.statusCodeName.asString}"), new NodeList())) - case Some((valueType, _)) => new TryStmt( - new BlockStmt(new NodeList( - new ExpressionStmt(new AssignExpr( - new VariableDeclarationExpr(new VariableDeclarator(valueType, "result"), FINAL), - new MethodCallExpr( - new FieldAccessExpr(new ThisExpr, "objectMapper"), - "readValue", - new NodeList[Expression]( - new MethodCallExpr(new NameExpr("response"), "getResponseBodyAsStream"), - jacksonTypeReferenceFor(valueType) + val requestCall = new MethodCallExpr( + httpMethodCallExpr, + "thenApply", + new NodeList[Expression]( + new LambdaExpr( + new NodeList(new Parameter(util.EnumSet.of(FINAL), RESPONSE_TYPE, new SimpleName("response"))), + new BlockStmt( + new NodeList( + new SwitchStmt( + new MethodCallExpr(new NameExpr("response"), "getStatusCode"), + new NodeList( + responses.value.map( + response => + new SwitchEntryStmt( + new IntegerLiteralExpr(response.statusCode), + new NodeList(response.value match { + case None => + new ReturnStmt( + new ObjectCreationExpr(null, + JavaParser.parseClassOrInterfaceType(s"${responseParentName}.${response.statusCodeName.asString}"), + new NodeList()) + ) + case Some((valueType, _)) => + new TryStmt( + new BlockStmt( + new NodeList( + new ExpressionStmt( + new AssignExpr( + new VariableDeclarationExpr(new VariableDeclarator(valueType, "result"), FINAL), + new MethodCallExpr( + new FieldAccessExpr(new ThisExpr, "objectMapper"), + "readValue", + new NodeList[Expression]( + new MethodCallExpr(new NameExpr("response"), "getResponseBodyAsStream"), + jacksonTypeReferenceFor(valueType) + ) + ), + AssignExpr.Operator.ASSIGN + ) + ), + new IfStmt( + new BinaryExpr(new NameExpr("result"), new NullLiteralExpr, BinaryExpr.Operator.EQUALS), + new BlockStmt( + new NodeList( + new ThrowStmt( + new ObjectCreationExpr(null, + MARSHALLING_EXCEPTION_TYPE, + new NodeList(new StringLiteralExpr("Failed to unmarshal response"))) + ) + ) + ), + new BlockStmt( + new NodeList( + new ReturnStmt( + new ObjectCreationExpr( + null, + JavaParser.parseClassOrInterfaceType(s"${responseParentName}.${response.statusCodeName.asString}"), + new NodeList[Expression](new NameExpr("result")) + ) + ) + ) + ) + ) + ) + ), + new NodeList( + new CatchClause( + new Parameter(util.EnumSet.of(FINAL), MARSHALLING_EXCEPTION_TYPE, new SimpleName("e")), + new BlockStmt( + new NodeList( + new ThrowStmt(new NameExpr("e")) + ) + ) + ), + new CatchClause( + new Parameter(util.EnumSet.of(FINAL), EXCEPTION_TYPE, new SimpleName("e")), + new BlockStmt( + new NodeList( + new ThrowStmt( + new ObjectCreationExpr(null, + MARSHALLING_EXCEPTION_TYPE, + new NodeList(new MethodCallExpr(new NameExpr("e"), "getMessage"), new NameExpr("e"))) + ) + ) + ) + ) + ), + null + ) + }) ) - ), AssignExpr.Operator.ASSIGN)), - new IfStmt( - new BinaryExpr(new NameExpr("result"), new NullLiteralExpr, BinaryExpr.Operator.EQUALS), - new BlockStmt(new NodeList(new ThrowStmt(new ObjectCreationExpr(null, MARSHALLING_EXCEPTION_TYPE, new NodeList(new StringLiteralExpr("Failed to unmarshal response")))))), - new BlockStmt(new NodeList(new ReturnStmt(new ObjectCreationExpr(null, JavaParser.parseClassOrInterfaceType(s"${responseParentName}.${response.statusCodeName.asString}"), new NodeList[Expression](new NameExpr("result")))))) - ) - )), - new NodeList( - new CatchClause( - new Parameter(util.EnumSet.of(FINAL), MARSHALLING_EXCEPTION_TYPE, new SimpleName("e")), - new BlockStmt(new NodeList( - new ThrowStmt(new NameExpr("e")) - )) - ), - new CatchClause( - new Parameter(util.EnumSet.of(FINAL), EXCEPTION_TYPE, new SimpleName("e")), - new BlockStmt(new NodeList( - new ThrowStmt(new ObjectCreationExpr(null, MARSHALLING_EXCEPTION_TYPE, new NodeList(new MethodCallExpr(new NameExpr("e"), "getMessage"), new NameExpr("e")))) - )) + ) :+ new SwitchEntryStmt( + null, + new NodeList(new ThrowStmt(new ObjectCreationExpr(null, HTTP_ERROR_TYPE, new NodeList(new NameExpr("response"))))) + ): _* ) - ), - null + ) ) - }))) :+ new SwitchEntryStmt(null, new NodeList(new ThrowStmt(new ObjectCreationExpr(null, HTTP_ERROR_TYPE, new NodeList(new NameExpr("response")))))): _* - )) - )), true) - )) + ), + true + ) + ) + ) - callBuilderCls.addMethod("call", PUBLIC) + callBuilderCls + .addMethod("call", PUBLIC) .setType(completionStageType(responseParentType)) .addThrownException(CLIENT_EXCEPTION_TYPE) .setBody(new BlockStmt(List[Statement](new ReturnStmt(requestCall)).toNodeList)) @@ -510,58 +680,76 @@ object AsyncHttpClientClientGenerator { case GenerateResponseDefinitions(operationId, responses, protocolElems) => val abstractClassName = s"${operationId.capitalize}Response" - val genericTypeParam = JavaParser.parseClassOrInterfaceType("T") - - val responseData = responses.value.map({ case Response(statusCodeName, valueType) => - val responseName: String = statusCodeName.asString - val responseType = JavaParser.parseClassOrInterfaceType(responseName) - val responseLambdaName = s"handle${responseName}" - - val responseInnerClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, STATIC), false, responseName); - responseInnerClass.addExtendedType(abstractClassName) - val (foldMethodParamType, foldMethodApplier, foldMethodArgs) = valueType.fold( - (supplierType(genericTypeParam), "get", new NodeList[Expression]()) - )({ vt => - val finalValueType: Type = vt.unbox - - responseInnerClass.addField(finalValueType, "value", PRIVATE, FINAL) - - val constructor = responseInnerClass.addConstructor(PUBLIC) - constructor.addParameter(new Parameter(util.EnumSet.of(FINAL), finalValueType, new SimpleName("value"))) - constructor.setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new AssignExpr( - new FieldAccessExpr(new ThisExpr, "value"), - new NameExpr("value"), - AssignExpr.Operator.ASSIGN - )) - ))) - - val getValueMethod = responseInnerClass.addMethod("getValue", PUBLIC) - getValueMethod.setType(finalValueType) - getValueMethod.setBody(new BlockStmt(new NodeList( - new ReturnStmt(new FieldAccessExpr(new ThisExpr, "value")) - ))) - - (functionType(vt, genericTypeParam), "apply", new NodeList[Expression]( - new MethodCallExpr(new EnclosedExpr(new CastExpr(responseType, new ThisExpr)), "getValue") - )) - }) + val genericTypeParam = JavaParser.parseClassOrInterfaceType("T") + + val responseData = responses.value.map({ + case Response(statusCodeName, valueType) => + val responseName: String = statusCodeName.asString + val responseType = JavaParser.parseClassOrInterfaceType(responseName) + val responseLambdaName = s"handle${responseName}" + + val responseInnerClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, STATIC), false, responseName); + responseInnerClass.addExtendedType(abstractClassName) + val (foldMethodParamType, foldMethodApplier, foldMethodArgs) = valueType.fold( + (supplierType(genericTypeParam), "get", new NodeList[Expression]()) + )({ + vt => + val finalValueType: Type = vt.unbox + + responseInnerClass.addField(finalValueType, "value", PRIVATE, FINAL) + + val constructor = responseInnerClass.addConstructor(PUBLIC) + constructor.addParameter(new Parameter(util.EnumSet.of(FINAL), finalValueType, new SimpleName("value"))) + constructor.setBody( + new BlockStmt( + new NodeList( + new ExpressionStmt( + new AssignExpr( + new FieldAccessExpr(new ThisExpr, "value"), + new NameExpr("value"), + AssignExpr.Operator.ASSIGN + ) + ) + ) + ) + ) - val foldMethodParameter = new Parameter(util.EnumSet.of(FINAL), foldMethodParamType, new SimpleName(responseLambdaName)) - - val foldMethodBranch = new IfStmt( - new InstanceOfExpr(new ThisExpr, responseType), - new BlockStmt(new NodeList( - new ReturnStmt(new MethodCallExpr( - new NameExpr(responseLambdaName), - foldMethodApplier, - foldMethodArgs - )) - )), - null - ) + val getValueMethod = responseInnerClass.addMethod("getValue", PUBLIC) + getValueMethod.setType(finalValueType) + getValueMethod.setBody( + new BlockStmt( + new NodeList( + new ReturnStmt(new FieldAccessExpr(new ThisExpr, "value")) + ) + ) + ) - (responseInnerClass, foldMethodParameter, foldMethodBranch) + (functionType(vt, genericTypeParam), + "apply", + new NodeList[Expression]( + new MethodCallExpr(new EnclosedExpr(new CastExpr(responseType, new ThisExpr)), "getValue") + )) + }) + + val foldMethodParameter = new Parameter(util.EnumSet.of(FINAL), foldMethodParamType, new SimpleName(responseLambdaName)) + + val foldMethodBranch = new IfStmt( + new InstanceOfExpr(new ThisExpr, responseType), + new BlockStmt( + new NodeList( + new ReturnStmt( + new MethodCallExpr( + new NameExpr(responseLambdaName), + foldMethodApplier, + foldMethodArgs + ) + ) + ) + ), + null + ) + + (responseInnerClass, foldMethodParameter, foldMethodBranch) }) val abstractResponseClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, STATIC, ABSTRACT), false, abstractClassName) @@ -575,33 +763,38 @@ object AsyncHttpClientClientGenerator { foldMethod.setType("T") foldMethodParameters.foreach(foldMethod.addParameter) - NonEmptyList.fromList(foldMethodIfBranches).foreach({ nel => - nel.reduceLeft({ (prev, next) => - prev.setElseStmt(next) - next - }) + NonEmptyList + .fromList(foldMethodIfBranches) + .foreach({ nel => + nel.reduceLeft({ (prev, next) => + prev.setElseStmt(next) + next + }) - nel.last.setElseStmt(new BlockStmt(new NodeList( - new ThrowStmt(new ObjectCreationExpr(null, ASSERTION_ERROR_TYPE, - new NodeList(new StringLiteralExpr("This is a bug in guardrail!"))) + nel.last.setElseStmt( + new BlockStmt( + new NodeList( + new ThrowStmt(new ObjectCreationExpr(null, ASSERTION_ERROR_TYPE, new NodeList(new StringLiteralExpr("This is a bug in guardrail!")))) + ) + ) ) - ))) - foldMethod.setBody(new BlockStmt(new NodeList(nel.head))) - }) + foldMethod.setBody(new BlockStmt(new NodeList(nel.head))) + }) Target.pure(List(abstractResponseClass)) case GenerateSupportDefinitions(tracing) => for { exceptionClasses <- generateClientExceptionClasses() - ahcSupport <- generateAsyncHttpClientSupportClass() + ahcSupport <- generateAsyncHttpClientSupportClass() (ahcSupportImports, ahcSupportClass) = ahcSupport jacksonSupport <- generateJacksonSupportClass() (jacksonSupportImports, jacksonSupportClass) = jacksonSupport } yield { - exceptionClasses.map({ case (imports, cls) => - SupportDefinition[JavaLanguage](new Name(cls.getNameAsString), imports, cls) + exceptionClasses.map({ + case (imports, cls) => + SupportDefinition[JavaLanguage](new Name(cls.getNameAsString), imports, cls) }) ++ List( SupportDefinition[JavaLanguage](new Name(ahcSupportClass.getNameAsString), ahcSupportImports, ahcSupportClass), SupportDefinition[JavaLanguage](new Name(jacksonSupportClass.getNameAsString), jacksonSupportImports, jacksonSupportClass), @@ -622,18 +815,21 @@ object AsyncHttpClientClientGenerator { val clientType = JavaParser.parseClassOrInterfaceType(clientName) val builderClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, STATIC), false, "Builder") - val serverUrl = serverUrls.map(_.head).map(uri => new URI(uri.toString + basePath.getOrElse(""))) + val serverUrl = serverUrls.map(_.head).map(uri => new URI(uri.toString + basePath.getOrElse(""))) val baseUrlField = builderClass.addField(URI_TYPE, "baseUrl", PRIVATE, FINAL) serverUrl.foreach({ serverUrl => builderClass.addFieldWithInitializer( - URI_TYPE, "DEFAULT_BASE_URL", + URI_TYPE, + "DEFAULT_BASE_URL", new MethodCallExpr( new NameExpr("URI"), "create", new NodeList[Expression](new StringLiteralExpr(serverUrl.toString)) ), - PRIVATE, STATIC, FINAL + PRIVATE, + STATIC, + FINAL ) baseUrlField.setFinal(false) baseUrlField.getVariables.iterator().next().setInitializer(new NameExpr("DEFAULT_BASE_URL")) @@ -643,9 +839,12 @@ object AsyncHttpClientClientGenerator { val clientNameField = builderClass.addField(STRING_TYPE, "clientName", PRIVATE, FINAL) tracingName.foreach({ tracingName => builderClass.addFieldWithInitializer( - STRING_TYPE, "DEFAULT_CLIENT_NAME", + STRING_TYPE, + "DEFAULT_CLIENT_NAME", new StringLiteralExpr(tracingName), - PRIVATE, STATIC, FINAL + PRIVATE, + STATIC, + FINAL ) clientNameField.setFinal(false) clientNameField.getVariables.iterator().next().setInitializer(new NameExpr("DEFAULT_CLIENT_NAME")) @@ -653,69 +852,91 @@ object AsyncHttpClientClientGenerator { } builderClass.addFieldWithInitializer( - optionalType(HTTP_CLIENT_FUNCTION_TYPE), "httpClient", + optionalType(HTTP_CLIENT_FUNCTION_TYPE), + "httpClient", new MethodCallExpr(new NameExpr("Optional"), "empty"), PRIVATE ) builderClass.addFieldWithInitializer( - optionalType(OBJECT_MAPPER_TYPE), "objectMapper", + optionalType(OBJECT_MAPPER_TYPE), + "objectMapper", new MethodCallExpr(new NameExpr("Optional"), "empty"), PRIVATE ) val builderConstructor = builderClass.addConstructor(PUBLIC) - def createConstructorParameter(tpe: Type, name: String): Parameter = { + def createConstructorParameter(tpe: Type, name: String): Parameter = new Parameter(util.EnumSet.of(FINAL), tpe, new SimpleName(name)) - } - def createBuilderConstructorAssignment(name: String): Statement = { - new ExpressionStmt(new AssignExpr( - new FieldAccessExpr(new ThisExpr, name), - new MethodCallExpr("requireNonNull", new NameExpr(name)), AssignExpr.Operator.ASSIGN) + def createBuilderConstructorAssignment(name: String): Statement = + new ExpressionStmt( + new AssignExpr(new FieldAccessExpr(new ThisExpr, name), new MethodCallExpr("requireNonNull", new NameExpr(name)), AssignExpr.Operator.ASSIGN) ) - } (serverUrl, tracingName) match { case (None, None) if tracing => - builderConstructor.setParameters(new NodeList( - createConstructorParameter(URI_TYPE, "baseUrl"), - createConstructorParameter(STRING_TYPE, "clientName") - )) - builderConstructor.setBody(new BlockStmt(new NodeList( - createBuilderConstructorAssignment("baseUrl"), - createBuilderConstructorAssignment("clientName") - ))) + builderConstructor.setParameters( + new NodeList( + createConstructorParameter(URI_TYPE, "baseUrl"), + createConstructorParameter(STRING_TYPE, "clientName") + ) + ) + builderConstructor.setBody( + new BlockStmt( + new NodeList( + createBuilderConstructorAssignment("baseUrl"), + createBuilderConstructorAssignment("clientName") + ) + ) + ) case (Some(_), None) if tracing => - builderConstructor.setParameters(new NodeList( - createConstructorParameter(STRING_TYPE, "clientName") - )) - builderConstructor.setBody(new BlockStmt(new NodeList( - createBuilderConstructorAssignment("clientName") - ))) + builderConstructor.setParameters( + new NodeList( + createConstructorParameter(STRING_TYPE, "clientName") + ) + ) + builderConstructor.setBody( + new BlockStmt( + new NodeList( + createBuilderConstructorAssignment("clientName") + ) + ) + ) case (None, _) => - builderConstructor.setParameters(new NodeList( - createConstructorParameter(URI_TYPE, "baseUrl") - )) - builderConstructor.setBody(new BlockStmt(new NodeList( - createBuilderConstructorAssignment("baseUrl") - ))) + builderConstructor.setParameters( + new NodeList( + createConstructorParameter(URI_TYPE, "baseUrl") + ) + ) + builderConstructor.setBody( + new BlockStmt( + new NodeList( + createBuilderConstructorAssignment("baseUrl") + ) + ) + ) - case (Some(_), Some(_)) => // no params + case (Some(_), Some(_)) => // no params - case (Some(_), _) if !tracing => // no params + case (Some(_), _) if !tracing => // no params } def addSetter(tpe: Type, name: String, initializer: String => Expression): Unit = { val setter = builderClass.addMethod(s"with${name.capitalize}", PUBLIC) setter.setType(BUILDER_TYPE) setter.addParameter(new Parameter(util.EnumSet.of(FINAL), tpe, new SimpleName(name))) - setter.setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new AssignExpr(new FieldAccessExpr(new ThisExpr, name), initializer(name), AssignExpr.Operator.ASSIGN)), - new ReturnStmt(new ThisExpr) - ))) + setter.setBody( + new BlockStmt( + new NodeList( + new ExpressionStmt(new AssignExpr(new FieldAccessExpr(new ThisExpr, name), initializer(name), AssignExpr.Operator.ASSIGN)), + new ReturnStmt(new ThisExpr) + ) + ) + ) } val nonNullInitializer: String => Expression = name => new MethodCallExpr(null, "requireNonNull", new NodeList[Expression](new NameExpr(name))) - def optionalInitializer(valueArg: String => Expression): String => Expression = name => new MethodCallExpr(new NameExpr("Optional"), "of", new NodeList[Expression](valueArg(name))) + def optionalInitializer(valueArg: String => Expression): String => Expression = + name => new MethodCallExpr(new NameExpr("Optional"), "of", new NodeList[Expression](valueArg(name))) if (serverUrl.isDefined) { addSetter(URI_TYPE, "baseUrl", nonNullInitializer) } @@ -723,43 +944,61 @@ object AsyncHttpClientClientGenerator { addSetter(STRING_TYPE, "clientName", nonNullInitializer) } addSetter(HTTP_CLIENT_FUNCTION_TYPE, "httpClient", optionalInitializer(new NameExpr(_))) - addSetter(OBJECT_MAPPER_TYPE, "objectMapper", optionalInitializer(name => new MethodCallExpr( - new NameExpr("JacksonSupport"), - "configureObjectMapper", - new NodeList[Expression](new NameExpr(name))) - )) + addSetter( + OBJECT_MAPPER_TYPE, + "objectMapper", + optionalInitializer(name => new MethodCallExpr(new NameExpr("JacksonSupport"), "configureObjectMapper", new NodeList[Expression](new NameExpr(name)))) + ) val buildMethod = builderClass.addMethod("build", PUBLIC) buildMethod.setType(clientType) - buildMethod.setBody(new BlockStmt(new NodeList( - new ReturnStmt(new ObjectCreationExpr(null, clientType, new NodeList(new ThisExpr))) - ))) + buildMethod.setBody( + new BlockStmt( + new NodeList( + new ReturnStmt(new ObjectCreationExpr(null, clientType, new NodeList(new ThisExpr))) + ) + ) + ) def addInternalGetter(tpe: Type, name: String, getterCall: Expression): Unit = { val getter = builderClass.addMethod(s"get${name.capitalize}", PRIVATE) getter.setType(tpe) - getter.setBody(new BlockStmt(new NodeList( - new ReturnStmt(new MethodCallExpr( - new FieldAccessExpr(new ThisExpr, name), - "orElseGet", - new NodeList[Expression]( - new LambdaExpr(new NodeList(), new ExpressionStmt(getterCall), true) + getter.setBody( + new BlockStmt( + new NodeList( + new ReturnStmt( + new MethodCallExpr( + new FieldAccessExpr(new ThisExpr, name), + "orElseGet", + new NodeList[Expression]( + new LambdaExpr(new NodeList(), new ExpressionStmt(getterCall), true) + ) + ) + ) ) - )) - ))) + ) + ) } - addInternalGetter(HTTP_CLIENT_FUNCTION_TYPE, "httpClient", new MethodCallExpr( - new NameExpr("AsyncHttpClientSupport"), - "createHttpClient", - new NodeList[Expression]( - new MethodCallExpr(new NameExpr("AsyncHttpClientSupport"), "createDefaultAsyncHttpClient") + addInternalGetter( + HTTP_CLIENT_FUNCTION_TYPE, + "httpClient", + new MethodCallExpr( + new NameExpr("AsyncHttpClientSupport"), + "createHttpClient", + new NodeList[Expression]( + new MethodCallExpr(new NameExpr("AsyncHttpClientSupport"), "createDefaultAsyncHttpClient") + ) + ) + ) + addInternalGetter( + OBJECT_MAPPER_TYPE, + "objectMapper", + new MethodCallExpr( + new NameExpr("JacksonSupport"), + "configureObjectMapper", + new NodeList[Expression](new ObjectCreationExpr(null, OBJECT_MAPPER_TYPE, new NodeList())) ) - )) - addInternalGetter(OBJECT_MAPPER_TYPE, "objectMapper", new MethodCallExpr( - new NameExpr("JacksonSupport"), - "configureObjectMapper", - new NodeList[Expression](new ObjectCreationExpr(null, OBJECT_MAPPER_TYPE, new NodeList())) - )) + ) val clientClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, clientName) @@ -773,21 +1012,28 @@ object AsyncHttpClientClientGenerator { val constructor = clientClass.addConstructor(PRIVATE) constructor.addParameter(new Parameter(util.EnumSet.of(FINAL), BUILDER_TYPE, new SimpleName("builder"))) def newFieldAccessExpr(scope: Expression, name: String): Expression = new FieldAccessExpr(scope, name) - def newMethodCallExpr(scope: Expression, name: String): Expression = new MethodCallExpr(scope, s"get${name.capitalize}") - constructor.setBody(new BlockStmt(new NodeList( - List[Option[(String, (Expression, String) => Expression)]]( - Some(("baseUrl", newFieldAccessExpr)), - if (tracing) Some(("clientName", newFieldAccessExpr)) else None, - Some(("httpClient", newMethodCallExpr)), - Some(("objectMapper", newMethodCallExpr)) - ).flatten.map({ case (name, value) => - new ExpressionStmt(new AssignExpr( - new FieldAccessExpr(new ThisExpr, name), - value(new NameExpr("builder"), name), - AssignExpr.Operator.ASSIGN - )) - }): _* - ))) + def newMethodCallExpr(scope: Expression, name: String): Expression = new MethodCallExpr(scope, s"get${name.capitalize}") + constructor.setBody( + new BlockStmt( + new NodeList( + List[Option[(String, (Expression, String) => Expression)]]( + Some(("baseUrl", newFieldAccessExpr)), + if (tracing) Some(("clientName", newFieldAccessExpr)) else None, + Some(("httpClient", newMethodCallExpr)), + Some(("objectMapper", newMethodCallExpr)) + ).flatten.map({ + case (name, value) => + new ExpressionStmt( + new AssignExpr( + new FieldAccessExpr(new ThisExpr, name), + value(new NameExpr("builder"), name), + AssignExpr.Operator.ASSIGN + ) + ) + }): _* + ) + ) + ) clientClass.addMember(builderClass) clientCalls.foreach(clientClass.addMember) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/Dropwizard.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/Dropwizard.scala index 92cc2b6fe4..1dd7480d1d 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/Dropwizard.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/Dropwizard.scala @@ -8,7 +8,20 @@ import com.twilio.guardrail.generators.Java.JacksonGenerator._ import com.twilio.guardrail.generators.JavaGenerator.JavaInterp import com.twilio.guardrail.generators.SwaggerGenerator import com.twilio.guardrail.languages.JavaLanguage -import com.twilio.guardrail.{ClientServerTerms, CodegenApplication, DefinitionPM, DefinitionPME, DefinitionPMEA, DefinitionPMEAP, FrameworkC, FrameworkCS, FrameworkCSF, ModelInterpreters, Parser, Target} +import com.twilio.guardrail.{ + ClientServerTerms, + CodegenApplication, + DefinitionPM, + DefinitionPME, + DefinitionPMEA, + DefinitionPMEAP, + FrameworkC, + FrameworkCS, + FrameworkCSF, + ModelInterpreters, + Parser, + Target +} object Dropwizard extends (CodegenApplication[JavaLanguage, ?] ~> Target) { val interpDefinitionPM: DefinitionPM[JavaLanguage, ?] ~> Target = ProtocolSupportTermInterp or ModelProtocolTermInterp diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala index da21e77465..2bcf0cc156 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardGenerator.scala @@ -4,11 +4,11 @@ import cats.instances.list._ import cats.syntax.traverse._ import cats.~> import com.github.javaparser.JavaParser -import com.github.javaparser.ast.Modifier.{ABSTRACT, FINAL, PRIVATE, PUBLIC} +import com.github.javaparser.ast.Modifier.{ ABSTRACT, FINAL, PRIVATE, PUBLIC } import com.github.javaparser.ast.NodeList -import com.github.javaparser.ast.body.{ClassOrInterfaceDeclaration, Parameter} +import com.github.javaparser.ast.body.{ ClassOrInterfaceDeclaration, Parameter } import com.github.javaparser.ast.expr._ -import com.github.javaparser.ast.stmt.{BlockStmt, ExpressionStmt, ReturnStmt} +import com.github.javaparser.ast.stmt.{ BlockStmt, ExpressionStmt, ReturnStmt } import com.twilio.guardrail.Target import com.twilio.guardrail.generators.syntax.Java._ import com.twilio.guardrail.languages.JavaLanguage @@ -18,7 +18,7 @@ import java.util object DropwizardGenerator { object FrameworkInterp extends (FrameworkTerm[JavaLanguage, ?] ~> Target) { def apply[T](term: FrameworkTerm[JavaLanguage, T]): Target[T] = term match { - case FileType(format) => safeParseType(format.getOrElse("java.io.File")) + case FileType(format) => safeParseType(format.getOrElse("java.io.File")) case ObjectType(format) => safeParseType("com.fasterxml.jackson.databind.JsonNode") case GetFrameworkImports(tracing) => diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala index d0fbdb9cb4..0e6e275ca7 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala @@ -7,13 +7,13 @@ import cats.syntax.flatMap._ import cats.syntax.traverse._ import com.github.javaparser.JavaParser import com.github.javaparser.ast.Modifier._ -import com.github.javaparser.ast.{Modifier, NodeList} -import com.github.javaparser.ast.`type`.{PrimitiveType, Type, VoidType} +import com.github.javaparser.ast.{ Modifier, NodeList } +import com.github.javaparser.ast.`type`.{ PrimitiveType, Type, VoidType } import com.github.javaparser.ast.body._ import com.github.javaparser.ast.expr._ import com.github.javaparser.ast.stmt._ -import com.twilio.guardrail.generators.{Response, ScalaParameter, ScalaParameters} -import com.twilio.guardrail.{ADT, ClassDefinition, EnumDefinition, RandomType, RenderedRoutes, StrictProtocolElems, SupportDefinition, Target} +import com.twilio.guardrail.generators.{ Response, ScalaParameter, ScalaParameters } +import com.twilio.guardrail.{ ADT, ClassDefinition, EnumDefinition, RandomType, RenderedRoutes, StrictProtocolElems, SupportDefinition, Target } import com.twilio.guardrail.generators.syntax.Java._ import com.twilio.guardrail.languages.JavaLanguage import com.twilio.guardrail.protocol.terms.server._ @@ -28,53 +28,53 @@ import scala.util.Try object DropwizardServerGenerator { private implicit class ContentTypeExt(val ct: RouteMeta.ContentType) extends AnyVal { def toJaxRsAnnotationName: String = ct match { - case RouteMeta.ApplicationJson => "APPLICATION_JSON" + case RouteMeta.ApplicationJson => "APPLICATION_JSON" case RouteMeta.UrlencodedFormData => "APPLICATION_FORM_URLENCODED" - case RouteMeta.MultipartFormData => "MULTIPART_FORM_DATA" - case RouteMeta.TextPlain => "TEXT_PLAIN" + case RouteMeta.MultipartFormData => "MULTIPART_FORM_DATA" + case RouteMeta.TextPlain => "TEXT_PLAIN" } } - private val ASYNC_RESPONSE_TYPE = JavaParser.parseClassOrInterfaceType("AsyncResponse") + private val ASYNC_RESPONSE_TYPE = JavaParser.parseClassOrInterfaceType("AsyncResponse") private val RESPONSE_BUILDER_TYPE = JavaParser.parseClassOrInterfaceType("Response.ResponseBuilder") - private val LOGGER_TYPE = JavaParser.parseClassOrInterfaceType("Logger") + private val LOGGER_TYPE = JavaParser.parseClassOrInterfaceType("Logger") - private def removeEmpty(s: String): Option[String] = if (s.trim.isEmpty) None else Some(s.trim) + private def removeEmpty(s: String): Option[String] = if (s.trim.isEmpty) None else Some(s.trim) private def splitPathComponents(s: String): List[String] = s.split("/").flatMap(removeEmpty).toList private def findPathPrefix(routePaths: List[String]): List[String] = { def getHeads(sss: List[List[String]]): (List[Option[String]], List[List[String]]) = (sss.map(_.headOption), sss.map(ss => ss.headOption.fold(List.empty[String])(_ => ss.tail))) - def checkMatch(matching: List[String], headsToCheck: List[Option[String]], restOfHeads: List[List[String]]): List[String] = { - headsToCheck.headOption.flatMap({ firstO => firstO.map({ first => - if (headsToCheck.tail.forall(_.contains(first))) { - val (nextHeads, nextRest) = getHeads(restOfHeads) - checkMatch(matching :+ first, nextHeads, nextRest) - } else { - matching - } - })}).getOrElse(matching) - } + def checkMatch(matching: List[String], headsToCheck: List[Option[String]], restOfHeads: List[List[String]]): List[String] = + headsToCheck.headOption + .flatMap({ firstO => + firstO.map({ first => + if (headsToCheck.tail.forall(_.contains(first))) { + val (nextHeads, nextRest) = getHeads(restOfHeads) + checkMatch(matching :+ first, nextHeads, nextRest) + } else { + matching + } + }) + }) + .getOrElse(matching) - val splitRoutePaths = routePaths.map(splitPathComponents) + val splitRoutePaths = routePaths.map(splitPathComponents) val (initialHeads, initialRest) = getHeads(splitRoutePaths) checkMatch(List.empty, initialHeads, initialRest) } // FIXME: does this handle includes from other files? - private def definitionName(refName: Option[String]): Option[String] = { + private def definitionName(refName: Option[String]): Option[String] = refName.flatMap({ rn => rn.split("/").toList match { case "#" :: _ :: name :: Nil => Some(name) - case _ => None + case _ => None } }) - } - def getBestConsumes(contentTypes: List[RouteMeta.ContentType], - parameters: ScalaParameters[JavaLanguage]): Option[RouteMeta.ContentType] = - { + def getBestConsumes(contentTypes: List[RouteMeta.ContentType], parameters: ScalaParameters[JavaLanguage]): Option[RouteMeta.ContentType] = { val priorityOrder = NonEmptyList.of( RouteMeta.UrlencodedFormData, RouteMeta.ApplicationJson, @@ -82,35 +82,43 @@ object DropwizardServerGenerator { RouteMeta.TextPlain ) - priorityOrder.foldLeft[Option[RouteMeta.ContentType]](None)({ - case (s @ Some(_), _) => s - case (None, next) => contentTypes.find(_ == next) - }) + priorityOrder + .foldLeft[Option[RouteMeta.ContentType]](None)({ + case (s @ Some(_), _) => s + case (None, next) => contentTypes.find(_ == next) + }) .orElse(parameters.formParams.headOption.map(_ => RouteMeta.UrlencodedFormData)) .orElse(parameters.bodyParams.map(_ => RouteMeta.ApplicationJson)) } private def getBestProduces(contentTypes: List[RouteMeta.ContentType], responses: List[ApiResponse], - protocolElems: List[StrictProtocolElems[JavaLanguage]]): Option[RouteMeta.ContentType] = - { + protocolElems: List[StrictProtocolElems[JavaLanguage]]): Option[RouteMeta.ContentType] = { val priorityOrder = NonEmptyList.of( RouteMeta.ApplicationJson, RouteMeta.TextPlain ) - priorityOrder.foldLeft[Option[RouteMeta.ContentType]](None)({ - case (s @ Some(_), _) => s - case (None, next) => contentTypes.find(_ == next) - }) - .orElse(responses.map({ resp => - protocolElems.find(pe => definitionName(Option(resp.get$ref())).contains(pe.name)).flatMap({ - case _: ClassDefinition[_] => Some(RouteMeta.ApplicationJson) - case RandomType(_, tpe) if tpe.isPrimitiveType || tpe.isNamed("String") => Some(RouteMeta.TextPlain) - case _: ADT[_] | _: EnumDefinition[_] => Some(RouteMeta.TextPlain) - case _ => None - }) - }).headOption.flatten) + priorityOrder + .foldLeft[Option[RouteMeta.ContentType]](None)({ + case (s @ Some(_), _) => s + case (None, next) => contentTypes.find(_ == next) + }) + .orElse( + responses + .map({ resp => + protocolElems + .find(pe => definitionName(Option(resp.get$ref())).contains(pe.name)) + .flatMap({ + case _: ClassDefinition[_] => Some(RouteMeta.ApplicationJson) + case RandomType(_, tpe) if tpe.isPrimitiveType || tpe.isNamed("String") => Some(RouteMeta.TextPlain) + case _: ADT[_] | _: EnumDefinition[_] => Some(RouteMeta.TextPlain) + case _ => None + }) + }) + .headOption + .flatten + ) } object ServerTermInterp extends (ServerTerm[JavaLanguage, ?] ~> Target) { @@ -155,175 +163,234 @@ object DropwizardServerGenerator { handlerType <- safeParseClassOrInterfaceType(handlerName) } yield { val basePathComponents = basePath.toList.flatMap(splitPathComponents) - val commonPathPrefix = findPathPrefix(routes.map(_._3.path)) - - val (routeMethods, handlerMethodSigs) = routes.map({ case (operationId, tracingFields, sr @ RouteMeta(path, httpMethod, operation), parameters, responses) => - parameters.parameters.foreach(p => p.param.setType(p.param.getType.unbox)) - - val method = new MethodDeclaration(util.EnumSet.of(PUBLIC), new VoidType, operationId) - val httpMethodAnnotation = httpMethod match { - case HttpMethod.DELETE => new MarkerAnnotationExpr("DELETE") - case HttpMethod.GET => new MarkerAnnotationExpr("GET") - case HttpMethod.HEAD => new MarkerAnnotationExpr("HEAD") - case HttpMethod.OPTIONS => new MarkerAnnotationExpr("OPTIONS") - case HttpMethod.PATCH => new SingleMemberAnnotationExpr(new Name("HttpMethod"), new StringLiteralExpr("PATCH")) - case HttpMethod.POST => new MarkerAnnotationExpr("POST") - case HttpMethod.PUT => new MarkerAnnotationExpr("PUT") - case HttpMethod.TRACE => new SingleMemberAnnotationExpr(new Name("HttpMethod"), new StringLiteralExpr("TRACE")) - } - method.addAnnotation(httpMethodAnnotation) + val commonPathPrefix = findPathPrefix(routes.map(_._3.path)) + + val (routeMethods, handlerMethodSigs) = routes + .map({ + case (operationId, tracingFields, sr @ RouteMeta(path, httpMethod, operation), parameters, responses) => + parameters.parameters.foreach(p => p.param.setType(p.param.getType.unbox)) + + val method = new MethodDeclaration(util.EnumSet.of(PUBLIC), new VoidType, operationId) + val httpMethodAnnotation = httpMethod match { + case HttpMethod.DELETE => new MarkerAnnotationExpr("DELETE") + case HttpMethod.GET => new MarkerAnnotationExpr("GET") + case HttpMethod.HEAD => new MarkerAnnotationExpr("HEAD") + case HttpMethod.OPTIONS => new MarkerAnnotationExpr("OPTIONS") + case HttpMethod.PATCH => new SingleMemberAnnotationExpr(new Name("HttpMethod"), new StringLiteralExpr("PATCH")) + case HttpMethod.POST => new MarkerAnnotationExpr("POST") + case HttpMethod.PUT => new MarkerAnnotationExpr("PUT") + case HttpMethod.TRACE => new SingleMemberAnnotationExpr(new Name("HttpMethod"), new StringLiteralExpr("TRACE")) + } + method.addAnnotation(httpMethodAnnotation) - val pathSuffix = splitPathComponents(path).drop(commonPathPrefix.length).mkString("/", "/", "") - if (pathSuffix.nonEmpty && pathSuffix != "/") { - method.addAnnotation(new SingleMemberAnnotationExpr(new Name("Path"), new StringLiteralExpr(pathSuffix))) - } + val pathSuffix = splitPathComponents(path).drop(commonPathPrefix.length).mkString("/", "/", "") + if (pathSuffix.nonEmpty && pathSuffix != "/") { + method.addAnnotation(new SingleMemberAnnotationExpr(new Name("Path"), new StringLiteralExpr(pathSuffix))) + } - val consumes = getBestConsumes(operation.consumes.flatMap(RouteMeta.ContentType.unapply).toList, parameters) - .orElse({ - if (parameters.formParams.nonEmpty) { - if (parameters.formParams.exists(_.isFile)) { - Some(RouteMeta.MultipartFormData) - } else { - Some(RouteMeta.UrlencodedFormData) - } - } else if (parameters.bodyParams.nonEmpty) { - Some(RouteMeta.ApplicationJson) - } else { - None + val consumes = getBestConsumes(operation.consumes.flatMap(RouteMeta.ContentType.unapply).toList, parameters) + .orElse({ + if (parameters.formParams.nonEmpty) { + if (parameters.formParams.exists(_.isFile)) { + Some(RouteMeta.MultipartFormData) + } else { + Some(RouteMeta.UrlencodedFormData) + } + } else if (parameters.bodyParams.nonEmpty) { + Some(RouteMeta.ApplicationJson) + } else { + None + } + }) + consumes + .map(c => new SingleMemberAnnotationExpr(new Name("Consumes"), new FieldAccessExpr(new NameExpr("MediaType"), c.toJaxRsAnnotationName))) + .foreach(method.addAnnotation) + + val successResponses = + operation.getResponses.entrySet.asScala.filter(entry => Try(entry.getKey.toInt / 100 == 2).getOrElse(false)).map(_.getValue).toList + val produces = getBestProduces(operation.produces.flatMap(RouteMeta.ContentType.unapply).toList, successResponses, protocolElems) + produces + .map(p => new SingleMemberAnnotationExpr(new Name("Produces"), new FieldAccessExpr(new NameExpr("MediaType"), p.toJaxRsAnnotationName))) + .foreach(method.addAnnotation) + + def addParamAnnotation(template: Parameter, annotationName: String, argName: String): Parameter = { + val parameter = template.clone() + parameter.addAnnotation(new SingleMemberAnnotationExpr(new Name(annotationName), new StringLiteralExpr(argName))) + parameter } - }) - consumes - .map(c => new SingleMemberAnnotationExpr(new Name("Consumes"), new FieldAccessExpr(new NameExpr("MediaType"), c.toJaxRsAnnotationName))) - .foreach(method.addAnnotation) - - val successResponses = operation.getResponses.entrySet.asScala.filter(entry => Try(entry.getKey.toInt / 100 == 2).getOrElse(false)).map(_.getValue).toList - val produces = getBestProduces(operation.produces.flatMap(RouteMeta.ContentType.unapply).toList, successResponses, protocolElems) - produces - .map(p => new SingleMemberAnnotationExpr(new Name("Produces"), new FieldAccessExpr(new NameExpr("MediaType"), p.toJaxRsAnnotationName))) - .foreach(method.addAnnotation) - - def addParamAnnotation(template: Parameter, annotationName: String, paramName: Name): Parameter = { - val parameter = template.clone() - parameter.addAnnotation(new SingleMemberAnnotationExpr(new Name(annotationName), new StringLiteralExpr(paramName.asString))) - parameter - } - val methodParams: List[Parameter] = List( - (parameters.pathParams, "PathParam"), - (parameters.headerParams, "HeaderParam"), - (parameters.queryStringParams, "QueryParam"), - (parameters.formParams, if (parameters.formParams.exists(_.isFile)) "FormDataParam" else "FormParam") - ).flatMap({ case (params, annotationName) => - params.map(param => addParamAnnotation(param.param, annotationName, param.paramName)) - }) ++ parameters.bodyParams.map(_.param) - - methodParams.foreach(method.addParameter) - method.addParameter( - new Parameter(util.EnumSet.of(FINAL), ASYNC_RESPONSE_TYPE, new SimpleName("asyncResponse")).addMarkerAnnotation("Suspended") - ) + val methodParams: List[Parameter] = List( + (parameters.pathParams, "PathParam"), + (parameters.headerParams, "HeaderParam"), + (parameters.queryStringParams, "QueryParam"), + (parameters.formParams, if (parameters.formParams.exists(_.isFile)) "FormDataParam" else "FormParam") + ).flatMap({ + case (params, annotationName) => + params.map(param => addParamAnnotation(param.param, annotationName, param.argName.value)) + }) ++ parameters.bodyParams.map(_.param) + + methodParams.foreach(method.addParameter) + method.addParameter( + new Parameter(util.EnumSet.of(FINAL), ASYNC_RESPONSE_TYPE, new SimpleName("asyncResponse")).addMarkerAnnotation("Suspended") + ) - val responseName = s"${operationId.capitalize}Response" - val responseType = JavaParser.parseClassOrInterfaceType(responseName) - - val responseBodyIfBranches = responses.value.collect({ - case r @ Response(_, Some(_)) => r - }).map({ case Response(statusCodeName, valueType) => - val responseType = JavaParser.parseClassOrInterfaceType(s"${responseName}.${statusCodeName}") - new IfStmt( - new InstanceOfExpr(new NameExpr("result"), responseType), - new BlockStmt(new NodeList( - new ExpressionStmt( - new MethodCallExpr( - new NameExpr("builder"), - "entity", - new NodeList[Expression]( - new MethodCallExpr( - new EnclosedExpr(new CastExpr(responseType, new NameExpr("result"))), - "getValue") + val responseName = s"${operationId.capitalize}Response" + val responseType = JavaParser.parseClassOrInterfaceType(responseName) + + val responseBodyIfBranches = responses.value + .collect({ + case r @ Response(_, Some(_)) => r + }) + .map({ + case Response(statusCodeName, valueType) => + val responseType = JavaParser.parseClassOrInterfaceType(s"${responseName}.${statusCodeName}") + new IfStmt( + new InstanceOfExpr(new NameExpr("result"), responseType), + new BlockStmt( + new NodeList( + new ExpressionStmt( + new MethodCallExpr( + new NameExpr("builder"), + "entity", + new NodeList[Expression]( + new MethodCallExpr(new EnclosedExpr(new CastExpr(responseType, new NameExpr("result"))), "getValue") + ) + ) + ) + ) + ), + null + ) + }) + NonEmptyList + .fromList(responseBodyIfBranches) + .foreach(_.reduceLeft({ (prev, next) => + prev.setElseStmt(next) + next + })) + + val whenCompleteLambda = new LambdaExpr( + new NodeList( + new Parameter(util.EnumSet.of(FINAL), JavaParser.parseClassOrInterfaceType(responseName), new SimpleName("result")), + new Parameter(util.EnumSet.of(FINAL), THROWABLE_TYPE, new SimpleName("err")) + ), + new BlockStmt( + new NodeList( + new IfStmt( + new BinaryExpr(new NameExpr("err"), new NullLiteralExpr, BinaryExpr.Operator.NOT_EQUALS), + new BlockStmt( + new NodeList( + new ExpressionStmt( + new MethodCallExpr( + new NameExpr("logger"), + "error", + new NodeList[Expression]( + new StringLiteralExpr(s"${handlerName}.${operationId} threw an exception ({}): {}"), + new MethodCallExpr(new MethodCallExpr(new NameExpr("err"), "getClass"), "getName"), + new MethodCallExpr(new NameExpr("err"), "getMessage"), + new NameExpr("err") + ) + ) + ), + new ExpressionStmt( + new MethodCallExpr( + new NameExpr("asyncResponse"), + "resume", + new NodeList[Expression]( + new MethodCallExpr(new MethodCallExpr( + new NameExpr("Response"), + "status", + new NodeList[Expression](new IntegerLiteralExpr(500)) + ), + "build") + ) + ) + ) + ) + ), + new BlockStmt( + new NodeList( + List( + Option( + new ExpressionStmt( + new VariableDeclarationExpr( + new VariableDeclarator( + RESPONSE_BUILDER_TYPE, + "builder", + new MethodCallExpr(new NameExpr("Response"), + "status", + new NodeList[Expression](new MethodCallExpr(new NameExpr("result"), "getStatusCode"))) + ), + FINAL + ) + ) + ), + responseBodyIfBranches.headOption, + Option( + new ExpressionStmt( + new MethodCallExpr(new NameExpr("asyncResponse"), + "resume", + new NodeList[Expression](new MethodCallExpr(new NameExpr("builder"), "build"))) + ) + ) + ).flatten: _* + ) + ) ) ) - ) - )), - null - ) - }) - NonEmptyList.fromList(responseBodyIfBranches).foreach(_.reduceLeft({ (prev, next) => - prev.setElseStmt(next) - next - })) + ), + true + ) - val whenCompleteLambda = new LambdaExpr( - new NodeList( - new Parameter(util.EnumSet.of(FINAL), JavaParser.parseClassOrInterfaceType(responseName), new SimpleName("result")), - new Parameter(util.EnumSet.of(FINAL), THROWABLE_TYPE, new SimpleName("err")) - ), - new BlockStmt(new NodeList(new IfStmt( - new BinaryExpr(new NameExpr("err"), new NullLiteralExpr, BinaryExpr.Operator.NOT_EQUALS), - new BlockStmt(new NodeList( - new ExpressionStmt(new MethodCallExpr(new NameExpr("logger"), "error", new NodeList[Expression]( - new StringLiteralExpr(s"${handlerName}.${operationId} threw an exception ({}): {}"), - new MethodCallExpr(new MethodCallExpr(new NameExpr("err"), "getClass"), "getName"), - new MethodCallExpr(new NameExpr("err"), "getMessage"), - new NameExpr("err") - ))), - new ExpressionStmt(new MethodCallExpr(new NameExpr("asyncResponse"), "resume", new NodeList[Expression]( - new MethodCallExpr(new MethodCallExpr( - new NameExpr("Response"), - "status", - new NodeList[Expression](new IntegerLiteralExpr(500)) - ), "build") - ))) - )), - new BlockStmt(new NodeList(List( - Option(new ExpressionStmt(new VariableDeclarationExpr( - new VariableDeclarator( - RESPONSE_BUILDER_TYPE, - "builder", - new MethodCallExpr(new NameExpr("Response"), "status", new NodeList[Expression](new MethodCallExpr(new NameExpr("result"), "getStatusCode"))) - ), - FINAL - ))), - responseBodyIfBranches.headOption, - Option(new ExpressionStmt(new MethodCallExpr(new NameExpr("asyncResponse"), "resume", new NodeList[Expression](new MethodCallExpr(new NameExpr("builder"), "build"))))) - ).flatten: _*)) - ))), - true - ) + val handlerCall = new MethodCallExpr( + new FieldAccessExpr(new ThisExpr, "handler"), + operationId, + new NodeList[Expression](methodParams.map(param => new NameExpr(param.getName.asString)): _*) + ) - val handlerCall = new MethodCallExpr( - new FieldAccessExpr(new ThisExpr, "handler"), operationId, - new NodeList[Expression](methodParams.map(param => new NameExpr(param.getName.asString)): _*) - ) + method.setBody( + new BlockStmt( + new NodeList( + new ExpressionStmt(new MethodCallExpr(handlerCall, "whenComplete", new NodeList[Expression](whenCompleteLambda))) + ) + ) + ) - method.setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new MethodCallExpr(handlerCall, "whenComplete", new NodeList[Expression](whenCompleteLambda))) - ))) + val futureResponseType = completionStageType(responseType) + val handlerMethodSig = new MethodDeclaration(util.EnumSet.noneOf(classOf[Modifier]), futureResponseType, operationId) + (parameters.pathParams ++ parameters.headerParams ++ parameters.queryStringParams ++ parameters.formParams ++ parameters.bodyParams).foreach({ + parameter => + handlerMethodSig.addParameter(parameter.param.clone()) + }) + handlerMethodSig.setBody(null) - val futureResponseType = completionStageType(responseType) - val handlerMethodSig = new MethodDeclaration(util.EnumSet.noneOf(classOf[Modifier]), futureResponseType, operationId) - (parameters.pathParams ++ parameters.headerParams ++ parameters.queryStringParams ++ parameters.formParams ++ parameters.bodyParams).foreach({ parameter => - handlerMethodSig.addParameter(parameter.param.clone()) + (method, handlerMethodSig) }) - handlerMethodSig.setBody(null) - - (method, handlerMethodSig) - }).unzip + .unzip val resourceConstructor = new ConstructorDeclaration(util.EnumSet.of(PUBLIC), resourceName) resourceConstructor.addParameter(new Parameter(util.EnumSet.of(FINAL), handlerType, new SimpleName("handler"))) - resourceConstructor.setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new AssignExpr(new FieldAccessExpr(new ThisExpr, "handler"), new NameExpr("handler"), AssignExpr.Operator.ASSIGN)) - ))) + resourceConstructor.setBody( + new BlockStmt( + new NodeList( + new ExpressionStmt(new AssignExpr(new FieldAccessExpr(new ThisExpr, "handler"), new NameExpr("handler"), AssignExpr.Operator.ASSIGN)) + ) + ) + ) val annotations = List( new SingleMemberAnnotationExpr(new Name("Path"), new StringLiteralExpr((basePathComponents ++ commonPathPrefix).mkString("/", "/", ""))) ) val supportDefinitions = List[BodyDeclaration[_]]( - new FieldDeclaration(util.EnumSet.of(PRIVATE, STATIC, FINAL), new VariableDeclarator( - LOGGER_TYPE, "logger", - new MethodCallExpr(new NameExpr("LoggerFactory"), "getLogger", new NodeList[Expression](new ClassExpr(resourceType))) - )), + new FieldDeclaration( + util.EnumSet.of(PRIVATE, STATIC, FINAL), + new VariableDeclarator( + LOGGER_TYPE, + "logger", + new MethodCallExpr(new NameExpr("LoggerFactory"), "getLogger", new NodeList[Expression](new ClassExpr(resourceType))) + ) + ), new FieldDeclaration(util.EnumSet.of(PRIVATE, FINAL), new VariableDeclarator(handlerType, "handler")), resourceConstructor ) @@ -361,57 +428,79 @@ object DropwizardServerGenerator { val getStatusCodeMethod = cls.addMethod("getStatusCode", PUBLIC) getStatusCodeMethod.setType(PrimitiveType.intType) - getStatusCodeMethod.setBody(new BlockStmt(new NodeList( - new ReturnStmt(new FieldAccessExpr(new ThisExpr, "statusCode")) - ))) + getStatusCodeMethod.setBody( + new BlockStmt( + new NodeList( + new ReturnStmt(new FieldAccessExpr(new ThisExpr, "statusCode")) + ) + ) + ) cls } val responseClasses = responses.value.map { response => val clsName: String = response.statusCodeName.asString - val clsType = JavaParser.parseClassOrInterfaceType(clsName) - val cls = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, STATIC), false, clsName) + val clsType = JavaParser.parseClassOrInterfaceType(clsName) + val cls = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, STATIC), false, clsName) cls.setExtendedTypes(new NodeList(abstractResponseClassType)) - val (fields, constructor, creator, methods) = response.value.fold[(List[FieldDeclaration], ConstructorDeclaration, BodyDeclaration[_], List[MethodDeclaration])]({ - val constructor = new ConstructorDeclaration(util.EnumSet.of(PRIVATE), clsName) - constructor.setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new MethodCallExpr("super", new IntegerLiteralExpr(response.statusCode))) - ))) - - val creator = new FieldDeclaration( - util.EnumSet.of(PUBLIC, STATIC, FINAL), - new VariableDeclarator(clsType, clsName, new ObjectCreationExpr(null, clsType, new NodeList)) - ) - - (List.empty[FieldDeclaration], constructor, creator, List.empty[MethodDeclaration]) - })({ case (valueType, _) => - val unboxedValueType: Type = valueType.unbox - val valueField = new FieldDeclaration(util.EnumSet.of(PRIVATE, FINAL), new VariableDeclarator(unboxedValueType, "value")) + val (fields, constructor, creator, methods) = + response.value.fold[(List[FieldDeclaration], ConstructorDeclaration, BodyDeclaration[_], List[MethodDeclaration])]({ + val constructor = new ConstructorDeclaration(util.EnumSet.of(PRIVATE), clsName) + constructor.setBody( + new BlockStmt( + new NodeList( + new ExpressionStmt(new MethodCallExpr("super", new IntegerLiteralExpr(response.statusCode))) + ) + ) + ) - val constructParam = new Parameter(util.EnumSet.of(FINAL), unboxedValueType, new SimpleName("value")) + val creator = new FieldDeclaration( + util.EnumSet.of(PUBLIC, STATIC, FINAL), + new VariableDeclarator(clsType, clsName, new ObjectCreationExpr(null, clsType, new NodeList)) + ) - val constructor = new ConstructorDeclaration(util.EnumSet.of(PRIVATE), clsName) - .addParameter(constructParam) - .setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new MethodCallExpr("super", new IntegerLiteralExpr(response.statusCode))), - new ExpressionStmt(new AssignExpr(new FieldAccessExpr(new ThisExpr, "value"), new NameExpr("value"), AssignExpr.Operator.ASSIGN)) - ))) + (List.empty[FieldDeclaration], constructor, creator, List.empty[MethodDeclaration]) + })({ + case (valueType, _) => + val unboxedValueType: Type = valueType.unbox + val valueField = new FieldDeclaration(util.EnumSet.of(PRIVATE, FINAL), new VariableDeclarator(unboxedValueType, "value")) + + val constructParam = new Parameter(util.EnumSet.of(FINAL), unboxedValueType, new SimpleName("value")) + + val constructor = new ConstructorDeclaration(util.EnumSet.of(PRIVATE), clsName) + .addParameter(constructParam) + .setBody( + new BlockStmt( + new NodeList( + new ExpressionStmt(new MethodCallExpr("super", new IntegerLiteralExpr(response.statusCode))), + new ExpressionStmt(new AssignExpr(new FieldAccessExpr(new ThisExpr, "value"), new NameExpr("value"), AssignExpr.Operator.ASSIGN)) + ) + ) + ) - val creator = new MethodDeclaration(util.EnumSet.of(PUBLIC, STATIC), clsType, clsName) - .addParameter(constructParam) - .setBody(new BlockStmt(new NodeList( - new ReturnStmt(new ObjectCreationExpr(null, clsType, new NodeList(new NameExpr("value")))) - ))) + val creator = new MethodDeclaration(util.EnumSet.of(PUBLIC, STATIC), clsType, clsName) + .addParameter(constructParam) + .setBody( + new BlockStmt( + new NodeList( + new ReturnStmt(new ObjectCreationExpr(null, clsType, new NodeList(new NameExpr("value")))) + ) + ) + ) - val getValueMethod = new MethodDeclaration(util.EnumSet.of(PUBLIC), unboxedValueType, "getValue") - getValueMethod.setBody(new BlockStmt(new NodeList( - new ReturnStmt(new FieldAccessExpr(new ThisExpr, "value")) - ))) + val getValueMethod = new MethodDeclaration(util.EnumSet.of(PUBLIC), unboxedValueType, "getValue") + getValueMethod.setBody( + new BlockStmt( + new NodeList( + new ReturnStmt(new FieldAccessExpr(new ThisExpr, "value")) + ) + ) + ) - (valueField :: Nil, constructor, creator, getValueMethod :: Nil) - }) + (valueField :: Nil, constructor, creator, getValueMethod :: Nil) + }) (fields ++ Option(constructor) ++ methods).foreach(cls.addMember) abstractResponseClass.addMember(creator) @@ -425,9 +514,11 @@ object DropwizardServerGenerator { case GenerateSupportDefinitions(tracing) => val showerClass = SHOWER_CLASS_DEF - Target.pure(List( - SupportDefinition[JavaLanguage](new Name(showerClass.getNameAsString), List.empty, showerClass) - )) + Target.pure( + List( + SupportDefinition[JavaLanguage](new Name(showerClass.getNameAsString), List.empty, showerClass) + ) + ) case RenderClass(className, handlerName, classAnnotations, combinedRouteTerms, extraRouteParams, responseDefinitions, supportDefinitions) => def doRender: Target[List[BodyDeclaration[_]]] = { @@ -436,7 +527,7 @@ object DropwizardServerGenerator { supportDefinitions.foreach(cls.addMember) combinedRouteTerms.foreach({ case bd: BodyDeclaration[_] => cls.addMember(bd) - case _ => + case _ => }) Target.pure(cls +: responseDefinitions) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala index 62794ee6cf..0e97184ffe 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -6,8 +6,8 @@ import _root_.io.swagger.v3.oas.models.media._ import cats.data.NonEmptyList import cats.implicits._ import cats.~> -import com.github.javaparser.ast.`type`.{ClassOrInterfaceType, PrimitiveType, Type} -import com.twilio.guardrail.extract.{Default, ScalaEmptyIsNull} +import com.github.javaparser.ast.`type`.{ ClassOrInterfaceType, PrimitiveType, Type } +import com.twilio.guardrail.extract.{ Default, ScalaEmptyIsNull } import com.twilio.guardrail.generators.syntax.Java._ import com.twilio.guardrail.generators.syntax.RichString import com.twilio.guardrail.languages.JavaLanguage @@ -15,9 +15,9 @@ import com.twilio.guardrail.protocol.terms.protocol._ import java.util.Locale import scala.collection.JavaConverters._ import com.github.javaparser.JavaParser -import com.github.javaparser.ast.{ImportDeclaration, Node, NodeList} +import com.github.javaparser.ast.{ ImportDeclaration, Node, NodeList } import com.github.javaparser.ast.stmt._ -import com.github.javaparser.ast.Modifier.{ABSTRACT, FINAL, PRIVATE, PUBLIC, STATIC} +import com.github.javaparser.ast.Modifier.{ ABSTRACT, FINAL, PRIVATE, PUBLIC, STATIC } import com.github.javaparser.ast.body._ import com.github.javaparser.ast.expr._ import java.util @@ -25,35 +25,34 @@ import scala.language.existentials import scala.util.Try object JacksonGenerator { - private case class ParameterTerm(propertyName: String, - parameterName: String, - fieldType: Type, - parameterType: Type, - defaultValue: Option[Expression]) + private case class ParameterTerm(propertyName: String, parameterName: String, fieldType: Type, parameterType: Type, defaultValue: Option[Expression]) // returns a tuple of (requiredTerms, optionalTerms) // note that required terms _that have a default value_ are conceptually optional. private def sortParams(params: List[ProtocolParameter[JavaLanguage]]): (List[ParameterTerm], List[ParameterTerm]) = { def defaultValueToExpression(defaultValue: Option[Node]): Option[Expression] = defaultValue match { case Some(expr: Expression) => Some(expr) - case _ => None + case _ => None } - params.map({ case ProtocolParameter(term, name, _, _, _, selfDefaultValue) => - val parameterType = if (term.getType.isOptional) { - term.getType.containedType.unbox - } else { - term.getType - } - val defaultValue = defaultValueToExpression(selfDefaultValue) - - ParameterTerm(name, term.getNameAsString, term.getType, parameterType, defaultValue) - }).partition( - pt => !pt.fieldType.isOptional && pt.defaultValue.isEmpty - ) + params + .map({ + case ProtocolParameter(term, name, _, _, _, selfDefaultValue) => + val parameterType = if (term.getType.isOptional) { + term.getType.containedType.unbox + } else { + term.getType + } + val defaultValue = defaultValueToExpression(selfDefaultValue) + + ParameterTerm(name, term.getNameAsString, term.getType, parameterType, defaultValue) + }) + .partition( + pt => !pt.fieldType.isOptional && pt.defaultValue.isEmpty + ) } - private def addParents(cls: ClassOrInterfaceDeclaration, parentOpt: Option[SuperClass[JavaLanguage]]): Unit = { + private def addParents(cls: ClassOrInterfaceDeclaration, parentOpt: Option[SuperClass[JavaLanguage]]): Unit = parentOpt.foreach({ parent => val directParent = JavaParser.parseClassOrInterfaceType(parent.clsName) val otherParents = parent.interfaces.map(JavaParser.parseClassOrInterfaceType) @@ -61,7 +60,6 @@ object JacksonGenerator { new NodeList((directParent +: otherParents): _*) ) }) - } private def lookupTypeName(tpeName: String, concreteTypes: List[PropMeta[JavaLanguage]])(f: Type => Target[Type]): Option[Target[Type]] = concreteTypes @@ -69,9 +67,11 @@ object JacksonGenerator { .map(_.tpe) .map(f) - private val HASH_MAP_TYPE_DIAMONDED = JavaParser.parseClassOrInterfaceType("java.util.HashMap") + private val HASH_MAP_TYPE_DIAMONDED = JavaParser + .parseClassOrInterfaceType("java.util.HashMap") .setTypeArguments(new NodeList[Type]) - private val ARRAY_LIST_TYPE_DIAMONDED = JavaParser.parseClassOrInterfaceType("java.util.ArrayList") + private val ARRAY_LIST_TYPE_DIAMONDED = JavaParser + .parseClassOrInterfaceType("java.util.ArrayList") .setTypeArguments(new NodeList[Type]) object EnumProtocolTermInterp extends (EnumProtocolTerm[JavaLanguage, ?] ~> Target) { @@ -98,11 +98,13 @@ object JacksonGenerator { val enumType = JavaParser.parseType(clsName) val enumDefns = elems.map { - case (value, termName, _) => new EnumConstantDeclaration( - new NodeList(), new SimpleName(termName.getIdentifier.toSnakeCase.toUpperCase(Locale.US)), - new NodeList(new StringLiteralExpr(value)), - new NodeList() - ) + case (value, termName, _) => + new EnumConstantDeclaration( + new NodeList(), + new SimpleName(termName.getIdentifier.toSnakeCase.toUpperCase(Locale.US)), + new NodeList(new StringLiteralExpr(value)), + new NodeList() + ) } val nameField = new FieldDeclaration( @@ -112,13 +114,19 @@ object JacksonGenerator { val constructor = new ConstructorDeclaration(util.EnumSet.of(PRIVATE), clsName) constructor.addParameter(new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("name"))) - constructor.setBody(new BlockStmt(new NodeList( - new ExpressionStmt(new AssignExpr( - new FieldAccessExpr(new ThisExpr, "name"), - new NameExpr("name"), - AssignExpr.Operator.ASSIGN - )) - ))) + constructor.setBody( + new BlockStmt( + new NodeList( + new ExpressionStmt( + new AssignExpr( + new FieldAccessExpr(new ThisExpr, "name"), + new NameExpr("name"), + AssignExpr.Operator.ASSIGN + ) + ) + ) + ) + ) val getNameMethod = new MethodDeclaration( util.EnumSet.of(PUBLIC), @@ -126,9 +134,13 @@ object JacksonGenerator { "getName" ) getNameMethod.addMarkerAnnotation("JsonValue") - getNameMethod.setBody(new BlockStmt(new NodeList( - new ReturnStmt(new FieldAccessExpr(new ThisExpr, "name")) - ))) + getNameMethod.setBody( + new BlockStmt( + new NodeList( + new ReturnStmt(new FieldAccessExpr(new ThisExpr, "name")) + ) + ) + ) val parseMethod = new MethodDeclaration( util.EnumSet.of(PUBLIC, STATIC), @@ -137,41 +149,56 @@ object JacksonGenerator { ) parseMethod.addMarkerAnnotation("JsonCreator") parseMethod.addParameter(new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("name"))) - parseMethod.setBody(new BlockStmt(new NodeList( - new ForEachStmt( - new VariableDeclarationExpr(new VariableDeclarator(enumType, "value"), FINAL), - new MethodCallExpr("values"), - new BlockStmt(new NodeList( - new IfStmt( - new MethodCallExpr("value.name.equals", new NameExpr("name")), - new ReturnStmt(new NameExpr("value")), - null - ) - )) - ), - new ThrowStmt(new ObjectCreationExpr( - null, - JavaParser.parseClassOrInterfaceType("IllegalArgumentException"), + parseMethod.setBody( + new BlockStmt( new NodeList( - new BinaryExpr( - new BinaryExpr(new StringLiteralExpr("Name '"), new NameExpr("name"), BinaryExpr.Operator.PLUS), - new StringLiteralExpr(s"' is not valid for enum '${clsName}'"), - BinaryExpr.Operator.PLUS + new ForEachStmt( + new VariableDeclarationExpr(new VariableDeclarator(enumType, "value"), FINAL), + new MethodCallExpr("values"), + new BlockStmt( + new NodeList( + new IfStmt( + new MethodCallExpr("value.name.equals", new NameExpr("name")), + new ReturnStmt(new NameExpr("value")), + null + ) + ) + ) + ), + new ThrowStmt( + new ObjectCreationExpr( + null, + JavaParser.parseClassOrInterfaceType("IllegalArgumentException"), + new NodeList( + new BinaryExpr( + new BinaryExpr(new StringLiteralExpr("Name '"), new NameExpr("name"), BinaryExpr.Operator.PLUS), + new StringLiteralExpr(s"' is not valid for enum '${clsName}'"), + BinaryExpr.Operator.PLUS + ) + ) + ) ) ) - )) - ))) - - val staticInitializer = new InitializerDeclaration(true, new BlockStmt(new NodeList( - new ExpressionStmt(new MethodCallExpr( - new MethodCallExpr(new NameExpr("Shower"), "getInstance"), - "register", - new NodeList[Expression]( - new ClassExpr(JavaParser.parseClassOrInterfaceType(clsName)), - new MethodReferenceExpr(new NameExpr(clsName), null, "getName") + ) + ) + + val staticInitializer = new InitializerDeclaration( + true, + new BlockStmt( + new NodeList( + new ExpressionStmt( + new MethodCallExpr( + new MethodCallExpr(new NameExpr("Shower"), "getInstance"), + "register", + new NodeList[Expression]( + new ClassExpr(JavaParser.parseClassOrInterfaceType(clsName)), + new MethodReferenceExpr(new NameExpr(clsName), null, "getName") + ) + ) + ) ) - )) - ))) + ) + ) val enumClass = new EnumDeclaration( util.EnumSet.of(PUBLIC), @@ -196,11 +223,12 @@ object JacksonGenerator { "com.fasterxml.jackson.annotation.JsonCreator", "com.fasterxml.jackson.annotation.JsonValue" ).traverse(safeParseRawImport) - } yield StaticDefns[JavaLanguage]( - className = clsName, - extraImports = extraImports, - definitions = List.empty - ) + } yield + StaticDefns[JavaLanguage]( + className = clsName, + extraImports = extraImports, + definitions = List.empty + ) case BuildAccessor(clsName, termName) => Target.pure(new Name(s"${clsName}.${termName}")) @@ -238,7 +266,9 @@ object JacksonGenerator { case p: IntegerSchema if p.getFormat == "int64" => Default(p).extract[Long].traverse(x => Target.pure(new LongLiteralExpr(x))) case p: StringSchema => - Default(p).extract[String].traverse(x => Target.fromOption(Try(new StringLiteralExpr(x)).toOption, s"Default string literal for '${p.getTitle}' is null")) + Default(p) + .extract[String] + .traverse(x => Target.fromOption(Try(new StringLiteralExpr(x)).toOption, s"Default string literal for '${p.getTitle}' is null")) case _ => Target.pure(None) } @@ -273,19 +303,19 @@ object JacksonGenerator { .fold[Target[(Type, Option[Expression])]]( ( safeParseType(s"java.util.Optional<${tpe}>"), - Target.pure(Option( + Target.pure( + Option( defaultValue .fold( new MethodCallExpr(new NameExpr(s"java.util.Optional"), "empty") - )(t => - new MethodCallExpr("java.util.Optional.of", t) - ) - )) + )(t => new MethodCallExpr("java.util.Optional.of", t)) + ) + ) ).mapN((_, _)) )(Function.const(Target.pure((tpe, defaultValue))) _) (finalDeclType, finalDefaultValue) = _declDefaultPair term <- safeParseParameter(s"final ${finalDeclType} ${argName.escapeReservedWord}") - dep = classDep.filterNot(_.value == clsName) // Filter out our own class name + dep = classDep.filterNot(_.value == clsName) // Filter out our own class name } yield ProtocolParameter[JavaLanguage](term, name, dep, readOnlyKey, emptyToNull, defaultValue) } @@ -293,234 +323,323 @@ object JacksonGenerator { val dtoClassType = JavaParser.parseClassOrInterfaceType(clsName) val discriminators = parents.flatMap(_.discriminators) val parentOpt = parents.headOption - val params = (parents.reverse.flatMap(_.params) ++ selfParams).filterNot( + val params = (parents.reverse.flatMap(_.params) ++ selfParams).filterNot( param => discriminators.contains(param.term.getName().getId()) ) val (requiredTerms, optionalTerms) = sortParams(params) - val terms = requiredTerms ++ optionalTerms + val terms = requiredTerms ++ optionalTerms val dtoClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC), false, clsName) - dtoClass.addAnnotation(new NormalAnnotationExpr( - new Name("JsonIgnoreProperties"), - new NodeList(new MemberValuePair( - "ignoreUnknown", - new BooleanLiteralExpr(true) - )) - )) + dtoClass.addAnnotation( + new NormalAnnotationExpr( + new Name("JsonIgnoreProperties"), + new NodeList( + new MemberValuePair( + "ignoreUnknown", + new BooleanLiteralExpr(true) + ) + ) + ) + ) addParents(dtoClass, parentOpt) discriminators.foreach({ discriminator => val field = dtoClass.addFieldWithInitializer(STRING_TYPE, discriminator, new StringLiteralExpr(clsName), PRIVATE, FINAL) - field.addAnnotation(new NormalAnnotationExpr( - new Name("JsonProperty"), - new NodeList( - new MemberValuePair("value", new StringLiteralExpr(discriminator)), - new MemberValuePair("access", new FieldAccessExpr(new NameExpr("JsonProperty.Access"), "READ_ONLY")) + field.addAnnotation( + new NormalAnnotationExpr( + new Name("JsonProperty"), + new NodeList( + new MemberValuePair("value", new StringLiteralExpr(discriminator)), + new MemberValuePair("access", new FieldAccessExpr(new NameExpr("JsonProperty.Access"), "READ_ONLY")) + ) ) - )) + ) }) - terms.foreach({ case ParameterTerm(propertyName, parameterName, fieldType, _, _) => - val field: FieldDeclaration = dtoClass.addField(fieldType, parameterName, PRIVATE, FINAL) - field.addSingleMemberAnnotation("JsonProperty", new StringLiteralExpr(propertyName)) + terms.foreach({ + case ParameterTerm(propertyName, parameterName, fieldType, _, _) => + val field: FieldDeclaration = dtoClass.addField(fieldType, parameterName, PRIVATE, FINAL) + field.addSingleMemberAnnotation("JsonProperty", new StringLiteralExpr(propertyName)) }) val primaryConstructor = dtoClass.addConstructor(PRIVATE) primaryConstructor.addMarkerAnnotation("JsonCreator") - primaryConstructor.setParameters(new NodeList( - terms.map({ case ParameterTerm(propertyName, parameterName, fieldType, _, _) => - new Parameter(util.EnumSet.of(FINAL), fieldType, new SimpleName(parameterName)) - .addAnnotation(new SingleMemberAnnotationExpr(new Name("JsonProperty"), new StringLiteralExpr(propertyName))) - }): _* - )) + primaryConstructor.setParameters( + new NodeList( + terms.map({ + case ParameterTerm(propertyName, parameterName, fieldType, _, _) => + new Parameter(util.EnumSet.of(FINAL), fieldType, new SimpleName(parameterName)) + .addAnnotation(new SingleMemberAnnotationExpr(new Name("JsonProperty"), new StringLiteralExpr(propertyName))) + }): _* + ) + ) primaryConstructor.setBody( new BlockStmt( new NodeList( - terms.map({ case ParameterTerm(_, parameterName, fieldType, _, _) => - new ExpressionStmt(new AssignExpr( - new FieldAccessExpr(new ThisExpr, parameterName), - fieldType match { - case _: PrimitiveType => new NameExpr(parameterName) - case _ => new MethodCallExpr("requireNonNull", new NameExpr(parameterName)) - }, - AssignExpr.Operator.ASSIGN - )) + terms.map({ + case ParameterTerm(_, parameterName, fieldType, _, _) => + new ExpressionStmt( + new AssignExpr( + new FieldAccessExpr(new ThisExpr, parameterName), + fieldType match { + case _: PrimitiveType => new NameExpr(parameterName) + case _ => new MethodCallExpr("requireNonNull", new NameExpr(parameterName)) + }, + AssignExpr.Operator.ASSIGN + ) + ) }): _* ) ) ) // TODO: handle emptyToNull in the return for the getters - terms.foreach({ case ParameterTerm(_, parameterName, fieldType, _, _) => - val method = dtoClass.addMethod(s"get${parameterName.capitalize}", PUBLIC) - method.setType(fieldType) - method.setBody(new BlockStmt(new NodeList( - new ReturnStmt(new FieldAccessExpr(new ThisExpr, parameterName)) - ))) + terms.foreach({ + case ParameterTerm(_, parameterName, fieldType, _, _) => + val method = dtoClass.addMethod(s"get${parameterName.capitalize}", PUBLIC) + method.setType(fieldType) + method.setBody( + new BlockStmt( + new NodeList( + new ReturnStmt(new FieldAccessExpr(new ThisExpr, parameterName)) + ) + ) + ) }) - val toStringFieldExprs = NonEmptyList.fromList(terms).toList.flatMap(l => - (new StringLiteralExpr(s"${l.head.parameterName}="), new FieldAccessExpr(new ThisExpr, l.head.parameterName)) +: - l.tail.map(param => ( - new StringLiteralExpr(s", ${param.parameterName}="), - new FieldAccessExpr(new ThisExpr, param.parameterName) - )) - ) + val toStringFieldExprs = NonEmptyList + .fromList(terms) + .toList + .flatMap( + l => + (new StringLiteralExpr(s"${l.head.parameterName}="), new FieldAccessExpr(new ThisExpr, l.head.parameterName)) +: + l.tail.map( + param => + ( + new StringLiteralExpr(s", ${param.parameterName}="), + new FieldAccessExpr(new ThisExpr, param.parameterName) + ) + ) + ) - val toStringMethod = dtoClass.addMethod("toString", PUBLIC) + val toStringMethod = dtoClass + .addMethod("toString", PUBLIC) .setType(STRING_TYPE) .addMarkerAnnotation("Override") - toStringMethod.setBody(new BlockStmt(new NodeList(new ReturnStmt( - new BinaryExpr( - toStringFieldExprs.foldLeft[Expression](new StringLiteralExpr(s"${clsName}{"))({ case (prevExpr, (strExpr, fieldExpr)) => - new BinaryExpr( - new BinaryExpr(prevExpr, strExpr, BinaryExpr.Operator.PLUS), - fieldExpr, - BinaryExpr.Operator.PLUS + toStringMethod.setBody( + new BlockStmt( + new NodeList( + new ReturnStmt( + new BinaryExpr( + toStringFieldExprs.foldLeft[Expression](new StringLiteralExpr(s"${clsName}{"))({ + case (prevExpr, (strExpr, fieldExpr)) => + new BinaryExpr( + new BinaryExpr(prevExpr, strExpr, BinaryExpr.Operator.PLUS), + fieldExpr, + BinaryExpr.Operator.PLUS + ) + }), + new StringLiteralExpr("}"), + BinaryExpr.Operator.PLUS + ) ) - }), - new StringLiteralExpr("}"), - BinaryExpr.Operator.PLUS - ) - )))) - - val equalsConditions: List[Expression] = terms.map({ case ParameterTerm(_, parameterName, fieldType, _, _) => - fieldType match { - case _: PrimitiveType => new BinaryExpr( - new FieldAccessExpr(new ThisExpr, parameterName), - new FieldAccessExpr(new NameExpr("other"), parameterName), - BinaryExpr.Operator.EQUALS - ) - case _ => new MethodCallExpr( - new FieldAccessExpr(new ThisExpr, parameterName), - "equals", - new NodeList[Expression](new FieldAccessExpr(new NameExpr("other"), parameterName)) ) - } + ) + ) + + val equalsConditions: List[Expression] = terms.map({ + case ParameterTerm(_, parameterName, fieldType, _, _) => + fieldType match { + case _: PrimitiveType => + new BinaryExpr( + new FieldAccessExpr(new ThisExpr, parameterName), + new FieldAccessExpr(new NameExpr("other"), parameterName), + BinaryExpr.Operator.EQUALS + ) + case _ => + new MethodCallExpr( + new FieldAccessExpr(new ThisExpr, parameterName), + "equals", + new NodeList[Expression](new FieldAccessExpr(new NameExpr("other"), parameterName)) + ) + } }) - val returnExpr = NonEmptyList.fromList(equalsConditions).map(_.reduceLeft( - (prevExpr, condExpr) => new BinaryExpr(prevExpr, condExpr, BinaryExpr.Operator.AND) - )).getOrElse(new BooleanLiteralExpr(true)) + val returnExpr = NonEmptyList + .fromList(equalsConditions) + .map( + _.reduceLeft( + (prevExpr, condExpr) => new BinaryExpr(prevExpr, condExpr, BinaryExpr.Operator.AND) + ) + ) + .getOrElse(new BooleanLiteralExpr(true)) - val equalsMethod = dtoClass.addMethod("equals", PUBLIC) + val equalsMethod = dtoClass + .addMethod("equals", PUBLIC) .setType(PrimitiveType.booleanType) .addMarkerAnnotation("Override") .addParameter(new Parameter(util.EnumSet.of(FINAL), OBJECT_TYPE, new SimpleName("o"))) - equalsMethod.setBody(new BlockStmt(new NodeList( - new IfStmt( - new BinaryExpr(new ThisExpr, new NameExpr("o"), BinaryExpr.Operator.EQUALS), - new BlockStmt(new NodeList(new ReturnStmt(new BooleanLiteralExpr(true)))), - null - ), - new IfStmt( - new BinaryExpr( - new BinaryExpr(new NameExpr("o"), new NullLiteralExpr, BinaryExpr.Operator.EQUALS), - new BinaryExpr(new MethodCallExpr("getClass"), new MethodCallExpr(new NameExpr("o"), "getClass"), BinaryExpr.Operator.NOT_EQUALS), - BinaryExpr.Operator.OR - ), - new BlockStmt(new NodeList(new ReturnStmt(new BooleanLiteralExpr(false)))), - null - ), - new ExpressionStmt(new VariableDeclarationExpr(new VariableDeclarator( - dtoClassType, "other", new CastExpr(dtoClassType, new NameExpr("o")) - ), FINAL)), - new ReturnStmt(returnExpr) - ))) - - val hashCodeMethod = dtoClass.addMethod("hashCode", PUBLIC) + equalsMethod.setBody( + new BlockStmt( + new NodeList( + new IfStmt( + new BinaryExpr(new ThisExpr, new NameExpr("o"), BinaryExpr.Operator.EQUALS), + new BlockStmt(new NodeList(new ReturnStmt(new BooleanLiteralExpr(true)))), + null + ), + new IfStmt( + new BinaryExpr( + new BinaryExpr(new NameExpr("o"), new NullLiteralExpr, BinaryExpr.Operator.EQUALS), + new BinaryExpr(new MethodCallExpr("getClass"), new MethodCallExpr(new NameExpr("o"), "getClass"), BinaryExpr.Operator.NOT_EQUALS), + BinaryExpr.Operator.OR + ), + new BlockStmt(new NodeList(new ReturnStmt(new BooleanLiteralExpr(false)))), + null + ), + new ExpressionStmt( + new VariableDeclarationExpr(new VariableDeclarator( + dtoClassType, + "other", + new CastExpr(dtoClassType, new NameExpr("o")) + ), + FINAL) + ), + new ReturnStmt(returnExpr) + ) + ) + ) + + val hashCodeMethod = dtoClass + .addMethod("hashCode", PUBLIC) .setType(PrimitiveType.intType) .addMarkerAnnotation("Override") - hashCodeMethod.setBody(new BlockStmt(new NodeList(new ReturnStmt(new MethodCallExpr( - new NameExpr("java.util.Objects"), - "hash", - new NodeList[Expression](terms.map(term => new FieldAccessExpr(new ThisExpr, term.parameterName)): _*) - ))))) + hashCodeMethod.setBody( + new BlockStmt( + new NodeList( + new ReturnStmt( + new MethodCallExpr( + new NameExpr("java.util.Objects"), + "hash", + new NodeList[Expression](terms.map(term => new FieldAccessExpr(new ThisExpr, term.parameterName)): _*) + ) + ) + ) + ) + ) val builderMethod = dtoClass.addMethod("builder", PUBLIC, STATIC) builderMethod.setType("Builder") - builderMethod.setParameters(new NodeList( - requiredTerms.map({ case ParameterTerm(_, parameterName, _, parameterType, _) => - new Parameter(util.EnumSet.of(FINAL), parameterType, new SimpleName(parameterName)) - }): _* - )) - builderMethod.setBody(new BlockStmt(new NodeList( - new ReturnStmt( - new ObjectCreationExpr( - null, - JavaParser.parseClassOrInterfaceType("Builder"), - new NodeList(requiredTerms.map({ - case ParameterTerm(_, parameterName, _, _, _) => new NameExpr(parameterName) - }): _*) + builderMethod.setParameters( + new NodeList( + requiredTerms.map({ + case ParameterTerm(_, parameterName, _, parameterType, _) => + new Parameter(util.EnumSet.of(FINAL), parameterType, new SimpleName(parameterName)) + }): _* + ) + ) + builderMethod.setBody( + new BlockStmt( + new NodeList( + new ReturnStmt( + new ObjectCreationExpr( + null, + JavaParser.parseClassOrInterfaceType("Builder"), + new NodeList(requiredTerms.map({ + case ParameterTerm(_, parameterName, _, _, _) => new NameExpr(parameterName) + }): _*) + ) + ) ) ) - ))) + ) val builderClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, STATIC), false, "Builder") - requiredTerms.foreach({ case ParameterTerm(_, parameterName, fieldType, _, _) => - builderClass.addField(fieldType, parameterName, PRIVATE, FINAL) + requiredTerms.foreach({ + case ParameterTerm(_, parameterName, fieldType, _, _) => + builderClass.addField(fieldType, parameterName, PRIVATE, FINAL) }) - optionalTerms.foreach({ case ParameterTerm(_, parameterName, fieldType, _, defaultValue) => - val initializer = defaultValue.fold[Expression]( - new MethodCallExpr(new NameExpr("java.util.Optional"), "empty") - )(dv => - if (fieldType.isOptional) { - new MethodCallExpr(new NameExpr("java.util.Optional"), "of", new NodeList[Expression](dv)) - } else { - dv - } - ) - builderClass.addFieldWithInitializer(fieldType, parameterName, initializer, PRIVATE) + optionalTerms.foreach({ + case ParameterTerm(_, parameterName, fieldType, _, defaultValue) => + val initializer = defaultValue.fold[Expression]( + new MethodCallExpr(new NameExpr("java.util.Optional"), "empty") + )( + dv => + if (fieldType.isOptional) { + new MethodCallExpr(new NameExpr("java.util.Optional"), "of", new NodeList[Expression](dv)) + } else { + dv + } + ) + builderClass.addFieldWithInitializer(fieldType, parameterName, initializer, PRIVATE) }) val builderConstructor = builderClass.addConstructor(PRIVATE) - builderConstructor.setParameters(new NodeList( - requiredTerms.map({ case ParameterTerm(_, parameterName, _, parameterType, _) => - new Parameter(util.EnumSet.of(FINAL), parameterType, new SimpleName(parameterName)) - }): _* - )) - builderConstructor.setBody(new BlockStmt(new NodeList( - requiredTerms.map({ case ParameterTerm(_, parameterName, fieldType, _, _) => - new ExpressionStmt( - new AssignExpr( - new FieldAccessExpr(new ThisExpr, parameterName), - fieldType match { - case _: PrimitiveType => new NameExpr(parameterName) - case _ => new MethodCallExpr("requireNonNull", new NameExpr(parameterName)) - }, - AssignExpr.Operator.ASSIGN - ) + builderConstructor.setParameters( + new NodeList( + requiredTerms.map({ + case ParameterTerm(_, parameterName, _, parameterType, _) => + new Parameter(util.EnumSet.of(FINAL), parameterType, new SimpleName(parameterName)) + }): _* + ) + ) + builderConstructor.setBody( + new BlockStmt( + new NodeList( + requiredTerms.map({ + case ParameterTerm(_, parameterName, fieldType, _, _) => + new ExpressionStmt( + new AssignExpr( + new FieldAccessExpr(new ThisExpr, parameterName), + fieldType match { + case _: PrimitiveType => new NameExpr(parameterName) + case _ => new MethodCallExpr("requireNonNull", new NameExpr(parameterName)) + }, + AssignExpr.Operator.ASSIGN + ) + ) + }): _* ) - }): _* - ))) + ) + ) // TODO: leave out with${name}() if readOnlyKey? - optionalTerms.foreach({ case ParameterTerm(_, parameterName, fieldType, parameterType, _) => - val setter = builderClass.addMethod(s"with${parameterName.capitalize}", PUBLIC) - setter.setType("Builder") - setter.addAndGetParameter(new Parameter(util.EnumSet.of(FINAL), parameterType, new SimpleName(parameterName))) - setter.setBody(new BlockStmt(new NodeList( - new ExpressionStmt( - new AssignExpr( - new FieldAccessExpr(new ThisExpr, parameterName), - if (fieldType.isOptional) new MethodCallExpr("java.util.Optional.of", new NameExpr(parameterName)) else new NameExpr(parameterName), - AssignExpr.Operator.ASSIGN + optionalTerms.foreach({ + case ParameterTerm(_, parameterName, fieldType, parameterType, _) => + val setter = builderClass.addMethod(s"with${parameterName.capitalize}", PUBLIC) + setter.setType("Builder") + setter.addAndGetParameter(new Parameter(util.EnumSet.of(FINAL), parameterType, new SimpleName(parameterName))) + setter.setBody( + new BlockStmt( + new NodeList( + new ExpressionStmt( + new AssignExpr( + new FieldAccessExpr(new ThisExpr, parameterName), + if (fieldType.isOptional) new MethodCallExpr("java.util.Optional.of", new NameExpr(parameterName)) else new NameExpr(parameterName), + AssignExpr.Operator.ASSIGN + ) + ), + new ReturnStmt(new ThisExpr) + ) ) - ), - new ReturnStmt(new ThisExpr) - ))) + ) }) val buildMethod = builderClass.addMethod("build", PUBLIC) buildMethod.setType(clsName) - buildMethod.setBody(new BlockStmt(new NodeList( - new ReturnStmt(new ObjectCreationExpr( - null, - JavaParser.parseClassOrInterfaceType(clsName), - new NodeList(terms.map(param => new FieldAccessExpr(new ThisExpr, param.parameterName)): _*) - )) - ))) + buildMethod.setBody( + new BlockStmt( + new NodeList( + new ReturnStmt( + new ObjectCreationExpr( + null, + JavaParser.parseClassOrInterfaceType(clsName), + new NodeList(terms.map(param => new FieldAccessExpr(new ThisExpr, param.parameterName)): _*) + ) + ) + ) + ) + ) dtoClass.addMember(builderClass) @@ -548,7 +667,9 @@ object JacksonGenerator { case SwaggerUtil.DeferredArray(tpeName) => Target.fromOption(lookupTypeName(tpeName, concreteTypes)(tpe => safeParseType(s"Array<${tpe}>")), s"Unresolved reference ${tpeName}").flatten case SwaggerUtil.DeferredMap(tpeName) => - Target.fromOption(lookupTypeName(tpeName, concreteTypes)(tpe => safeParseType(s"Array>")), s"Unresolved reference ${tpeName}").flatten + Target + .fromOption(lookupTypeName(tpeName, concreteTypes)(tpe => safeParseType(s"Array>")), s"Unresolved reference ${tpeName}") + .flatten } } yield result } @@ -604,11 +725,12 @@ object JacksonGenerator { "com.fasterxml.jackson.annotation.JsonSubTypes", "com.fasterxml.jackson.annotation.JsonTypeInfo" ).traverse(safeParseRawImport) - } yield StaticDefns[JavaLanguage]( - clsName, - extraImports, - List.empty - ) + } yield + StaticDefns[JavaLanguage]( + clsName, + extraImports, + List.empty + ) case DecodeADT(clsName, children) => Target.pure(None) @@ -617,55 +739,67 @@ object JacksonGenerator { Target.pure(None) case RenderSealedTrait(className, selfParams, discriminator, parents, children) => - val parentOpt = parents.headOption - val params = (parents.reverse.flatMap(_.params) ++ selfParams).filterNot(_.term.getName.getId == discriminator) + val parentOpt = parents.headOption + val params = (parents.reverse.flatMap(_.params) ++ selfParams).filterNot(_.term.getName.getId == discriminator) val (requiredTerms, optionalTerms) = sortParams(params) - val terms = requiredTerms ++ optionalTerms + val terms = requiredTerms ++ optionalTerms val abstractClass = new ClassOrInterfaceDeclaration(util.EnumSet.of(PUBLIC, ABSTRACT), false, className) - abstractClass.addAnnotation(new NormalAnnotationExpr( - new Name("JsonIgnoreProperties"), - new NodeList(new MemberValuePair( - "ignoreUnknown", - new BooleanLiteralExpr(true) - )) - )) - abstractClass.addAnnotation(new NormalAnnotationExpr( - new Name("JsonTypeInfo"), - new NodeList( - new MemberValuePair( - "use", - new FieldAccessExpr(new NameExpr("JsonTypeInfo.Id"), "NAME") - ), - new MemberValuePair( - "include", - new FieldAccessExpr(new NameExpr("JsonTypeInfo.As"), "PROPERTY") - ), - new MemberValuePair( - "property", - new StringLiteralExpr(discriminator) + abstractClass.addAnnotation( + new NormalAnnotationExpr( + new Name("JsonIgnoreProperties"), + new NodeList( + new MemberValuePair( + "ignoreUnknown", + new BooleanLiteralExpr(true) + ) ) ) - )) + ) + abstractClass.addAnnotation( + new NormalAnnotationExpr( + new Name("JsonTypeInfo"), + new NodeList( + new MemberValuePair( + "use", + new FieldAccessExpr(new NameExpr("JsonTypeInfo.Id"), "NAME") + ), + new MemberValuePair( + "include", + new FieldAccessExpr(new NameExpr("JsonTypeInfo.As"), "PROPERTY") + ), + new MemberValuePair( + "property", + new StringLiteralExpr(discriminator) + ) + ) + ) + ) abstractClass.addSingleMemberAnnotation( "JsonSubTypes", - new ArrayInitializerExpr(new NodeList( - children.map(child => new NormalAnnotationExpr( - new Name("JsonSubTypes.Type"), - new NodeList( - new MemberValuePair("name", new StringLiteralExpr(child)), - new MemberValuePair("value", new ClassExpr(JavaParser.parseType(child))) - ) - )): _* - )) + new ArrayInitializerExpr( + new NodeList( + children.map( + child => + new NormalAnnotationExpr( + new Name("JsonSubTypes.Type"), + new NodeList( + new MemberValuePair("name", new StringLiteralExpr(child)), + new MemberValuePair("value", new ClassExpr(JavaParser.parseType(child))) + ) + ) + ): _* + ) + ) ) addParents(abstractClass, parentOpt) - terms.foreach({ case ParameterTerm(_, parameterName, fieldType, _, _) => - val method: MethodDeclaration = abstractClass.addMethod(s"get${parameterName.capitalize}", PUBLIC, ABSTRACT) - method.setType(fieldType) - method.setBody(null) + terms.foreach({ + case ParameterTerm(_, parameterName, fieldType, _, _) => + val method: MethodDeclaration = abstractClass.addMethod(s"get${parameterName.capitalize}", PUBLIC, ABSTRACT) + method.setType(fieldType) + method.setBody(null) }) Target.pure(abstractClass) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala index 599f5cd9a2..d204e928b0 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/JavaGenerator.scala @@ -5,8 +5,8 @@ import cats.instances.list._ import cats.instances.option._ import cats.syntax.traverse._ import com.github.javaparser.ast._ -import com.github.javaparser.ast.`type`.{ClassOrInterfaceType, PrimitiveType, Type, VoidType, ArrayType => AstArrayType} -import com.github.javaparser.ast.body.{BodyDeclaration, Parameter, TypeDeclaration} +import com.github.javaparser.ast.`type`.{ ClassOrInterfaceType, PrimitiveType, Type, VoidType, ArrayType => AstArrayType } +import com.github.javaparser.ast.body.{ BodyDeclaration, Parameter, TypeDeclaration } import com.github.javaparser.ast.expr._ import com.github.javaparser.ast.stmt.Statement import com.twilio.guardrail._ @@ -23,8 +23,8 @@ object JavaGenerator { def buildMethodCall(name: String, arg: Option[Node] = None): Target[Node] = arg match { case Some(expr: Expression) => Target.pure(new MethodCallExpr(name, expr)) - case None => Target.pure(new MethodCallExpr(name)) - case other => Target.raiseError(s"Need expression to call '${name}' but got a ${other.getClass.getName} instead") + case None => Target.pure(new MethodCallExpr(name)) + case other => Target.raiseError(s"Need expression to call '${name}' but got a ${other.getClass.getName} instead") } def apply[T](term: ScalaTerm[JavaLanguage, T]): Target[T] = term match { @@ -99,10 +99,10 @@ object JavaGenerator { case ExtractTypeName(tpe) => def extractTypeName(tpe: Type): Target[Name] = tpe match { case a: AstArrayType if a.getComponentType.isPrimitiveType => extractTypeName(new AstArrayType(a.getComponentType.asPrimitiveType().toBoxedType)) - case a: AstArrayType => safeParseName(a.asString) - case ci: ClassOrInterfaceType => safeParseName(ci.getNameAsString) - case p: PrimitiveType => safeParseName(p.toBoxedType.getNameAsString) - case _: VoidType => safeParseName("Void") + case a: AstArrayType => safeParseName(a.asString) + case ci: ClassOrInterfaceType => safeParseName(ci.getNameAsString) + case p: PrimitiveType => safeParseName(p.toBoxedType.getNameAsString) + case _: VoidType => safeParseName("Void") case _ => println(s"WARN: ExtractTypeName: unhandled type ${tpe.getClass.getName}") safeParseName("Void") @@ -114,15 +114,17 @@ object JavaGenerator { Target.pure(term.asString) case AlterMethodParameterName(param, name) => - safeParseSimpleName(name.asString.escapeReservedWord).map(new Parameter( - param.getTokenRange.orElse(null), - param.getModifiers, - param.getAnnotations, - param.getType, - param.isVarArgs, - param.getVarArgsAnnotations, - _ - )) + safeParseSimpleName(name.asString.escapeReservedWord).map( + new Parameter( + param.getTokenRange.orElse(null), + param.getModifiers, + param.getAnnotations, + param.getType, + param.isVarArgs, + param.getVarArgsAnnotations, + _ + ) + ) case DateType() => safeParseType("java.time.LocalDate") case DateTimeType() => safeParseType("java.time.OffsetDateTime") @@ -163,14 +165,17 @@ object JavaGenerator { case WritePackageObject(dtoPackagePath, dtoComponents, customImports, packageObjectImports, protocolImports, packageObjectContents, extraTypes) => for { pkgDecl <- buildPkgDecl(dtoComponents) - } yield Some(WriteTree( - resolveFile(dtoPackagePath)(List.empty).resolve("package-info.java"), - pkgDecl.toString(printer).getBytes(StandardCharsets.UTF_8) - )) + } yield + Some( + WriteTree( + resolveFile(dtoPackagePath)(List.empty).resolve("package-info.java"), + pkgDecl.toString(printer).getBytes(StandardCharsets.UTF_8) + ) + ) case WriteProtocolDefinition(outputPath, pkgName, definitions, dtoComponents, imports, elem) => for { - pkgDecl <- buildPkgDecl(definitions) + pkgDecl <- buildPkgDecl(definitions) showerImport <- safeParseRawImport((pkgName :+ "Shower").mkString(".")) } yield { elem match { @@ -241,8 +246,8 @@ object JavaGenerator { dtoComponents, Client(pkg, clientName, imports, staticDefns, client, responseDefinitions)) => for { - pkgDecl <- buildPkgDecl(pkgName ++ pkg) - commonImport <- safeParseRawImport((pkgName :+ "*").mkString(".")) + pkgDecl <- buildPkgDecl(pkgName ++ pkg) + commonImport <- safeParseRawImport((pkgName :+ "*").mkString(".")) dtoComponentsImport <- safeParseRawImport((dtoComponents :+ "*").mkString(".")) } yield { val cu = new CompilationUnit() @@ -261,7 +266,12 @@ object JavaGenerator { ) } - case WriteServer(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, Server(pkg, extraImports, handlerDefinition, serverDefinitions)) => + case WriteServer(pkgPath, + pkgName, + customImports, + frameworkImplicitName, + dtoComponents, + Server(pkg, extraImports, handlerDefinition, serverDefinitions)) => def writeClass(pkgDecl: PackageDeclaration, extraImports: List[ImportDeclaration], definition: TypeDeclaration[_]): WriteTree = { val cu = new CompilationUnit() cu.setPackageDeclaration(pkgDecl) @@ -274,17 +284,16 @@ object JavaGenerator { ) } - def writeDefinition(pkgDecl: PackageDeclaration, extraImports: List[ImportDeclaration], definition: BodyDeclaration[_]): Target[WriteTree] = { + def writeDefinition(pkgDecl: PackageDeclaration, extraImports: List[ImportDeclaration], definition: BodyDeclaration[_]): Target[WriteTree] = definition match { case td: TypeDeclaration[_] => Target.pure(writeClass(pkgDecl, extraImports, td)) - case other => Target.raiseError(s"Class definition must be a TypeDeclaration but it is a ${other.getClass.getName}") + case other => Target.raiseError(s"Class definition must be a TypeDeclaration but it is a ${other.getClass.getName}") } - } for { pkgDecl <- buildPkgDecl(pkgName ++ pkg) - commonImport <- safeParseRawImport((pkgName :+ "*").mkString(".")) + commonImport <- safeParseRawImport((pkgName :+ "*").mkString(".")) dtoComponentsImport <- safeParseRawImport((dtoComponents :+ "*").mkString(".")) allExtraImports = extraImports ++ List(commonImport, dtoComponentsImport) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala index 454931dc30..e8728e35b0 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaGenerator.scala @@ -254,10 +254,11 @@ object ScalaGenerator { stat.copy(rhs = q"${companion}.${mirror}") }) - Target.pure(Some( - WriteTree( - dtoPackagePath.resolve("package.scala"), - source""" + Target.pure( + Some( + WriteTree( + dtoPackagePath.resolve("package.scala"), + source""" package ${dtoPkg} ..${customImports ++ packageObjectImports ++ protocolImports} @@ -270,8 +271,9 @@ object ScalaGenerator { ..${(mirroredImplicits ++ statements ++ extraTypes).to[List]} } """.syntax.getBytes(StandardCharsets.UTF_8) + ) ) - )) + ) case WriteProtocolDefinition(outputPath, pkgName, definitions, dtoComponents, imports, elem) => Target.pure(elem match { case EnumDefinition(_, _, _, cls, staticDefns) => @@ -349,11 +351,17 @@ object ScalaGenerator { """.syntax.getBytes(StandardCharsets.UTF_8) ) ) - case WriteServer(pkgPath, pkgName, customImports, frameworkImplicitName, dtoComponents, Server(pkg, extraImports, handlerDefinition, serverDefinitions)) => + case WriteServer(pkgPath, + pkgName, + customImports, + frameworkImplicitName, + dtoComponents, + Server(pkg, extraImports, handlerDefinition, serverDefinitions)) => Target.pure( - List(WriteTree( - resolveFile(pkgPath)(pkg.toList :+ "Routes.scala"), - source""" + List( + WriteTree( + resolveFile(pkgPath)(pkg.toList :+ "Routes.scala"), + source""" package ${buildPkgTerm((pkgName ++ pkg.toList))} ..${extraImports} import ${buildPkgTerm(List("_root_") ++ pkgName ++ List("Implicits"))}._ @@ -363,7 +371,8 @@ object ScalaGenerator { ${handlerDefinition} ..${serverDefinitions} """.syntax.getBytes(StandardCharsets.UTF_8) - )) + ) + ) ) } } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaParameter.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaParameter.scala index 2385f92500..3a89715400 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaParameter.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/ScalaParameter.scala @@ -3,20 +3,20 @@ package generators import io.swagger.v3.oas.models.media.Schema import io.swagger.v3.oas.models.parameters._ -import com.twilio.guardrail.extract.{Default, ScalaFileHashAlgorithm} +import com.twilio.guardrail.extract.{ Default, ScalaFileHashAlgorithm } import com.twilio.guardrail.generators.syntax.RichString import com.twilio.guardrail.languages.LA import com.twilio.guardrail.languages.ScalaLanguage import com.twilio.guardrail.shims._ -import com.twilio.guardrail.terms.{ScalaTerm, ScalaTerms, SwaggerTerm, SwaggerTerms} -import com.twilio.guardrail.terms.framework.{FrameworkTerm, FrameworkTerms} +import com.twilio.guardrail.terms.{ ScalaTerm, ScalaTerms, SwaggerTerm, SwaggerTerms } +import com.twilio.guardrail.terms.framework.{ FrameworkTerm, FrameworkTerms } import java.util.Locale import scala.meta._ import cats.MonadError import cats.implicits._ import cats.arrow.FunctionK import cats.free.Free -import cats.data.{EitherK, EitherT} +import cats.data.{ EitherK, EitherT } import com.twilio.guardrail.SwaggerUtil.ResolvedType import com.twilio.guardrail.extract.VendorExtension.VendorExtensible @@ -80,7 +80,7 @@ object ScalaParameter { def resolveParam(param: Parameter, typeFetcher: Parameter => Free[F, String]): Free[F, ResolvedType[L]] = for { - tpeName <- typeFetcher(param) + tpeName <- typeFetcher(param) customTypeName <- SwaggerUtil.customTypeName(param) res <- (SwaggerUtil.typeName[L, F](tpeName, Option(param.format()), customTypeName), getDefault(param)) .mapN(SwaggerUtil.Resolved[L](_, None, _)) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index ac5de287a2..7aef6b99ff 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -1,10 +1,10 @@ package com.twilio.guardrail.generators.syntax import com.github.javaparser.JavaParser -import com.github.javaparser.ast.`type`.{ClassOrInterfaceType, Type} -import com.github.javaparser.ast.body.{ClassOrInterfaceDeclaration, Parameter} -import com.github.javaparser.ast.expr.{Expression, Name, SimpleName} -import com.github.javaparser.ast.{CompilationUnit, ImportDeclaration, Node, NodeList} +import com.github.javaparser.ast.`type`.{ ClassOrInterfaceType, Type } +import com.github.javaparser.ast.body.{ ClassOrInterfaceDeclaration, Parameter } +import com.github.javaparser.ast.expr.{ Expression, Name, SimpleName } +import com.github.javaparser.ast.{ CompilationUnit, ImportDeclaration, Node, NodeList } import com.github.javaparser.printer.PrettyPrinterConfiguration import com.github.javaparser.printer.PrettyPrinterConfiguration.IndentType import com.twilio.guardrail.Target @@ -30,13 +30,13 @@ object Java { def containedType: Type = tpe match { case cls: ClassOrInterfaceType => cls.getTypeArguments.asScala.filter(_.size == 1).fold(tpe)(_.get(0)) - case _ => tpe + case _ => tpe } def unbox: Type = tpe match { case cls: ClassOrInterfaceType if cls.isBoxedType => cls.toUnboxedType - case _ => tpe + case _ => tpe } def isNamed(name: String): Boolean = @@ -44,7 +44,7 @@ object Java { case cls: ClassOrInterfaceType if name.contains(".") => (cls.getScope.asScala.fold("")(_.getName.asString + ".") + cls.getNameAsString) == name case cls: ClassOrInterfaceType => cls.getNameAsString == name - case _ => false + case _ => false } def name: Option[String] = @@ -59,31 +59,32 @@ object Java { def toNodeList: NodeList[T] = new NodeList[T](l: _*) } - private[this] def safeParse[T](log: String)(parser: String => T, s: String)(implicit cls: ClassTag[T]): Target[T] = { + private[this] def safeParse[T](log: String)(parser: String => T, s: String)(implicit cls: ClassTag[T]): Target[T] = Target.log.function(s"${log}: ${s}") { Try(parser(s)).toEither.fold(t => Target.raiseError(s"Unable to parse '${s}' to a ${cls.runtimeClass.getName}: ${t.getMessage}"), Target.pure) } - } - def safeParseCode(s: String): Target[CompilationUnit] = safeParse("safeParseCode")(JavaParser.parse, s) - def safeParseSimpleName(s: String): Target[SimpleName] = safeParse("safeParseSimpleName")(JavaParser.parseSimpleName, s) - def safeParseName(s: String): Target[Name] = safeParse("safeParseName")(JavaParser.parseName, s) - def safeParseType(s: String): Target[Type] = safeParse("safeParseType")(JavaParser.parseType, s) - def safeParseClassOrInterfaceType(s: String): Target[ClassOrInterfaceType] = safeParse("safeParseClassOrInterfaceType")(JavaParser.parseClassOrInterfaceType, s) - def safeParseExpression[T <: Expression](s: String)(implicit cls: ClassTag[T]): Target[T] = safeParse[T]("safeParseExpression")(JavaParser.parseExpression[T], s) - def safeParseParameter(s: String): Target[Parameter] = safeParse("safeParseParameter")(JavaParser.parseParameter, s) - def safeParseImport(s: String): Target[ImportDeclaration] = safeParse("safeParseImport")(JavaParser.parseImport, s) - def safeParseRawImport(s: String): Target[ImportDeclaration] = safeParse("safeParseRawImport")(JavaParser.parseImport, s"import ${s};") - def safeParseRawStaticImport(s: String): Target[ImportDeclaration] = safeParse("safeParseStaticImport")(JavaParser.parseImport, s"import static ${s};") - - def completionStageType(of: Type): ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("CompletionStage" ).setTypeArguments(of) - def optionalType(of: Type): ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Optional").setTypeArguments(of) + def safeParseCode(s: String): Target[CompilationUnit] = safeParse("safeParseCode")(JavaParser.parse, s) + def safeParseSimpleName(s: String): Target[SimpleName] = safeParse("safeParseSimpleName")(JavaParser.parseSimpleName, s) + def safeParseName(s: String): Target[Name] = safeParse("safeParseName")(JavaParser.parseName, s) + def safeParseType(s: String): Target[Type] = safeParse("safeParseType")(JavaParser.parseType, s) + def safeParseClassOrInterfaceType(s: String): Target[ClassOrInterfaceType] = + safeParse("safeParseClassOrInterfaceType")(JavaParser.parseClassOrInterfaceType, s) + def safeParseExpression[T <: Expression](s: String)(implicit cls: ClassTag[T]): Target[T] = + safeParse[T]("safeParseExpression")(JavaParser.parseExpression[T], s) + def safeParseParameter(s: String): Target[Parameter] = safeParse("safeParseParameter")(JavaParser.parseParameter, s) + def safeParseImport(s: String): Target[ImportDeclaration] = safeParse("safeParseImport")(JavaParser.parseImport, s) + def safeParseRawImport(s: String): Target[ImportDeclaration] = safeParse("safeParseRawImport")(JavaParser.parseImport, s"import ${s};") + def safeParseRawStaticImport(s: String): Target[ImportDeclaration] = safeParse("safeParseStaticImport")(JavaParser.parseImport, s"import static ${s};") + + def completionStageType(of: Type): ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("CompletionStage").setTypeArguments(of) + def optionalType(of: Type): ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Optional").setTypeArguments(of) def functionType(in: Type, out: Type): ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Function").setTypeArguments(in, out) - def supplierType(of: Type): ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Supplier").setTypeArguments(of) + def supplierType(of: Type): ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Supplier").setTypeArguments(of) - val OBJECT_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Object") - val STRING_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("String") - val THROWABLE_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Throwable") + val OBJECT_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Object") + val STRING_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("String") + val THROWABLE_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("Throwable") val ASSERTION_ERROR_TYPE: ClassOrInterfaceType = JavaParser.parseClassOrInterfaceType("AssertionError") val printer: PrettyPrinterConfiguration = new PrettyPrinterConfiguration() @@ -98,29 +99,83 @@ object Java { // from https://en.wikipedia.org/wiki/List_of_Java_keywords private val reservedWords = Set( - "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", - "do", "double", "else", "enum", "exports", "extends", "false", "final", "finally", "float", "for", "goto", "if", - "implements", "import", "instanceof", "int", "interface", "long", "module", "native", "new", "null", "package", - "private", "protected", "public", "requires", "return", "short", "static", "strictfp", "super", "switch", - "synchronized", "this", "throw", "throws", "transient", "true", "try", "var", "void", "volatile", "while" + "abstract", + "assert", + "boolean", + "break", + "byte", + "case", + "catch", + "char", + "class", + "const", + "continue", + "default", + "do", + "double", + "else", + "enum", + "exports", + "extends", + "false", + "final", + "finally", + "float", + "for", + "goto", + "if", + "implements", + "import", + "instanceof", + "int", + "interface", + "long", + "module", + "native", + "new", + "null", + "package", + "private", + "protected", + "public", + "requires", + "return", + "short", + "static", + "strictfp", + "super", + "switch", + "synchronized", + "this", + "throw", + "throws", + "transient", + "true", + "try", + "var", + "void", + "volatile", + "while" ) implicit class RichJavaString(val s: String) extends AnyVal { def escapeReservedWord: String = if (reservedWords.contains(s)) s + "_" else s - def unescapeReservedWord: String = if (s.endsWith("_")) { - val prefix = s.substring(0, s.length - 1) - if (reservedWords.contains(prefix)) { - prefix + def unescapeReservedWord: String = + if (s.endsWith("_")) { + val prefix = s.substring(0, s.length - 1) + if (reservedWords.contains(prefix)) { + prefix + } else { + s + } } else { s } - } else { - s - } } lazy val SHOWER_CLASS_DEF: ClassOrInterfaceDeclaration = { - JavaParser.parseResource(getClass.getClassLoader, "java/Shower.java", StandardCharsets.UTF_8) + JavaParser + .parseResource(getClass.getClassLoader, "java/Shower.java", StandardCharsets.UTF_8) .getClassByName("Shower") .asScala .getOrElse( diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Scala.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Scala.scala index b1fa41e595..3623257c91 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Scala.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Scala.scala @@ -1,8 +1,8 @@ package com.twilio.guardrail.generators.syntax import cats.data.NonEmptyList -import com.twilio.guardrail.{StaticDefns, SwaggerUtil, Target} -import com.twilio.guardrail.generators.{RawParameterName, ScalaParameter} +import com.twilio.guardrail.{ StaticDefns, SwaggerUtil, Target } +import com.twilio.guardrail.generators.{ RawParameterName, ScalaParameter } import com.twilio.guardrail.languages.ScalaLanguage import scala.meta._ diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/package.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/package.scala index 69430995ad..81df4e0b26 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/package.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/package.scala @@ -23,7 +23,7 @@ package object syntax { } def toSnakeCase: String = { - val noPascal = "^[A-Z]".r.replaceAllIn(s, _.group(0).toLowerCase(Locale.US)) + val noPascal = "^[A-Z]".r.replaceAllIn(s, _.group(0).toLowerCase(Locale.US)) val fromCamel = "[A-Z]".r.replaceAllIn(noPascal, "_" + _.group(0)) fromCamel.replaceAllLiterally("-", "_") } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/client/ClientTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/client/ClientTerm.scala index e647817bc5..e1e9643129 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/client/ClientTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/client/ClientTerm.scala @@ -4,7 +4,7 @@ import cats.data.NonEmptyList import com.twilio.guardrail.generators.{ Responses, ScalaParameters } import com.twilio.guardrail.languages.LA import com.twilio.guardrail.terms.RouteMeta -import com.twilio.guardrail.{RenderedClientOperation, StaticDefns, StrictProtocolElems, SupportDefinition} +import com.twilio.guardrail.{ RenderedClientOperation, StaticDefns, StrictProtocolElems, SupportDefinition } import java.net.URI sealed trait ClientTerm[L <: LA, T] diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/client/ClientTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/client/ClientTerms.scala index 3c2bcb99b7..98a3fc33c9 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/client/ClientTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/client/ClientTerms.scala @@ -6,7 +6,7 @@ import cats.free.Free import com.twilio.guardrail.generators.{ Responses, ScalaParameters } import com.twilio.guardrail.languages.LA import com.twilio.guardrail.terms.RouteMeta -import com.twilio.guardrail.{RenderedClientOperation, StaticDefns, StrictProtocolElems, SupportDefinition} +import com.twilio.guardrail.{ RenderedClientOperation, StaticDefns, StrictProtocolElems, SupportDefinition } import java.net.URI class ClientTerms[L <: LA, F[_]](implicit I: InjectK[ClientTerm[L, ?], F]) { diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerm.scala index 688107579c..1e7e58b6dd 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerm.scala @@ -5,14 +5,11 @@ import com.twilio.guardrail.StaticDefns import com.twilio.guardrail.languages.LA sealed trait EnumProtocolTerm[L <: LA, T] -case class ExtractEnum[L <: LA](swagger: Schema[_]) extends EnumProtocolTerm[L, Either[String, List[String]]] -case class RenderMembers[L <: LA](clsName: String, elems: List[(String, L#TermName, L#TermSelect)]) extends EnumProtocolTerm[L, Option[L#ObjectDefinition]] -case class EncodeEnum[L <: LA](clsName: String) extends EnumProtocolTerm[L, Option[L#ValueDefinition]] -case class DecodeEnum[L <: LA](clsName: String) extends EnumProtocolTerm[L, Option[L#ValueDefinition]] -case class RenderClass[L <: LA](clsName: String, - tpe: L#Type, - elems: List[(String, L#TermName, L#TermSelect)]) - extends EnumProtocolTerm[L, L#ClassDefinition] +case class ExtractEnum[L <: LA](swagger: Schema[_]) extends EnumProtocolTerm[L, Either[String, List[String]]] +case class RenderMembers[L <: LA](clsName: String, elems: List[(String, L#TermName, L#TermSelect)]) extends EnumProtocolTerm[L, Option[L#ObjectDefinition]] +case class EncodeEnum[L <: LA](clsName: String) extends EnumProtocolTerm[L, Option[L#ValueDefinition]] +case class DecodeEnum[L <: LA](clsName: String) extends EnumProtocolTerm[L, Option[L#ValueDefinition]] +case class RenderClass[L <: LA](clsName: String, tpe: L#Type, elems: List[(String, L#TermName, L#TermSelect)]) extends EnumProtocolTerm[L, L#ClassDefinition] case class RenderStaticDefns[L <: LA](clsName: String, members: Option[L#ObjectDefinition], accessors: List[L#TermName], diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerms.scala index 7a23c4deb4..3a9e310ac2 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerms.scala @@ -29,7 +29,10 @@ class ModelProtocolTerms[L <: LA, F[_]](implicit I: InjectK[ModelProtocolTerm[L, params: List[ProtocolParameter[L]], parents: List[SuperClass[L]] = Nil): Free[F, Option[L#ValueDefinition]] = Free.inject[ModelProtocolTerm[L, ?], F](DecodeModel[L](clsName, needCamelSnakeConversion, params, parents)) - def renderDTOStaticDefns(clsName: String, deps: List[L#TermName], encoder: Option[L#ValueDefinition], decoder: Option[L#ValueDefinition]): Free[F, StaticDefns[L]] = + def renderDTOStaticDefns(clsName: String, + deps: List[L#TermName], + encoder: Option[L#ValueDefinition], + decoder: Option[L#ValueDefinition]): Free[F, StaticDefns[L]] = Free.inject[ModelProtocolTerm[L, ?], F](RenderDTOStaticDefns[L](clsName, deps, encoder, decoder)) } object ModelProtocolTerms { diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/PolyProtocolTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/PolyProtocolTerm.scala index dbaadf71f9..e63a347b9d 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/PolyProtocolTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/PolyProtocolTerm.scala @@ -2,9 +2,9 @@ package com.twilio.guardrail.protocol.terms.protocol import cats.InjectK import cats.free.Free -import com.twilio.guardrail.{ProtocolParameter, StaticDefns, SuperClass} +import com.twilio.guardrail.{ ProtocolParameter, StaticDefns, SuperClass } import com.twilio.guardrail.languages.LA -import io.swagger.v3.oas.models.media.{ComposedSchema, Schema} +import io.swagger.v3.oas.models.media.{ ComposedSchema, Schema } /** * Protocol for Polymorphic models @@ -14,7 +14,11 @@ sealed trait PolyProtocolTerm[L <: LA, T] case class ExtractSuperClass[L <: LA](swagger: ComposedSchema, definitions: List[(String, Schema[_])]) extends PolyProtocolTerm[L, List[(String, Schema[_], List[Schema[_]])]] -case class RenderSealedTrait[L <: LA](className: String, params: List[ProtocolParameter[L]], discriminator: String, parents: List[SuperClass[L]] = Nil, children: List[String] = Nil) +case class RenderSealedTrait[L <: LA](className: String, + params: List[ProtocolParameter[L]], + discriminator: String, + parents: List[SuperClass[L]] = Nil, + children: List[String] = Nil) extends PolyProtocolTerm[L, L#Trait] case class EncodeADT[L <: LA](clsName: String, children: List[String] = Nil) extends PolyProtocolTerm[L, Option[L#ValueDefinition]] diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala index d54b2131ca..5b037c02ab 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala @@ -1,6 +1,6 @@ package com.twilio.guardrail.protocol.terms.server -import com.twilio.guardrail.{RenderedRoutes, StrictProtocolElems, SupportDefinition, TracingField} +import com.twilio.guardrail.{ RenderedRoutes, StrictProtocolElems, SupportDefinition, TracingField } import com.twilio.guardrail.terms.RouteMeta import com.twilio.guardrail.languages.LA import com.twilio.guardrail.generators.{ Responses, ScalaParameters } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala index 37745d87f3..0c820feb93 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala @@ -2,8 +2,8 @@ package com.twilio.guardrail.protocol.terms.server import cats.InjectK import cats.free.Free -import com.twilio.guardrail.{RenderedRoutes, StrictProtocolElems, SupportDefinition, TracingField} -import com.twilio.guardrail.generators.{Responses, ScalaParameters} +import com.twilio.guardrail.{ RenderedRoutes, StrictProtocolElems, SupportDefinition, TracingField } +import com.twilio.guardrail.generators.{ Responses, ScalaParameters } import com.twilio.guardrail.terms.RouteMeta import com.twilio.guardrail.languages.LA import io.swagger.v3.oas.models.Operation @@ -29,7 +29,9 @@ class ServerTerms[L <: LA, F[_]](implicit I: InjectK[ServerTerm[L, ?], F]) { extraRouteParams: List[L#MethodParameter], responseDefinitions: List[L#Definition], supportDefinitions: List[L#Definition]): Free[F, List[L#Definition]] = - Free.inject[ServerTerm[L, ?], F](RenderClass(resourceName, handlerName, annotations, combinedRouteTerms, extraRouteParams, responseDefinitions, supportDefinitions)) + Free.inject[ServerTerm[L, ?], F]( + RenderClass(resourceName, handlerName, annotations, combinedRouteTerms, extraRouteParams, responseDefinitions, supportDefinitions) + ) def renderHandler(handlerName: String, methodSigs: List[L#MethodDeclaration], handlerDefinitions: List[L#Statement]): Free[F, L#Definition] = Free.inject[ServerTerm[L, ?], F](RenderHandler(handlerName, methodSigs, handlerDefinitions)) def getExtraImports(tracing: Boolean): Free[F, List[L#Import]] = From e8ff7923dd92a6529877d663b1dbb6d0206085b5 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Mon, 18 Mar 2019 13:29:38 -0700 Subject: [PATCH 85/86] Remove scala-2.12-isms to fix 2.11 build --- .../src/main/scala/com/twilio/guardrail/CLI.scala | 7 +++++-- .../scala/com/twilio/guardrail/ProtocolGenerator.scala | 2 +- .../Java/AsyncHttpClientClientGenerator.scala | 2 +- .../generators/Java/DropwizardServerGenerator.scala | 1 + .../guardrail/generators/Java/JacksonGenerator.scala | 2 +- .../com/twilio/guardrail/generators/syntax/Java.scala | 7 +++++-- .../protocol/terms/protocol/EnumProtocolTerm.scala | 1 + .../protocol/terms/protocol/ModelProtocolTerm.scala | 1 + .../scala/com/twilio/guardrail/terms/SwaggerTerm.scala | 1 + .../scala/core/Dropwizard/DropwizardShowerTest.scala | 10 +++++----- 10 files changed, 22 insertions(+), 12 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala index 81f4aba6ae..04a9804019 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala @@ -8,8 +8,8 @@ import com.twilio.guardrail.core.CoreTermInterp import com.twilio.guardrail.terms.CoreTerm import com.twilio.swagger.core.{ LogLevel, LogLevels, StructuredLogger } import com.twilio.guardrail.languages.{ JavaLanguage, LA, ScalaLanguage } - import scala.io.AnsiColor +import scala.util.{ Failure, Success } object CLICommon { def run[L <: LA](args: Array[String])(interpreter: CoreTerm[L, ?] ~> CoreTarget): Unit = { @@ -167,7 +167,10 @@ object CLI extends CLICommon { }, { str => import com.github.javaparser.JavaParser import scala.util.Try - Try(JavaParser.parseImport(s"import ${str};")).toEither.leftMap(t => UnparseableArgument("import", t.getMessage)) + Try(JavaParser.parseImport(s"import ${str};")) match { + case Success(value) => Right(value) + case Failure(t) => Left(UnparseableArgument("import", t.getMessage)) + } } ) } diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala index 1c4df9d8da..cefd26be96 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/ProtocolGenerator.scala @@ -12,7 +12,7 @@ import com.twilio.guardrail.terms.framework.FrameworkTerms import com.twilio.guardrail.terms.{ ScalaTerms, SwaggerTerms } import java.util.Locale import scala.collection.JavaConverters._ -import scala.language.{ higherKinds, postfixOps, reflectiveCalls } +import scala.language.{ existentials, higherKinds, postfixOps, reflectiveCalls } case class ProtocolDefinitions[L <: LA](elems: List[StrictProtocolElems[L]], protocolImports: List[L#Import], diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala index d189726d29..fc73812262 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/AsyncHttpClientClientGenerator.scala @@ -512,7 +512,7 @@ object AsyncHttpClientClientGenerator { .setParameters( List( new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("name")), - new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("value")), + new Parameter(util.EnumSet.of(FINAL), STRING_TYPE, new SimpleName("value")) ).toNodeList ) .setType(callBuilderType) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala index 0e6e275ca7..611d9b7c18 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala @@ -23,6 +23,7 @@ import io.swagger.v3.oas.models.PathItem.HttpMethod import io.swagger.v3.oas.models.responses.ApiResponse import java.util import scala.collection.JavaConverters._ +import scala.language.existentials import scala.util.Try object DropwizardServerGenerator { diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala index 0e97184ffe..d13da7294b 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/JacksonGenerator.scala @@ -684,7 +684,7 @@ object JacksonGenerator { (List( "com.fasterxml.jackson.annotation.JsonCreator", "com.fasterxml.jackson.annotation.JsonIgnoreProperties", - "com.fasterxml.jackson.annotation.JsonProperty", + "com.fasterxml.jackson.annotation.JsonProperty" ).map(safeParseRawImport) ++ List( "java.util.Objects.requireNonNull" ).map(safeParseRawStaticImport)).sequence diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala index 7aef6b99ff..0a82ff60f5 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/syntax/Java.scala @@ -11,7 +11,7 @@ import com.twilio.guardrail.Target import java.nio.charset.StandardCharsets import java.util.Optional import scala.reflect.ClassTag -import scala.util.Try +import scala.util.{ Failure, Success, Try } object Java { implicit class RichJavaOptional[T](val o: Optional[T]) extends AnyVal { @@ -61,7 +61,10 @@ object Java { private[this] def safeParse[T](log: String)(parser: String => T, s: String)(implicit cls: ClassTag[T]): Target[T] = Target.log.function(s"${log}: ${s}") { - Try(parser(s)).toEither.fold(t => Target.raiseError(s"Unable to parse '${s}' to a ${cls.runtimeClass.getName}: ${t.getMessage}"), Target.pure) + Try(parser(s)) match { + case Success(value) => Target.pure(value) + case Failure(t) => Target.raiseError(s"Unable to parse '${s}' to a ${cls.runtimeClass.getName}: ${t.getMessage}") + } } def safeParseCode(s: String): Target[CompilationUnit] = safeParse("safeParseCode")(JavaParser.parse, s) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerm.scala index 1e7e58b6dd..9ac279c7c7 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/EnumProtocolTerm.scala @@ -3,6 +3,7 @@ package com.twilio.guardrail.protocol.terms.protocol import _root_.io.swagger.v3.oas.models.media.Schema import com.twilio.guardrail.StaticDefns import com.twilio.guardrail.languages.LA +import scala.language.existentials sealed trait EnumProtocolTerm[L <: LA, T] case class ExtractEnum[L <: LA](swagger: Schema[_]) extends EnumProtocolTerm[L, Either[String, List[String]]] diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerm.scala index ee898981c4..0b5f74bbaf 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/protocol/ModelProtocolTerm.scala @@ -4,6 +4,7 @@ import _root_.io.swagger.v3.oas.models.media.Schema import com.twilio.guardrail.SwaggerUtil.ResolvedType import com.twilio.guardrail.languages.LA import com.twilio.guardrail.{ ProtocolParameter, StaticDefns, SuperClass } +import scala.language.existentials sealed trait ModelProtocolTerm[L <: LA, T] case class ExtractProperties[L <: LA](swagger: Schema[_]) extends ModelProtocolTerm[L, List[(String, Schema[_])]] diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/SwaggerTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/SwaggerTerm.scala index 365f85e3f0..8a867c4896 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/terms/SwaggerTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/terms/SwaggerTerm.scala @@ -11,6 +11,7 @@ import com.twilio.guardrail.languages.LA import com.twilio.guardrail.shims._ import scala.collection.JavaConverters._ +import scala.language.existentials import com.twilio.guardrail.terms.framework.FrameworkTerms import io.swagger.v3.oas.models.{ Operation, PathItem } import io.swagger.v3.oas.models.PathItem.HttpMethod diff --git a/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardShowerTest.scala b/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardShowerTest.scala index 06767f13a9..a0eb9442f7 100644 --- a/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardShowerTest.scala +++ b/modules/sample-dropwizard/src/test/scala/core/Dropwizard/DropwizardShowerTest.scala @@ -5,7 +5,7 @@ import java.math import java.net.{URI, URL} import java.time.{LocalDate, OffsetDateTime} import org.scalatest.{FreeSpec, Matchers} -import scala.util.Try +import scala.util.{Failure, Success, Try} class DropwizardShowerTest extends FreeSpec with Matchers { private val shower = Shower.getInstance @@ -120,10 +120,10 @@ class DropwizardShowerTest extends FreeSpec with Matchers { "Shower should not be able to show for unregistered types" - { "This test class" in { assert(!shower.canShow(getClass)) - Try(shower.show(this)).fold( - _.getClass shouldBe classOf[Shower.UnshowableInstanceException], - _ => fail("shower.show() should have thrown and UnshowableInstanceException") - ) + Try(shower.show(this)) match { + case Failure(t) => t.getClass shouldBe classOf[Shower.UnshowableInstanceException] + case Success(_) => fail ("shower.show() should have thrown and UnshowableInstanceException") + } } } } From fb393c7d039f30aa7d9e354338be2429199bc6e2 Mon Sep 17 00:00:00 2001 From: "Brian J. Tarricone" Date: Tue, 19 Mar 2019 13:43:05 -0700 Subject: [PATCH 86/86] Pass tracing boolean to GenerateRoutes for server generation --- .../main/scala/com/twilio/guardrail/ServerGenerator.scala | 2 +- .../guardrail/generators/AkkaHttpServerGenerator.scala | 2 +- .../guardrail/generators/EndpointsServerGenerator.scala | 2 +- .../twilio/guardrail/generators/Http4sServerGenerator.scala | 2 +- .../generators/Java/DropwizardServerGenerator.scala | 2 +- .../twilio/guardrail/protocol/terms/server/ServerTerm.scala | 3 ++- .../twilio/guardrail/protocol/terms/server/ServerTerms.scala | 5 +++-- 7 files changed, 10 insertions(+), 8 deletions(-) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala index 3b81a5634e..6d36ef6b08 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/ServerGenerator.scala @@ -64,7 +64,7 @@ object ServerGenerator { } yield (responseDefinitions, (operationId, tracingField, route, parameters, responses)) } (responseDefinitions, serverOperations) = responseServerPair.unzip - renderedRoutes <- generateRoutes(resourceName, basePath, serverOperations, protocolElems) + renderedRoutes <- generateRoutes(context.tracing, resourceName, basePath, serverOperations, protocolElems) handlerSrc <- renderHandler(handlerName, renderedRoutes.methodSigs, renderedRoutes.handlerDefinitions) extraRouteParams <- getExtraRouteParams(context.tracing) classSrc <- renderClass( diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpServerGenerator.scala index 14db8c24a1..456de55148 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/AkkaHttpServerGenerator.scala @@ -119,7 +119,7 @@ object AkkaHttpServerGenerator { } else Target.pure(None) } yield res - case GenerateRoutes(resourceName, basePath, routes, protocolElems) => + case GenerateRoutes(tracing, resourceName, basePath, routes, protocolElems) => for { renderedRoutes <- routes.traverse { case (operationId, tracingFields, sr @ RouteMeta(path, method, operation), parameters, responses) => diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsServerGenerator.scala index a161bf0fae..40d929666c 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/EndpointsServerGenerator.scala @@ -10,7 +10,7 @@ object EndpointsServerGenerator { def apply[T](term: ServerTerm[ScalaLanguage, T]): Target[T] = term match { case GenerateResponseDefinitions(operationId, responses, protocolElems) => ??? case BuildTracingFields(operation, resourceName, tracing) => ??? - case GenerateRoutes(resourceName, basePath, routes, protocolElems) => ??? + case GenerateRoutes(tracing, resourceName, basePath, routes, protocolElems) => ??? case RenderHandler(handlerName, methodSigs, handlerDefinitions) => ??? case GetExtraRouteParams(tracing) => ??? case GenerateSupportDefinitions(tracing) => ??? diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sServerGenerator.scala index dc1e3035fb..8bb36dfc9e 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Http4sServerGenerator.scala @@ -46,7 +46,7 @@ object Http4sServerGenerator { } else Target.pure(None) } yield res - case GenerateRoutes(resourceName, basePath, routes, protocolElems) => + case GenerateRoutes(tracing, resourceName, basePath, routes, protocolElems) => for { renderedRoutes <- routes .traverse { diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala index 611d9b7c18..ef3c296016 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/generators/Java/DropwizardServerGenerator.scala @@ -157,7 +157,7 @@ object DropwizardServerGenerator { Target.pure(Option.empty) } - case GenerateRoutes(resourceName, basePath, routes, protocolElems) => + case GenerateRoutes(tracing, resourceName, basePath, routes, protocolElems) => for { resourceType <- safeParseClassOrInterfaceType(resourceName) handlerName = s"${resourceName.replaceAll("Resource$", "")}Handler" diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala index 5b037c02ab..53a89d2980 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerm.scala @@ -8,7 +8,8 @@ import io.swagger.v3.oas.models.Operation sealed trait ServerTerm[L <: LA, T] case class BuildTracingFields[L <: LA](operation: Operation, resourceName: List[String], tracing: Boolean) extends ServerTerm[L, Option[TracingField[L]]] -case class GenerateRoutes[L <: LA](resourceName: String, +case class GenerateRoutes[L <: LA](tracing: Boolean, + resourceName: String, basePath: Option[String], routes: List[(String, Option[TracingField[L]], RouteMeta, ScalaParameters[L], Responses[L])], protocolElems: List[StrictProtocolElems[L]]) diff --git a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala index 0c820feb93..c0378313f0 100644 --- a/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala +++ b/modules/codegen/src/main/scala/com/twilio/guardrail/protocol/terms/server/ServerTerms.scala @@ -11,11 +11,12 @@ import io.swagger.v3.oas.models.Operation class ServerTerms[L <: LA, F[_]](implicit I: InjectK[ServerTerm[L, ?], F]) { def buildTracingFields(operation: Operation, resourceName: List[String], tracing: Boolean): Free[F, Option[TracingField[L]]] = Free.inject[ServerTerm[L, ?], F](BuildTracingFields(operation, resourceName, tracing)) - def generateRoutes(resourceName: String, + def generateRoutes(tracing: Boolean, + resourceName: String, basePath: Option[String], routes: List[(String, Option[TracingField[L]], RouteMeta, ScalaParameters[L], Responses[L])], protocolElems: List[StrictProtocolElems[L]]): Free[F, RenderedRoutes[L]] = - Free.inject[ServerTerm[L, ?], F](GenerateRoutes(resourceName, basePath, routes, protocolElems)) + Free.inject[ServerTerm[L, ?], F](GenerateRoutes(tracing, resourceName, basePath, routes, protocolElems)) def getExtraRouteParams(tracing: Boolean): Free[F, List[L#MethodParameter]] = Free.inject[ServerTerm[L, ?], F](GetExtraRouteParams(tracing)) def generateResponseDefinitions(operationId: String, responses: Responses[L], protocolElems: List[StrictProtocolElems[L]]): Free[F, List[L#Definition]] =