Skip to content

Instantly share code, notes, and snippets.

@trbngr
Last active July 16, 2019 18:06
Show Gist options
  • Save trbngr/75195f0f8fd8f5b33491b1dc22707249 to your computer and use it in GitHub Desktop.
Save trbngr/75195f0f8fd8f5b33491b1dc22707249 to your computer and use it in GitHub Desktop.
DynamoFormats for ADT
package com.company.macros
import scala.reflect.macros.blackbox
class ADTMacros(val c: blackbox.Context) {
import c.universe._
def dynamoFormat[T: c.WeakTypeTag]: Tree = {
generate[T] { (tpe, symbols) =>
val cases = symbols.map { symbol =>
val name = q"${symbol.name.decodedName.toString.trim}"
cq"""$name => cats.data.Xor.right(${symbol.name.toTermName})"""
}
q"""com.gu.scanamo.DynamoFormat.xmap[$tpe, String]{case ..$cases}(_.toString)"""
}
}
private def generate[T: c.WeakTypeTag](func: (c.Type, List[c.Symbol]) => Tree): Tree = {
val t = weakTypeTag[T]
val (tpe, types) = t.tpe → collectKnownSubtypes(t.tpe.typeSymbol)
types match {
case Left(error) ⇒ reportErrors(error :: Nil)
case Right(values) => func(tpe, values)
}
}
private def collectKnownSubtypes(s: c.Symbol): Either[(c.Position, String), List[c.Symbol]] = {
if (s.isModule || s.isModuleClass)
Right(List(s))
else if (s.isClass) {
val cs = s.asClass
if ((cs.isTerm || cs.isAbstract) && cs.isSealed) {
cs.knownDirectSubclasses.foldLeft(Right(Nil): Either[(c.Position, String), List[Symbol]]) {
case (Left(error), _) => Left(error)
case (Right(set), knownSubclass) =>
collectKnownSubtypes(knownSubclass) match {
case Left(error) => Left(error)
case Right(subset) => Right(set ++ subset)
}
}
} else Left(cs.pos -> "Only sealed hierarchies or case objects are supported for ADT macros.")
} else Left(c.enclosingPosition -> "Only sealed hierarchies or case objects are supported for ADT macros.")
}
private def reportErrors(errors: Seq[(c.Position, String)]) = {
require(errors.nonEmpty)
val (lastPos, lastError) = errors.last
errors.dropRight(1).foreach { case (pos, error) ⇒ c.error(pos, error) }
c.abort(lastPos, lastError)
}
}
package com.company.dynamodb
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
import cats.data.Xor
import com.company.entity._
import com.gu.scanamo.DynamoFormat
trait DynamoFormats {
import DynamoFormat._
import Macros._
implicit val offsetDateTimeFormat = coercedXmap[OffsetDateTime, String, IllegalArgumentException](OffsetDateTime.parse)(_.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME))
implicit def setFormat[T](implicit f: DynamoFormat[T]): DynamoFormat[Set[T]] = xmap[Set[T], List[T]](l => Xor.right(l.toSet))(_.toList)
implicit val conferenceStateFormat = formatFromADT[ConferenceState]
implicit val sessionStateFormat = formatFromADT[SessionState]
implicit val presenterStateFormat = formatFromADT[PresenterState]
implicit val sponsorStateFormat = formatFromADT[SponsorState]
implicit val roomStateFormat = formatFromADT[RoomState]
implicit val trackStateFormat = formatFromADT[TrackState]
implicit val appointmentStateFormat = formatFromADT[AppointmentState]
implicit val productStateFormat = formatFromADT[ProductState]
implicit val surveyStateFormat = formatFromADT[SurveyState]
implicit val surveyEntryStateFormat = formatFromADT[SurveyEntryState]
}
package com.company.dynamodb
import com.gu.scanamo.DynamoFormat
import scala.language.experimental.macros
object Macros {
def formatFromADT[T]: DynamoFormat[T] = macro com.eventday.macros.ADTMacros.dynamoFormat[T]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment