Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java language support redux #192

Merged
merged 86 commits into from Mar 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
5d9a934
Add initial Java language types
kelnos Dec 22, 2018
97327d4
simplify StaticDefns to just one 'definitions' member
kelnos Jan 12, 2019
7d9ac12
add initial JavaGenerator (still missing some things though)
kelnos Jan 12, 2019
068c4cf
Adding stubs for java interpreter
blast-hardcheese Jan 25, 2019
84eec2b
Adding runJavaExample
blast-hardcheese Jan 25, 2019
87d04ec
Parsing imports
blast-hardcheese Jan 25, 2019
459bf98
Better logging for java syntax
blast-hardcheese Feb 18, 2019
57d1e86
Switching away from primitive types
blast-hardcheese Feb 19, 2019
f4a3d0a
Enable --debug
blast-hardcheese Feb 19, 2019
f11bd70
Wiring in JavaInterp
blast-hardcheese Feb 19, 2019
25d1713
Fleshing out Jackson stub
blast-hardcheese Feb 19, 2019
a14fe7b
Further work on Jackson stub
blast-hardcheese Mar 1, 2019
5e1893a
Provide ProtocolParameter to RenderDTOClass
kelnos Feb 27, 2019
873ac4c
Move toCamelCase and toPascalCase to a shared RichString implicit
kelnos Feb 27, 2019
8365fad
Make DTO EncodeModel and DecodeModel optional
kelnos Feb 27, 2019
91538a4
Make enum RenderMembers, EncodeEnum, and DecodeEnum optional
kelnos Feb 27, 2019
45b25a2
Pass elems to enum RenderClass
kelnos Feb 27, 2019
9feaf65
Make PolyProtocolTerm EncodeADT and DecodeADT optional
kelnos Mar 1, 2019
0137818
Pass full ProtocolParameter list to RenderSealedTrait for poly proto
kelnos Mar 1, 2019
696bb75
Provide list of children to poly RenderSealedTrait
kelnos Mar 1, 2019
31818eb
Finish Jackson generator implementation
kelnos Mar 1, 2019
83cbece
Make framework implicits and package objects optional
kelnos Mar 1, 2019
a03af72
Various fixes to type parsing in JavaGenerator
kelnos Mar 1, 2019
9378a09
Make implicits and package objects optional in the framework interpreter
kelnos Mar 1, 2019
d91809d
Add stub Dropwizard and AsyncHttpClient generators
kelnos Mar 1, 2019
2a70ee7
Add GetFrameworkDefintions and RenderFrameworkDefinitions
kelnos Mar 2, 2019
a97963b
Flesh out the AsyncHttpClient generator a bit
kelnos Mar 2, 2019
f99301c
Fix ExtractTypeName in JavaGenerator
kelnos Mar 5, 2019
4477bb8
Allow WriteServer to return list of WriteTree
kelnos Mar 5, 2019
d7357aa
Split server's handler defn and resource defn into separate params
kelnos Mar 8, 2019
139113b
Allow route terms to be a list rather than combined
kelnos Mar 5, 2019
0ba74d7
Allow passing annotations to be added to server classes
kelnos Mar 5, 2019
4f0a486
Implement WriteServer for JavaGenerator
kelnos Mar 5, 2019
333f608
Implement first cut at a Dropwizard server generator
kelnos Mar 5, 2019
c651ba1
Make the AsyncHttpClient impl use a composable HTTP client function
kelnos Mar 5, 2019
0a7505e
Rough operation method parameter impl for AbstractHttpClient
kelnos Mar 5, 2019
4bd7fe4
Don't include clientName/tracingName in AHC if tracing disabled
kelnos Mar 5, 2019
5ce8176
Add defaultValue member to ProtocolParameter
kelnos Mar 5, 2019
30e7265
Error out in AsyncHttpClient if tracing is requested
kelnos Mar 5, 2019
0eea0d5
Escape reserved words in Java
kelnos Mar 5, 2019
1830b7a
Add initial Java compilation & tests
kelnos Mar 5, 2019
3dafa0f
Use Jackson TypeReference when deserializing to a generic type
kelnos Mar 6, 2019
0fcf385
Actually write out the Java server response definition classes
kelnos Mar 6, 2019
73c94fb
Add/fix a bunch of missing/incorrect imports for the Java generation
kelnos Mar 6, 2019
1cda5ba
Implement a "show" pattern for Java
kelnos Mar 6, 2019
4006086
Add a package-info.java file to the DTO components path
kelnos Mar 6, 2019
ccbbcd0
Unbox boxed types in Java server responses
kelnos Mar 6, 2019
d4b43b8
Add Shower test
kelnos Mar 6, 2019
8646e05
Move Dropwizard test to scala
kelnos Mar 6, 2019
e6eefe3
Don't use DTO's Builder class for Jackson deserialization
kelnos Mar 7, 2019
77f6f2c
Add toString, equals, and hashCode to Java DTOs
kelnos Mar 7, 2019
6f771cb
Flesh out the DW test a bit more (it actually passes now)
kelnos Mar 7, 2019
7b27ce3
Add a GenerateSupportDefinitions phase to client & server
kelnos Mar 7, 2019
33d8b67
Move AHC and ObjectMapper creation/config code to support classes
kelnos Mar 7, 2019
5db82eb
Be sure to appropriately unbox Java types in parameters
kelnos Mar 7, 2019
bc5d589
Use Shower for interpolating path components in Java
kelnos Mar 7, 2019
be4aa18
Support property default values in Jackson protocol objects
kelnos Mar 8, 2019
91558a0
Use a CallBuilder pattern for AsyncHttpClient operations
kelnos Mar 9, 2019
86874d6
Simplify Java cmdline import parser
kelnos Mar 13, 2019
1a3ee62
map+sequence -> traverse where possible
kelnos Mar 13, 2019
f3bde92
Add UrlencodedFormData to RouteMeta.ContentType
kelnos Mar 14, 2019
3146653
Better consumes/produces handling for Dropwizard
kelnos Mar 14, 2019
ad0faa5
Move toDashedCase to common code
kelnos Mar 14, 2019
5132747
Disable --debug when running Java example
kelnos Mar 15, 2019
4646d3d
Remove stub Java akka-http generator
kelnos Mar 15, 2019
66baec4
Add ClientException as thrown type for AsyncHttpClient call methods
kelnos Mar 15, 2019
56fcc16
Simplify default value transformation in Jackson generator
kelnos Mar 15, 2019
6b70dcc
Remove commented out code in Java syntax helpers
kelnos Mar 15, 2019
a076ed6
Remove unneeded FIXME comment
kelnos Mar 15, 2019
cb17aee
Simplify map+sequence to traverse in Jackson generator
kelnos Mar 15, 2019
d921718
Simplify ComposedSchema transformation in Jackson generator
kelnos Mar 15, 2019
1dafc40
Have Java fold method lambdas take the response body, not response type
kelnos Mar 15, 2019
35f1b42
Clean up Java Type creators for common parameterizeable types
kelnos Mar 15, 2019
991c746
Remove need for temp var in DW server resource in lieu of EnclosedExpr
kelnos Mar 15, 2019
b2c98c2
Make Java output a little prettier
kelnos Mar 15, 2019
d6c552e
Fix missing private modifier for fields in AHC call builders
kelnos Mar 15, 2019
5986771
Clean up Java linter warnings in generated code
kelnos Mar 15, 2019
9fe9a8d
Make custom type vendor extensions configurable by the language impl
kelnos Mar 16, 2019
8b8b137
Split java & scala test suites into their own sbt commands
kelnos Mar 16, 2019
cbcaeb2
Aid discoverability in DW response types with static creator methods
kelnos Mar 16, 2019
c9eda6e
Add simple DW server test using the dropwizard-testing framework
kelnos Mar 16, 2019
16b9c2c
Don't add a DW @Path annotation to methods if the path is empty
kelnos Mar 16, 2019
54ddfb1
Make SwaggerUtil.generateUrlPathParams() language-agnostic
kelnos Mar 16, 2019
060430d
Run sbt format
kelnos Mar 18, 2019
e8ff792
Remove scala-2.12-isms to fix 2.11 build
kelnos Mar 18, 2019
fb393c7
Pass tracing boolean to GenerateRoutes for server generation
kelnos Mar 19, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -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
96 changes: 79 additions & 17 deletions build.sbt
Expand Up @@ -17,7 +17,11 @@ 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"
val ahcVersion = "2.8.1"
val dropwizardVersion = "1.3.9"
val jerseyVersion = "2.25.1"

