Skip to content

Commit 899a5ff

Browse files
committed
@whenAbsent annotation for GenCodec and RPC
1 parent f98f72a commit 899a5ff

File tree

13 files changed

+102
-20
lines changed

13 files changed

+102
-20
lines changed

commons-annotations/src/main/scala/com/avsystem/commons/rpc/rpcAnnotations.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ sealed trait RawParamAnnotation extends RawRpcAnnotation
2323
*/
2424
class rpcName(val name: String) extends RpcAnnotation
2525

26+
class whenAbsent[+T](v: => T) extends serialization.whenAbsent[T](v)
27+
2628
/**
2729
* Base trait for RPC tag annotations. Tagging gives more direct control over how real methods
2830
* and their parameters are matched against raw methods and their parameters.

commons-annotations/src/main/scala/com/avsystem/commons/serialization/transientDefault.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ package serialization
44
import scala.annotation.StaticAnnotation
55

66
/**
7-
* If some case class field has default value, you can use this annotation on this field to instruct an
8-
* automatically derived `GenCodec` to not persist the value of that field if it's equal to the default value.
7+
* If some case class field has default value or [[whenAbsent]] annotation, you can use [[transientDefault]]
8+
* on this field to instruct an automatically derived `GenCodec` to not persist the value of that field if
9+
* it's equal to the default value.
910
*
1011
* For example:
1112
* {{{
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.avsystem.commons
2+
package serialization
3+
4+
import scala.annotation.StaticAnnotation
5+
6+
/**
7+
* An alternative way to provide default value for case class parameter used during deserialization with `GenCodec`
8+
* when its field is missing in data being deserialized. Normally, Scala-level default parameter values are picked
9+
* up, but you may want to use this annotation instead if you don't want to pollute your Scala classes with
10+
* unintended default parameter values (i.e. you want a default value *only* for deserialization).
11+
*
12+
* {{{
13+
* case class HasDefault(@whenAbsent("default") str: String)
14+
* object HasDefault extends HasGenCodec[HasDefault]
15+
* }}}
16+
*
17+
* If a parameter has both Scala-level default value and is annotated with `@whenAbsent` then value from annotation
18+
* takes priority. You can use this to have different source-level default value and different
19+
* default value for deserialization. You can also leverage this to "remove" default value for deserialization:
20+
*
21+
* {{{
22+
* case class HasNoDefault(@whenAbsent(throw new Exception) str: String = "default")
23+
* object HasDefault extends HasGenCodec[HasDefault]
24+
* }}}
25+
*/
26+
class whenAbsent[+T](v: => T) extends StaticAnnotation {
27+
def value: T = v
28+
}

commons-core/src/test/scala/com/avsystem/commons/rpc/NewRawRpc.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ case class NewRpcMetadata[T: TypeName](
5555
@verbatim procedures: Map[String, FireMetadata],
5656
functions: Map[String, CallMetadata[_]],
5757
getters: Map[String, GetterMetadata[_]],
58-
@tagged[POST] posters: Map[String, PostMetadata[_]],
58+
@tagged[POST] posters: Map[String, PostMetadata[_]]
5959
)
6060
object NewRpcMetadata extends RpcMetadataCompanion[NewRpcMetadata]
6161

commons-core/src/test/scala/com/avsystem/commons/rpc/NewRpcTest.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ package rpc
44
import com.github.ghik.silencer.silent
55

66
trait SomeBase {
7+
def difolt: Boolean = true
8+
79
@POST def postit(arg: String, @header("X-Bar") bar: String, int: Int, @header("X-Foo") foo: String): String
810
}
911

1012
trait NamedVarargs extends SomeBase {
1113
def varargsMethod(krap: String, dubl: Double)(czy: Boolean, @renamed(42, "nejm") ints: Int*): Future[Unit]
12-
def defaultValueMethod(int: Int = 0, bul: Boolean): Future[Unit]
14+
def defaultValueMethod(int: Int = 0, @whenAbsent(difolt) bul: Boolean): Future[Unit]
1315
def flames(arg: String, otherArg: => Int, varargsy: Double*): Unit
1416
def overload(int: Int): Unit
1517
def overload: NamedVarargs

commons-core/src/test/scala/com/avsystem/commons/rpc/RPCTest.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ class RPCTest extends WordSpec with Matchers with BeforeAndAfterAll {
2929
rawRpc.fire("doStuff")(List(42, "omgsrsly", Some(true)))
3030
assert("doStuffResult" === get(rawRpc.call("doStuffBoolean")(List(true))))
3131
rawRpc.fire("doStuffInt")(List(5))
32+
rawRpc.fire("doStuffInt")(Nil)
3233
rawRpc.fire("handleMore")(Nil)
3334
rawRpc.fire("handle")(Nil)
35+
rawRpc.fire("takeCC")(Nil)
3436
rawRpc.fire("srslyDude")(Nil)
3537
rawRpc.get("innerRpc")(List("innerName")).fire("proc")(Nil)
3638
assert("innerRpc.funcResult" === get(rawRpc.get("innerRpc")(List("innerName")).call("func")(List(42))))
@@ -40,8 +42,10 @@ class RPCTest extends WordSpec with Matchers with BeforeAndAfterAll {
4042
("doStuff", List(42, "omgsrsly", Some(true))),
4143
("doStuffBoolean", List(true)),
4244
("doStuffInt", List(5)),
45+
("doStuffInt", List(42)),
4346
("handleMore", Nil),
4447
("handle", Nil),
48+
("takeCC", List(Record(-1, "_"))),
4549
("srslyDude", Nil),
4650
("innerRpc", List("innerName")),
4751
("innerRpc.proc", Nil),

commons-core/src/test/scala/com/avsystem/commons/rpc/TestRPC.scala

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ trait InnerRPC {
1919
object InnerRPC extends DummyRPC.RPCCompanion[InnerRPC]
2020

2121
trait TestRPC {
22+
def defaultNum: Int = 42
23+
2224
@silent
2325
def handle: Unit
2426

@@ -30,9 +32,9 @@ trait TestRPC {
3032
def doStuff(yes: Boolean): Future[String]
3133

3234
@rpcName("doStuffInt")
33-
def doStuff(num: Int): Unit
35+
def doStuff(@whenAbsent(defaultNum) num: Int): Unit
3436

35-
def takeCC(r: Record): Unit
37+
def takeCC(r: Record = Record(-1, "_")): Unit
3638

3739
def srslyDude(): Unit
3840

@@ -71,14 +73,13 @@ object TestRPC extends DummyRPC.RPCCompanion[TestRPC] {
7173
onProcedure("handle", Nil)
7274

7375
def takeCC(r: Record): Unit =
74-
onProcedure("recordCC", List(r))
76+
onProcedure("takeCC", List(r))
7577

7678
def srslyDude(): Unit =
7779
onProcedure("srslyDude", Nil)
7880

7981
def innerRpc(name: String): InnerRPC = {
80-
onInvocation("innerRpc", List(name), None)
81-
new InnerRPC {
82+
onGet("innerRpc", List(name), new InnerRPC {
8283
def func(arg: Int): Future[String] =
8384
onCall("innerRpc.func", List(arg), "innerRpc.funcResult")
8485

@@ -90,7 +91,7 @@ object TestRPC extends DummyRPC.RPCCompanion[TestRPC] {
9091

9192
def indirectRecursion() =
9293
outer
93-
}
94+
})
9495
}
9596
}
9697
}

commons-core/src/test/scala/com/avsystem/commons/serialization/GenCodecTest.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ class GenCodecTest extends CodecTestBase {
262262
class OnlyVarargsCaseClassLike(val strings: Seq[String]) extends Wrapper[OnlyVarargsCaseClassLike](strings)
263263
object OnlyVarargsCaseClassLike {
264264
def apply(strings: String*): OnlyVarargsCaseClassLike = new OnlyVarargsCaseClassLike(strings)
265-
def unapplySeq(vccl: OnlyVarargsCaseClassLike): Opt[(Seq[String])] = vccl.strings.opt
265+
def unapplySeq(vccl: OnlyVarargsCaseClassLike): Opt[Seq[String]] = vccl.strings.opt
266266
implicit val codec: GenCodec[OnlyVarargsCaseClassLike] = GenCodec.materialize[OnlyVarargsCaseClassLike]
267267
}
268268

@@ -278,7 +278,7 @@ class GenCodecTest extends CodecTestBase {
278278
)
279279
}
280280

281-
case class HasDefaults(@transientDefault int: Int = 42, str: String)
281+
case class HasDefaults(@transientDefault int: Int = 42, @transientDefault @whenAbsent("dafuq") str: String = "kek")
282282
object HasDefaults {
283283
implicit val codec: GenCodec[HasDefaults] = GenCodec.materialize[HasDefaults]
284284
}
@@ -287,6 +287,7 @@ class GenCodecTest extends CodecTestBase {
287287
testWriteReadAndAutoWriteRead(HasDefaults(str = "lol"), Map("str" -> "lol"))
288288
testWriteReadAndAutoWriteRead(HasDefaults(43, "lol"), Map("int" -> 43, "str" -> "lol"))
289289
testWriteReadAndAutoWriteRead(HasDefaults(str = null), Map("str" -> null))
290+
testWriteReadAndAutoWriteRead(HasDefaults(str = "dafuq"), Map())
290291
}
291292

292293
case class Node[T](value: T, children: List[Node[T]] = Nil)

commons-macros/src/main/scala/com/avsystem/commons/macros/rpc/RPCMacros.scala

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ abstract class RPCMacroCommons(ctx: blackbox.Context) extends AbstractMacroCommo
1010
import c.universe._
1111

1212
val RpcPackage = q"$CommonsPkg.rpc"
13-
val RpcNameType: Type = getType(tq"$RpcPackage.rpcName")
14-
val RpcNameNameSym: Symbol = RpcNameType.member(TermName("name"))
1513
val AsRealCls = tq"$RpcPackage.AsReal"
1614
val AsRealObj = q"$RpcPackage.AsReal"
1715
val AsRawCls = tq"$RpcPackage.AsRaw"
@@ -22,6 +20,9 @@ abstract class RPCMacroCommons(ctx: blackbox.Context) extends AbstractMacroCommo
2220
val CanBuildFromCls = tq"$CollectionPkg.generic.CanBuildFrom"
2321
val ParamPositionObj = q"$RpcPackage.ParamPosition"
2422

23+
val RpcNameAT: Type = getType(tq"$RpcPackage.rpcName")
24+
val RpcNameNameSym: Symbol = RpcNameAT.member(TermName("name"))
25+
val WhenAbsentAT: Type = getType(tq"$RpcPackage.whenAbsent[_]")
2526
val RpcArityAT: Type = getType(tq"$RpcPackage.RpcArity")
2627
val SingleArityAT: Type = getType(tq"$RpcPackage.single")
2728
val OptionalArityAT: Type = getType(tq"$RpcPackage.optional")
@@ -62,6 +63,11 @@ abstract class RPCMacroCommons(ctx: blackbox.Context) extends AbstractMacroCommo
6263
}.foreach { companion =>
6364
registerImplicitImport(q"import $companion.implicits._")
6465
}
66+
67+
def containsInaccessibleThises(tree: Tree): Boolean = tree.exists {
68+
case t@This(_) if !enclosingClasses.contains(t.symbol) => true
69+
case _ => false
70+
}
6571
}
6672

6773
final class RPCMacros(ctx: blackbox.Context) extends RPCMacroCommons(ctx)

commons-macros/src/main/scala/com/avsystem/commons/macros/rpc/RPCMetadatas.scala

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,16 +277,23 @@ trait RPCMetadatas { this: RPCMacroCommons with RPCSymbols with RPCMappings =>
277277
reportProblem(s"${arity.collectedType} is not a subtype of StaticAnnotation")
278278
}
279279

280+
def validated(annot: Annot): Annot = {
281+
if (containsInaccessibleThises(annot.tree)) {
282+
reportProblem(s"reified annotation must not contain this-references inaccessible outside RPC trait")
283+
}
284+
annot
285+
}
286+
280287
def materializeFor(rpcSym: Real): Tree = arity match {
281288
case RpcArity.Single(annotTpe) =>
282-
rpcSym.annot(annotTpe).map(a => c.untypecheck(a.tree)).getOrElse {
289+
rpcSym.annot(annotTpe).map(a => c.untypecheck(validated(a).tree)).getOrElse {
283290
val msg = s"${rpcSym.problemStr}: cannot materialize value for $description: no annotation of type $annotTpe found"
284291
q"$RpcPackage.RpcUtils.compilationError(${StringLiteral(msg, rpcSym.pos)})"
285292
}
286293
case RpcArity.Optional(annotTpe) =>
287-
mkOptional(rpcSym.annot(annotTpe).map(a => c.untypecheck(a.tree)))
294+
mkOptional(rpcSym.annot(annotTpe).map(a => c.untypecheck(validated(a).tree)))
288295
case RpcArity.Multi(annotTpe, _) =>
289-
mkMulti(allAnnotations(rpcSym.symbol, annotTpe).map(a => c.untypecheck(a.tree)))
296+
mkMulti(allAnnotations(rpcSym.symbol, annotTpe).map(a => c.untypecheck(validated(a).tree)))
290297
}
291298

292299
def tryMaterializeFor(rpcSym: Real): Res[Tree] =

0 commit comments

Comments
 (0)