Skip to content

Commit

Permalink
Add Json support
Browse files Browse the repository at this point in the history
  • Loading branch information
xuwei-k committed Jan 5, 2016
1 parent 06856a4 commit a67b9a5
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 10 deletions.
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ lazy val proptest = project.in(file("proptest"))
"com.github.os72" % "protoc-jar" % "3.0.0-b1",
"com.google.protobuf" % "protobuf-java" % "3.0.0-beta-2",
"io.grpc" % "grpc-all" % "0.9.0" % "test",
"com.google.protobuf" % "protobuf-java-util" % "3.0.0-beta-2" % "test",
"com.trueaccord.lenses" %% "lenses" % "0.4.1",
"org.scalacheck" %% "scalacheck" % "1.12.4" % "test",
"org.scalatest" %% "scalatest" % (if (scalaVersion.value.startsWith("2.12")) "2.2.5-M2" else "2.2.5") % "test"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,11 @@ trait DescriptorPimps {
def nameSymbol = scalaName.asSymbol

def baseClasses: Seq[String] =
Seq("com.trueaccord.scalapb.GeneratedMessage",
(if(json) {
Seq("com.trueaccord.scalapb.GeneratedMessageJson")
} else {
Seq("com.trueaccord.scalapb.GeneratedMessage")
}) ++ Seq(
s"com.trueaccord.scalapb.Message[$nameSymbol]",
s"com.trueaccord.lenses.Updatable[$nameSymbol]") ++ extendsOption

Expand All @@ -236,6 +240,8 @@ trait DescriptorPimps {

def javaConversions = params.javaConversions && !isMapEntry

def json = params.json && !isMapEntry

def isTopLevel = message.getContainingType == null

class MapType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import com.trueaccord.scalapb.TextFormat
import com.trueaccord.scalapb.textformat.TextFormatUtils
import scala.collection.JavaConversions._

case class GeneratorParams(javaConversions: Boolean = false, flatPackage: Boolean = false, grpc: Boolean = false)
case class GeneratorParams(javaConversions: Boolean = false, flatPackage: Boolean = false, grpc: Boolean = false, json: Boolean = false)

class ProtobufGenerator(val params: GeneratorParams) extends DescriptorPimps {
def printEnum(e: EnumDescriptor, printer: FunctionalPrinter): FunctionalPrinter = {
Expand Down Expand Up @@ -798,17 +798,46 @@ class ProtobufGenerator(val params: GeneratorParams) extends DescriptorPimps {
else fp.add(signature + "throw new MatchError(__field)")
}

def generateFromJson(message: Descriptor): FunctionalPrinter => FunctionalPrinter = { p =>
val JsonFormat = "_root_.com.google.protobuf.util.JsonFormat"
p.add(
s"def fromJsonString(json: String): ${message.nameSymbol} = {",
s" val registry = $JsonFormat.TypeRegistry.newBuilder().add(this.descriptor).build()",
s" val parser = $JsonFormat.parser().usingTypeRegistry(registry)",
s" val builder = ${message.javaTypeName}.newBuilder()",
s" parser.merge(json, builder)",
s" ${message.nameSymbol}.fromJavaProto(builder.build())",
s"}",
s"def fromJsonReader(json: java.io.Reader): ${message.nameSymbol} = {",
s" val registry = $JsonFormat.TypeRegistry.newBuilder().add(this.descriptor).build()",
s" val parser = $JsonFormat.parser().usingTypeRegistry(registry)",
s" val builder = ${message.javaTypeName}.newBuilder()",
s" parser.merge(json, builder)",
s" ${message.nameSymbol}.fromJavaProto(builder.build())",
s"}"
)
}

def generateMessageCompanion(message: Descriptor)(printer: FunctionalPrinter): FunctionalPrinter = {
val className = message.nameSymbol
val mixins = if (message.javaConversions)
s"with com.trueaccord.scalapb.JavaProtoSupport[$className, ${message.javaTypeName}] " else ""
val companionType = s"com.trueaccord.scalapb.GeneratedMessageCompanion[$className] $mixins"
val mixins = List(
if (message.javaConversions)
s"with com.trueaccord.scalapb.JavaProtoSupport[$className, ${message.javaTypeName}] "
else
"",
if (message.json)
s"with com.trueaccord.scalapb.GeneratedMessageJsonCompanion[$className] "
else
""
)
val companionType = s"com.trueaccord.scalapb.GeneratedMessageCompanion[$className] ${mixins.mkString}"
printer.addM(
s"""object $className extends $companionType {
| implicit def messageCompanion: $companionType = this""")
.indent
.when(message.javaConversions)(generateToJavaProto(message))
.when(message.javaConversions)(generateFromJavaProto(message))
.when(message.json)(generateFromJson(message))
.call(generateFromFieldsMap(message))
.call(generateDescriptor(message))
.call(generateMessageCompanionForField(message))
Expand Down Expand Up @@ -869,6 +898,8 @@ class ProtobufGenerator(val params: GeneratorParams) extends DescriptorPimps {
_
.add(s"def $withMethod(__v: ${field.scalaTypeName}): ${message.nameSymbol} = copy(${field.scalaName.asSymbol} = __v)")
}
}.when(message.json){
_.add(s"def toJsonString: String = _root_.com.google.protobuf.util.JsonFormat.printer.print(${message.nameSymbol}.toJavaProto(this))")
}.print(message.getOneofs) {
case (oneof, printer) =>
printer.addM(
Expand Down Expand Up @@ -1039,6 +1070,7 @@ object ProtobufGenerator {
case (Right(params), "java_conversions") => Right(params.copy(javaConversions = true))
case (Right(params), "flat_package") => Right(params.copy(flatPackage = true))
case (Right(params), "grpc") => Right(params.copy(grpc = true))
case (Right(params), "json") => Right(params.copy(javaConversions = true, json = true))
case (Right(params), p) => Left(s"Unrecognized parameter: '$p'")
case (x, _) => x
}
Expand Down
18 changes: 18 additions & 0 deletions e2e/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "2.2.1" % "test",
"io.grpc" % "grpc-all" % grpcVersion,
"org.scalacheck" %% "scalacheck" % "1.12.4" % "test",
"com.google.protobuf" % "protobuf-java-util" % "3.0.0-beta-2",
"com.trueaccord.scalapb" %% "scalapb-runtime-grpc" % com.trueaccord.scalapb.Version.scalapbVersion,
"com.trueaccord.scalapb" %% "scalapb-runtime" % com.trueaccord.scalapb.Version.scalapbVersion % PB.protobufConfig
)
Expand Down Expand Up @@ -56,3 +57,20 @@ grpcExePath := xsbti.SafeLazy {
exe
}

// TODO add `json: SettingKey[Boolean]` to sbt-scalapb
PB.protocOptions in PB.protobufConfig := {
val conf = (PB.generatedTargets in PB.protobufConfig).value
val scalaOpts = conf.find(_._2.endsWith(".scala")) match {
case Some(targetForScala) =>
Seq(s"--scala_out=json,grpc,java_conversions:${targetForScala._1.absolutePath}")
case None =>
Nil
}
val javaOpts = conf.find(_._2.endsWith(".java")) match {
case Some(targetForJava) =>
Seq(s"--java_out=${targetForJava._1.absolutePath}")
case None =>
Nil
}
scalaOpts ++ javaOpts
}
6 changes: 5 additions & 1 deletion proptest/src/test/scala/GeneratedCodeSpec.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import java.io.{File, PrintWriter}
import java.io.{StringReader, File, PrintWriter}
import java.nio.charset.Charset
import java.nio.file.{Files, Paths}
import java.util.logging.LogManager

import SchemaGenerators.CompiledSchema
import com.google.protobuf
Expand All @@ -25,6 +26,7 @@ class GeneratedCodeSpec extends PropSpec with GeneratorDrivenPropertyChecks with
}

property("Java and Scala protos are equivalent") {
LogManager.getLogManager().reset()
forAll(SchemaGenerators.genCompiledSchema, workers(1), minSuccessful(20)) {
schema: CompiledSchema =>
forAll(GenData.genMessageValueInstance(schema.rootNode)) {
Expand Down Expand Up @@ -85,6 +87,8 @@ class GeneratedCodeSpec extends PropSpec with GeneratorDrivenPropertyChecks with
javaParse(scalaAscii) should be(javaProto)
javaParse(scalaUnicodeAscii) should be(javaProto)

companion.fromJsonString(scalaProto.toJsonString) should be(scalaProto)
companion.fromJsonReader(new StringReader(scalaProto.toJsonString)) should be(scalaProto)
} catch {
case e: Exception =>
println(e.printStackTrace)
Expand Down
9 changes: 5 additions & 4 deletions proptest/src/test/scala/SchemaGenerators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ object SchemaGenerators {
val args = Seq("--proto_path",
(tmpDir.toString + ":protobuf:third_party"),
"--java_out", tmpDir.toString,
"--scala_out", "grpc,java_conversions:" + tmpDir.toString) ++ files
"--scala_out", "json,grpc,java_conversions:" + tmpDir.toString) ++ files
runProtoc(args: _*)
}

Expand Down Expand Up @@ -142,6 +142,7 @@ object SchemaGenerators {
jarForClass[com.trueaccord.scalapb.Scalapb].getPath,
jarForClass[com.trueaccord.scalapb.grpc.Grpc.type].getPath,
jarForClass[com.google.protobuf.Message].getPath,
jarForClass[com.google.protobuf.util.JsonFormat].getPath,
jarForClass[io.grpc.Channel].getPath,
jarForClass[com.google.common.util.concurrent.ListenableFuture[_]],
jarForClass[javax.annotation.Nullable],
Expand All @@ -162,7 +163,7 @@ object SchemaGenerators {
run.compile(scalaFiles.map(_.toString).toList)
}

type CompanionWithJavaSupport[A <: GeneratedMessage with Message[A]] = GeneratedMessageCompanion[A] with JavaProtoSupport[A, _]
type CompanionWithJavaSupport[A <: GeneratedMessageJson with Message[A]] = GeneratedMessageJsonCompanion[A] with JavaProtoSupport[A, _]

case class CompiledSchema(rootNode: RootNode, rootDir: File) {
lazy val classLoader = URLClassLoader.newInstance(Array[URL](rootDir.toURI.toURL), this.getClass.getClassLoader)
Expand All @@ -180,11 +181,11 @@ object SchemaGenerators {
cls.getMethod("parseFrom", classOf[Array[Byte]]).invoke(null, bytes).asInstanceOf[com.google.protobuf.Message]
}

def scalaObject(m: MessageNode): CompanionWithJavaSupport[_ <: GeneratedMessage] = {
def scalaObject(m: MessageNode): CompanionWithJavaSupport[_ <: GeneratedMessageJson] = {
val className = rootNode.scalaObjectName(m)
val u = scala.reflect.runtime.universe
val mirror = u.runtimeMirror(classLoader)
mirror.reflectModule(mirror.staticModule(className)).instance.asInstanceOf[CompanionWithJavaSupport[_ <: GeneratedMessage]]
mirror.reflectModule(mirror.staticModule(className)).instance.asInstanceOf[CompanionWithJavaSupport[_ <: GeneratedMessageJson]]
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ trait GeneratedMessage extends Serializable {
def serializedSize: Int
}

trait GeneratedMessageJson extends GeneratedMessage {
def toJsonString: String
}

trait Message[A] {
def mergeFrom(input: CodedInputStream): A
}
Expand All @@ -96,6 +100,12 @@ trait JavaProtoSupport[ScalaPB, JavaPB] {
def toJavaProto(scalaProto: ScalaPB): JavaPB
}

trait GeneratedMessageJsonCompanion[A <: GeneratedMessageJson with Message[A]] extends GeneratedMessageCompanion[A] {
def fromJsonString(json: String): A

def fromJsonReader(json: java.io.Reader): A
}

trait GeneratedMessageCompanion[A <: GeneratedMessage with Message[A]] {
def parseFrom(input: CodedInputStream): A = LiteParser.parseFrom(this, input)

Expand Down

0 comments on commit a67b9a5

Please sign in to comment.