mainClass in assembly := Some("com.twilio.guardrail.CLI")

Expand All @@ -43,8 +47,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(
("dropwizard", "dropwizard"),
)
(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-${frameworkPackage}/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(
Expand All @@ -65,12 +89,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) := {
Expand All @@ -81,23 +113,27 @@ 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" !)
}

// Deprecated command
addCommandAlias("example", "runtimeSuite")

addCommandAlias("cli", "runMain com.twilio.guardrail.CLI")
addCommandAlias("runtimeSuite", "; resetSample ; runScalaExample ; " + frameworks.map(x => s"${x}Sample/test").mkString("; "))
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("testSuite", "; scalaTestSuite")
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")
kelnos marked this conversation as resolved.
Show resolved Hide resolved
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 ; javaTestSuite")

addCommandAlias(
"publishBintray",
Expand Down Expand Up @@ -150,13 +186,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 := {
Expand Down Expand Up @@ -245,6 +282,31 @@ lazy val endpointsSample = (project in file("modules/sample-endpoints"))
scalafmtOnCompile := false
)

lazy val dropwizardSample = (project in file("modules/sample-dropwizard"))
.settings(
codegenSettings,
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
)

watchSources ++= (baseDirectory.value / "modules/sample/src/test" ** "*.scala").get
watchSources ++= (baseDirectory.value / "modules/sample/src/test" ** "*.java").get

logBuffered in Test := false
80 changes: 80 additions & 0 deletions 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<T> {
String show(T value);
}

private static final Shower instance = new Shower();

public static Shower getInstance() {
return instance;
}

private final java.util.Map<Class<?>, Showable<?>> showables = new java.util.concurrent.ConcurrentHashMap<>();

private Shower() {
registerDefaultInstances();
}

public <T> void register(final Class<T> cls, final Showable<T> showable) {
this.showables.put(cls, showable);
}

@SuppressWarnings("unchecked")
public String show(final Object value) {
return show(value, (Class<Object>)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<Object> cls) {
if (this.showables.containsKey(cls)) {
final Showable<Object> showable = (Showable<Object>)this.showables.get(cls);
return showable.show(value);
} else {
final Class<Object> 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);
}
}
20 changes: 18 additions & 2 deletions modules/codegen/src/main/scala/com/twilio/guardrail/CLI.scala
Expand Up @@ -7,9 +7,9 @@ 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.{ 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 = {
Expand Down Expand Up @@ -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)
}

Expand All @@ -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", {
Expand All @@ -157,4 +160,17 @@ object CLI extends CLICommon {
_.parse[Importer].toEither.bimap(err => UnparseableArgument("import", err.toString), importer => Import(List(importer)))
}
)

val javaInterpreter = CoreTermInterp[JavaLanguage](
"dropwizard", {
case "dropwizard" => Java.Dropwizard
}, { str =>
import com.github.javaparser.JavaParser
import scala.util.Try
Try(JavaParser.parseImport(s"import ${str};")) match {
case Success(value) => Right(value)
case Failure(t) => Left(UnparseableArgument("import", t.getMessage))
}
}
)
}
Expand Up @@ -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],
Expand All @@ -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))
Expand Down Expand Up @@ -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)
}
}