Skip to content

Commit 16e9de8

Browse files
committed
add macros module
1 parent 3aa717a commit 16e9de8

File tree

5 files changed

+179
-1
lines changed

5 files changed

+179
-1
lines changed

Diff for: README.md

+8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ The structure of this project is hugely inspired by [scalapb-json4s](https://git
77

88
Include in your `build.sbt` file
99

10+
### core
11+
1012
```scala
1113
libraryDependencies += "io.github.scalapb-json" %% "scalapb-circe" % "0.4.0"
1214
```
@@ -17,6 +19,12 @@ for scala-js
1719
libraryDependencies += "io.github.scalapb-json" %%% "scalapb-circe" % "0.4.0"
1820
```
1921

22+
### macros
23+
24+
```scala
25+
libraryDependencies += "io.github.scalapb-json" %% "scalapb-circe-macros" % "0.4.1"
26+
```
27+
2028
### for ScalaPB 0.7.x
2129

2230
see https://github.com/scalapb-json/scalapb-circe/tree/0.2.x

Diff for: build.sbt

+24
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,30 @@ val tagOrHash = Def.setting {
1717

1818
val unusedWarnings = Seq("-Ywarn-unused")
1919

20+
lazy val macros = project
21+
.in(file("macros"))
22+
.settings(
23+
commonSettings,
24+
name := UpdateReadme.scalapbCirceMacrosName,
25+
libraryDependencies ++= Seq(
26+
"io.github.scalapb-json" %%% "scalapb-json-macros" % scalapbJsonCommonVersion.value,
27+
)
28+
)
29+
.dependsOn(
30+
scalapbCirceJVM
31+
)
32+
33+
lazy val tests = crossProject(JVMPlatform, JSPlatform)
34+
.in(file("tests"))
35+
.settings(
36+
commonSettings,
37+
noPublish,
38+
)
39+
.configure(_ dependsOn macros)
40+
.dependsOn(
41+
scalapbCirce % "test->test"
42+
)
43+
2044
val scalapbCirce = crossProject(JVMPlatform, JSPlatform)
2145
.in(file("core"))
2246
.enablePlugins(BuildInfoPlugin)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package scalapb_circe
2+
3+
import scalapb.{GeneratedMessage, GeneratedMessageCompanion, Message}
4+
5+
import scala.reflect.macros.blackbox
6+
import language.experimental.macros
7+
import scala.util.Try
8+
9+
object ProtoMacrosCirce {
10+
11+
implicit class ProtoContextCirce(private val c: StringContext) extends AnyVal {
12+
def struct(): com.google.protobuf.struct.Struct =
13+
macro ProtoMacrosCirce.protoStructInterpolation
14+
def value(): com.google.protobuf.struct.Value =
15+
macro ProtoMacrosCirce.protoValueInterpolation
16+
}
17+
18+
implicit class FromJsonCirce[A <: GeneratedMessage with Message[A]](
19+
private val companion: GeneratedMessageCompanion[A]
20+
) extends AnyVal {
21+
def fromJsonConstant(json: String): A =
22+
macro ProtoMacrosCirce.fromJsonConstantImpl0[A]
23+
24+
def fromJson(json: String): A =
25+
macro ProtoMacrosCirce.fromJsonImpl[A]
26+
27+
def fromJsonDebug(json: String): A =
28+
macro ProtoMacrosCirce.fromJsonDebugImpl
29+
30+
def fromJsonOpt(json: String): Option[A] =
31+
macro ProtoMacrosCirce.fromJsonOptImpl[A]
32+
33+
def fromJsonEither(json: String): Either[Throwable, A] =
34+
macro ProtoMacrosCirce.fromJsonEitherImpl[A]
35+
36+
def fromJsonTry(json: String): Try[A] =
37+
macro ProtoMacrosCirce.fromJsonTryImpl[A]
38+
}
39+
}
40+
41+
class ProtoMacrosCirce(override val c: blackbox.Context) extends scalapb_json.ProtoMacrosCommon(c) {
42+
43+
import c.universe._
44+
45+
override def fromJsonImpl[A: c.WeakTypeTag](json: c.Tree): c.Tree = {
46+
val A = weakTypeTag[A]
47+
q"_root_.scalapb_circe.JsonFormat.fromJsonString[$A]($json)"
48+
}
49+
50+
override def fromJsonConstantImpl[A <: GeneratedMessage with Message[A]: c.WeakTypeTag: GeneratedMessageCompanion](
51+
string: String
52+
): c.Tree = {
53+
val A = weakTypeTag[A]
54+
scalapb_circe.JsonFormat.fromJsonString[A](string)
55+
q"_root_.scalapb_circe.JsonFormat.fromJsonString[$A]($string)"
56+
}
57+
58+
private[this] def parseJson(json: String): io.circe.Json = {
59+
io.circe.parser.parse(json).fold(throw _, identity)
60+
}
61+
62+
override protected[this] def protoString2Struct(string: String): c.Tree = {
63+
val json = parseJson(string)
64+
val struct = StructFormat.structParser(json)
65+
q"$struct"
66+
}
67+
68+
override protected[this] def protoString2Value(string: String): c.Tree = {
69+
val json = parseJson(string)
70+
val value = StructFormat.structValueParser(json)
71+
q"$value"
72+
}
73+
}

Diff for: project/UpdateReadme.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ import sbtrelease.Git
55
object UpdateReadme {
66

77
val scalapbCirceName = "scalapb-circe"
8+
val scalapbCirceMacrosName = "scalapb-circe-macros"
89

910
val updateReadmeTask = { state: State =>
1011
val extracted = Project.extract(state)
1112
val v = extracted get version
1213
val org = extracted get organization
13-
val modules = scalapbCirceName :: Nil
14+
val modules = scalapbCirceName :: scalapbCirceMacrosName :: Nil
1415
val readme = "README.md"
1516
val readmeFile = file(readme)
1617
val newReadme = Predef
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package scalapb_circe
2+
3+
import org.scalatest.FunSpec
4+
import org.scalatest.Matchers
5+
import scalapb_circe.ProtoMacrosCirce._
6+
import com.google.protobuf.struct._
7+
import jsontest.test.MyTest
8+
9+
import scala.util.Success
10+
11+
class ProtoMacrosCirceTest extends FunSpec with Matchers {
12+
describe("ProtoMacrosCirce") {
13+
it("struct") {
14+
assert(struct"{}" == Struct.defaultInstance)
15+
16+
assert(
17+
struct"""{ "a" : [1, false, null, "x"] }""" == Struct(
18+
Map(
19+
"a" -> Value(
20+
Value.Kind.ListValue(
21+
ListValue(
22+
List(
23+
Value(Value.Kind.NumberValue(1.0)),
24+
Value(Value.Kind.BoolValue(false)),
25+
Value(Value.Kind.NullValue(NullValue.NULL_VALUE)),
26+
Value(Value.Kind.StringValue("x"))
27+
)
28+
)
29+
)
30+
)
31+
)
32+
)
33+
)
34+
35+
""" struct" { invalid json " """ shouldNot compile
36+
}
37+
38+
it("value") {
39+
assert(value" 42 " == Value(Value.Kind.NumberValue(42)))
40+
assert(value" false " == Value(Value.Kind.BoolValue(false)))
41+
assert(value""" "x" """ == Value(Value.Kind.StringValue("x")))
42+
assert(value""" [] """ == Value(Value.Kind.ListValue(ListValue())))
43+
44+
""" value" { invalid json " """ shouldNot compile
45+
}
46+
47+
it("fromJson") {
48+
assert(MyTest.fromJsonConstant("{}") === MyTest())
49+
assert(
50+
MyTest.fromJsonConstant(
51+
"""{
52+
"hello" : "foo",
53+
"foobar" : 42
54+
}"""
55+
) === MyTest().update(
56+
_.hello := "foo",
57+
_.foobar := 42
58+
)
59+
)
60+
""" jsontest.test.MyTest.fromJsonConstant("{") """ shouldNot compile
61+
62+
assert(MyTest.fromJsonTry("{").isFailure)
63+
assert(MyTest.fromJsonTry("""{"hello":"foo"}""") === Success(MyTest(hello = Some("foo"))))
64+
65+
assert(MyTest.fromJsonOpt("{").isEmpty)
66+
assert(MyTest.fromJsonOpt("""{"hello":"foo"}""") === Some(MyTest(hello = Some("foo"))))
67+
68+
assert(MyTest.fromJsonEither("{").isLeft)
69+
assert(MyTest.fromJsonEither("""{"hello":"foo"}""") === Right(MyTest(hello = Some("foo"))))
70+
}
71+
}
72+
}

0 commit comments

Comments
 (0)