Redditを見ていたところ面白いコンパイラプラグインを発見したので紹介する。
Redacted
Redactedは、case classの特定のフィールドをtoString()
から除外し、***
で塗り潰して見えなくできるコンパイラプラグインだ。
RedactedはScala 2.13系と3系の両方に対応しており、JVMでの動作が可能だ(Scala.jsとScala Nativeには対応していないようだ)。
百聞は一見に如かず、つまり以下のようなコードを書けるようになる:
import io.github.polentino.redacted.* case class User(id: Int, name: String, @redacted password: String) @main def hello(): Unit = val u = User(42, "windymelt", "foobar2000") println(u)
このコードを実行すると、以下の出力になる:
User(42,windymelt,***)
この作用により、うっかり秘匿しなければならないデータを持つcase classを表示してログに残ってしまう、ということを防げる。
インストール
Redactedは2つの形式で提供されている。すなわち、sbtプラグインと、手動でコンパイラプラグインを導入する形だ。どちらを選択しても実現できることは同じだ。
簡単なのはsbtプラグインなので、特に理由がなければこちらを選ぶとよいだろう。
sbtプラグイン
project/plugin.scala
に以下の通り追記する(バージョンは執筆時点での最新):
addSbtPlugin("io.github.polentino" % "sbt-redacted" % "1.1.0")
次に、build.sbt
に以下の設定を行い、プロジェクトでプラグインを有効にする:
lazy val root = (project in file(".")) .enablePlugins(RedactedPlugin) .setting( redactedVersion := "0.9.0", // ... )
これだけでよい。
手動でやる
手動でやる場合はbuild.sbt
で以下のようにする:
libraryDependencies ++= Seq( "io.github.polentino" %% "redacted" % "0.9.0" cross CrossVersion.full, compilerPlugin("io.github.polentino" %% "redacted-plugin" % "0.9.0" cross CrossVersion.full) )
使い方
使い方はとてもシンプルで、case classのフィールドに@redacted
アノテーションを配置するだけだ。というかこれ以外の機能は一切ない。
- case classの
- 任意のフィールドを
- 隠匿する
これだけ。
import io.github.polentino.redacted.* // アノテーションをインポートする case class Payment(id: Int, user: Int, @redacted creditCardNumber: String) println(Payment(42, 100, "1234-5678-9012-3456")) // => Payment(42, 100, ***)
@redacted
があるフィールドを持つcase classは、自動的にtoString()
が上書きされ、画面に表示した際にそのフィールドが***
で塗り潰されるようになる。
ちなみに、直接フィールドにアクセスした場合は何の変更も加えられない:
val p = Payment(42, 100, "1234-5678-9012-3456") println(p.creditCardNumber) // => 1234-5678-9012-3456
RedactedはあくまでもtoString()
でうっかり漏れてしまうことを防ぐためのプラグインであり、それ以外の仕事はしないという姿勢に徹している。わざわざ隠しているフィールドにアクセスしているということは余計な心配は不要だろう、というわけだ。
注意
Redactedはコンパイラプラグインだ。ということは、Scalaのバージョンが上がり、なおかつコンパイラのAPIに破壊的な変更が起こった場合、このプラグインは追従しなければならず、その間はScalaをアップデートできないリスクがある。この点だけ注意が必要だ。
とはいっても、RedactedはScala 3.3系のLTSに対応しているので、アプリケーションをLTSで実行しているなら当面の間は問題ないだろう。ちなみに動作はScala 3.7.0で確認したため、今のところ最新版のScalaでも問題なく動作している。
サンプル
確認に使ったサンプルコードはこれ。