Skip to content

Commit

Permalink
Add support for marking certain types as serializable via sbt configu…
Browse files Browse the repository at this point in the history
…ration (and not via `@SerializabilityTrait`) (#304)

Add support for marking certain types as serializable via configuration (and not via `@SerializabilityTrait`)
  • Loading branch information
PawelLipski authored Apr 11, 2023
1 parent 75a6d66 commit f422418
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 1 deletion.
7 changes: 7 additions & 0 deletions docs/GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@ This option disables detection of messages/events/state based on type of argumen
This option disables detection of messages/events/state based on return type of the function given as argument to method. This detection is enabled by default. If you want to disable it, add the following setting:<br>
`Compile / scalacOptions += "-P:serializability-checker-plugin:--disable-detection-higher-order-function"`<br><br>

- `--types-explicitly-marked-as-serializable=<type1>,<type2>,...`

This option can be used to pass a comma-separated list of fully-qualified names of types that should be considered serializable by the checker, even if they do **not** extend a designated serializability trait.
The list is empty by default. If you want to mark some types as serializable, add the following setting (here shown with sample types):<br>

`Compile / scalacOptions += "-P:serializability-checker-plugin:--types-explicitly-marked-as-serializable=scala.util.Either,scala.collection.immutable.Set"`<br><br>

### Codec Registration Checker Compiler Plugin
Before using this compiler plugin, make sure that you are using both [annotations](#annotations) properly. If so &mdash; the plugin can be used right away. This plugin checks whether classes marked with serializability trait are being referenced in a marked serializer, which ensures that codecs will be registered in runtime.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ class SerializabilityCheckerCompilerPlugin(override val global: Global) extends
pluginOptions.detectFromMethods = !options.contains(disableMethods)
pluginOptions.detectFromUntypedMethods = !options.contains(disableMethodsUntyped)
pluginOptions.detectFromHigherOrderFunctions = !options.contains(disableHigherOrderFunctions)
options.find(_.startsWith(typesExplicitlyMarkedAsSerializable)).foreach { opt =>
pluginOptions.typesExplicitlyMarkedAsSerializable =
opt.stripPrefix(typesExplicitlyMarkedAsSerializable).split(",").toSeq.map(_.strip())
}
true
}

Expand All @@ -37,6 +41,7 @@ class SerializabilityCheckerCompilerPlugin(override val global: Global) extends
|$disableMethods - disables detection of messages/events/state based on type of arguments to a method, e.g. akka.actor.typed.ActorRef.tell
|$disableMethodsUntyped - disables detection of messages/events/state based on type of arguments to a method that takes Any, used for Akka Classic
|$disableHigherOrderFunctions - disables detection of messages/events/state based on return type of the function given as argument to method
|$typesExplicitlyMarkedAsSerializable - comma-separated list of fully-qualified names of types that should be considered serializable by this checker, even if they do NOT extend a designated serializability trait
|""".stripMargin)
}

Expand All @@ -49,6 +54,7 @@ object SerializabilityCheckerCompilerPlugin {
val disableMethods = "--disable-detection-methods"
val disableMethodsUntyped = "--disable-detection-untyped-methods"
val disableHigherOrderFunctions = "--disable-detection-higher-order-function"
val typesExplicitlyMarkedAsSerializable = "--types-explicitly-marked-as-serializable="
}
val serializabilityTraitType = "org.virtuslab.ash.annotation.SerializabilityTrait"
}
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ class SerializabilityCheckerCompilerPluginComponent(
Some(tp)
else if (akkaSerializabilityTraits.contains(tp.typeSymbol.fullName))
Some(tp)
else if (pluginOptions.typesExplicitlyMarkedAsSerializable.contains(tp.typeSymbol.fullName))
Some(tp)
else if (tp.typeSymbol.isAbstractType)
findSuperclassAnnotatedWithSerializabilityTrait(tp.upperBound)
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ class SerializabilityCheckerOptions(
var detectFromGenericMethods: Boolean = true,
var detectFromMethods: Boolean = true,
var detectFromUntypedMethods: Boolean = true,
var detectFromHigherOrderFunctions: Boolean = true)
var detectFromHigherOrderFunctions: Boolean = true,
var typesExplicitlyMarkedAsSerializable: Seq[String] = Seq.empty)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.random.project

import akka.actor.ActorRef

object TellEitherSeqSetTest {

val ref: ActorRef = ???
ref.tell(Right("hello"), null)
ref.tell(Seq("hello"), null)
ref.tell(Set("hello"), null)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.random.project

import akka.actor.ActorRef

object TellEitherTest {

val ref: ActorRef = ???
ref.tell(Right("hello"), null)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.random.project

import akka.actor.ActorRef

object TellSeqTest {

val ref: ActorRef = ???
ref.tell(Seq("hello"), null)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.random.project

import akka.actor.ActorRef

object TellSetTest {

val ref: ActorRef = ???
ref.tell(Set("hello"), null)
}
Original file line number Diff line number Diff line change
Expand Up @@ -180,5 +180,38 @@ class SerializabilityCheckerCompilerPluginComponentSpec extends AnyWordSpecLike
val code = getResourceAsString("GenericsTest3.scala")
SerializabilityCheckerCompiler.compileCode(List(serNoCode, code)) should be("")
}

"fail on usage of Either as a message" in {
val code = getResourceAsString("TellEitherTest.scala")
SerializabilityCheckerCompiler.compileCode(List(code)) should include(
"Right[Nothing,String] is used as Akka message")
}

"fail on usage of Seq as a message" in {
val code = getResourceAsString("TellSeqTest.scala")
SerializabilityCheckerCompiler.compileCode(List(code)) should include("Seq[String] is used as Akka message")
}

"fail on usage of Set as a message" in {
val code = getResourceAsString("TellSetTest.scala")
SerializabilityCheckerCompiler.compileCode(List(code)) should include("Set[String] is used as Akka message")
}

"not fail on usage of Either as a message when Either is explicitly marked as serializable" in {
val code = getResourceAsString("TellEitherTest.scala")
SerializabilityCheckerCompiler.compileCode(
List(code),
List(typesExplicitlyMarkedAsSerializable + "scala.util.Either")) should be("")
}

"when multiple types are used as a message, then only fail on the ones that are NOT explicitly marked as serializable" in {
val code = getResourceAsString("TellEitherSeqSetTest.scala")
val output = SerializabilityCheckerCompiler.compileCode(
List(code),
List(typesExplicitlyMarkedAsSerializable + "scala.util.Either,scala.collection.immutable.Set"))
output should include("Seq[String] is used as Akka message")
(output should not).include("Right[Nothing,String] is used as Akka message")
(output should not).include("Set[String] is used as Akka message")
}
}
}

0 comments on commit f422418

Please sign in to comment.