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

feature: Add ability to define custom masker function for @masked annotation #1244

Merged
merged 1 commit into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ object MaskMode {
def this(length: Int) = this(0, length)
}
case class Regexp(pattern: Regex) extends MaskMode

/** Allows to define a custom masker function */
case class Custom(f: String => String) extends MaskMode
}

object masking {
Expand Down Expand Up @@ -54,6 +57,8 @@ object masking {
.getOrElse(shown)
case MaskMode.ForLength(offset, maxLength) =>
loop(shown.toCharArray, shown.length min (offset max 0), if (maxLength == -1) shown.length else maxLength)
case MaskMode.Custom(f) =>
f(shown)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package derivation
import derevo.derive
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import tofu.logging.derivation.MaskMode.Custom

class DerivedLoggableSuite extends AnyFlatSpec with Matchers {

Expand Down Expand Up @@ -85,6 +86,18 @@ class DerivedLoggableSuite extends AnyFlatSpec with Matchers {
"maybeStr2=<none>" +
"}"
}

it should "show mask fields with custom masker function" in {
val maskedCustom = MaskedCustom(
sensitiveField = "som sensitive data",
firstName = Some("John"),
age = 42
)

json(maskedCustom) shouldBe """{"sensitiveField":"*","firstName":"J***","age":"**"}"""
Loggable[MaskedCustom].logShow(maskedCustom) shouldBe
"MaskedCustom{sensitiveField=*,firstName=Some(J***),age=**}"
}
}

object DerivedLoggableSuite {
Expand Down Expand Up @@ -120,4 +133,17 @@ object DerivedLoggableSuite {
@masked maybeDouble: Option[Double],
@masked maybeStr2: Option[String]
)

@derive(loggable)
final case class MaskedCustom(
@masked(Custom(_ => "*")) sensitiveField: String,
@masked(Custom(maskName)) firstName: Option[String],
@masked(Custom(maskAge)) age: Int
)

def maskName(name: String): String =
name.take(1) + "***"

def maskAge(i: String): String =
"*" * i.length
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package derivation

import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import tofu.logging.derivation.MaskMode.Custom

class DerivedLoggableSuite extends AnyFlatSpec with Matchers {

Expand Down Expand Up @@ -67,6 +68,35 @@ class DerivedLoggableSuite extends AnyFlatSpec with Matchers {
Loggable[MaskedBaz].logShow(MaskedBaz(None)) shouldBe "MaskedBaz{kek=<none>}"
}

it should "show mask Option values in logShow" in {
val maybeMasked = MaskedOptBaz(
maybeStr = Some("str"),
maybeInt = Some(123),
maybeBool = Some(true),
maybeDouble = Some(100.001),
maybeStr2 = None
)
Loggable[MaskedOptBaz].logShow(maybeMasked) shouldBe
"MaskedOptBaz{" +
"maybeStr=Some(***)," +
"maybeInt=Some(###)," +
"maybeBool=Some(****)," +
"maybeDouble=Some(###.###)," +
"maybeStr2=<none>" +
"}"
}

it should "show mask fields with custom masker function" in {
val maskedCustom = MaskedCustom(
sensitiveField = "som sensitive data",
firstName = Some("John"),
age = 42
)

json(maskedCustom) shouldBe """{"sensitiveField":"*","firstName":"J***","age":"**"}"""
Loggable[MaskedCustom].logShow(maskedCustom) shouldBe
"MaskedCustom{sensitiveField=*,firstName=Some(J***),age=**}"
}
}

object DerivedLoggableSuite {
Expand All @@ -89,4 +119,25 @@ object DerivedLoggableSuite {
derives Loggable

final case class MaskedBaz(@masked kek: Option[String], @ignoreOpt a: Option[String] = None) derives Loggable

final case class MaskedOptBaz(
@masked maybeStr: Option[String],
@masked maybeInt: Option[Int],
@masked maybeBool: Option[Boolean],
@masked maybeDouble: Option[Double],
@masked maybeStr2: Option[String]
) derives Loggable

final case class MaskedCustom(
@masked(Custom(_ => "*")) sensitiveField: String,
@masked(Custom(maskName)) firstName: Option[String],
@masked(Custom(maskAge)) age: Int
) derives Loggable

def maskName(name: String): String =
name.take(1) + "***"

def maskAge(i: String): String =
"*" * i.length

}
Loading