Skip to content

Instantly share code, notes, and snippets.

@mikla
Last active May 25, 2017 07:25
Show Gist options
  • Select an option

  • Save mikla/98f40fd916cec5fea762114b63123aa0 to your computer and use it in GitHub Desktop.

Select an option

Save mikla/98f40fd916cec5fea762114b63123aa0 to your computer and use it in GitHub Desktop.
Type class that extracts information about all fields into Map[String, String].
/**
* Type class that extracts information about all fields into Map[String, String].
* It's uses `PlainStringRecordValue` instances for particular type of the field to obtain string representation.
* @tparam T
*/
@typeclass trait PlainStringRecord[T] {
def plainStringRecord(t: T): Map[String, String]
}
object PlainStringRecord extends EventInfoLowerPriorityImplicits {
implicit def infoHNil: PlainStringRecord[HNil] = new PlainStringRecord[HNil] {
override def plainStringRecord(t: HNil): Map[String, String] = Map.empty
}
implicit def infoHCons[K <: Symbol, H, T <: HList](
implicit key: Witness.Aux[K],
sh: Lazy[PlainStringRecordValue[H]],
st: Lazy[PlainStringRecord[T]]
): PlainStringRecord[FieldType[K, H] :: T] = new PlainStringRecord[FieldType[K, H] :: T] {
override def plainStringRecord(t: FieldType[K, H] :: T): Map[String, String] = {
val head = Map(key.value.name -> sh.value.plainString(t.head))
val tail = st.value.plainStringRecord(t.tail)
head ++ tail
}
}
implicit def infoCNil: PlainStringRecord[CNil] = new PlainStringRecord[CNil] {
override def plainStringRecord(t: CNil): Map[String, String] = t.impossible
}
implicit def infoCCons[K <: Symbol, H, T <: Coproduct](
implicit key: Witness.Aux[K],
sh: PlainStringRecord[H],
st: PlainStringRecord[T]
): PlainStringRecord[FieldType[K, H] :+: T] = new PlainStringRecord[FieldType[K, H] :+: T] {
override def plainStringRecord(t: FieldType[K, H] :+: T): Map[String, String] = t match {
case Inl(l) => sh.plainStringRecord(l)
case Inr(r) => st.plainStringRecord(r)
}
}
}
trait EventInfoLowerPriorityImplicits {
implicit def infoGeneric[T, Repr](
implicit gen: LabelledGeneric.Aux[T, Repr],
sg: Lazy[PlainStringRecord[Repr]]): PlainStringRecord[T] = new PlainStringRecord[T] {
override def plainStringRecord(t: T): Map[String, String] = sg.value.plainStringRecord(gen.to(t))
}
}
@typeclass trait PlainStringRecordValue[T] {
def plainString(t: T): String
}
object PlainStringRecordValue extends PlainStringRecordValueLowerPriorityImplicits1 {
// put implicits here
}
trait PlainStringRecordValueLowerPriorityImplicits1 extends PlainStringRecordValueLowerPriorityImplicits2 {
implicit def optionPlainValue[T](
implicit V: PlainStringRecordValue[T]) = new PlainStringRecordValue[Option[T]] {
override def plainString(t: Option[T]): String = t.map(e => V.plainString(e)).getOrElse("-")
}
}
trait PlainStringRecordValueLowerPriorityImplicits2 {
implicit def plainStringValueAny[T]: PlainStringRecordValue[T] = new PlainStringRecordValue[T] {
override def plainString(t: T): String = t.toString
}
}
// test
import org.scalatest.{FunSuite, Matchers}
class PlainStringRecordSpec extends FunSuite with Matchers {
case class Value(v: String)
case class Event(name: String, value: Value)
test("PlainStringRecord should map representation of case class parameters and uses " +
"PlainStringRecordValue type class instance to retrieve field info") {
implicit val eventPlainStringValue: PlainStringRecordValue[Value] = new PlainStringRecordValue[Value] {
override def plainString(t: Value): String = t.v
}
val event = Event("name", Value("value"))
implicitly[PlainStringRecord[Event]].plainStringRecord(event) should be (Map("name" -> "name", "value" -> "value"))
}
test("PlainStringRecord should map representation of case class parameters and fallback to default .toString " +
"if no implicit PlainStringRecordValue found for particular type") {
val event = Event("name", Value("value"))
implicitly[PlainStringRecord[Event]].plainStringRecord(event) should be {
Map("name" -> "name", "value" -> "Value(value)")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment