Skip to content

Instantly share code, notes, and snippets.

@fommil
Last active August 7, 2017 11:30
Show Gist options
  • Save fommil/5d3819c8cc89d1532afe6fdf24f61a26 to your computer and use it in GitHub Desktop.
Save fommil/5d3819c8cc89d1532afe6fdf24f61a26 to your computer and use it in GitHub Desktop.
XML Encoder - not able to handle recursive types
package xmlformat
import scala.xml._
import export._
import simulacrum._
@typeclass
trait Encoder[A] { self =>
def toXml(a: A): NodeSeq
}
object Encoder extends EncoderLowPriority {
// https://github.com/mpilquist/simulacrum/issues/5
def instance[A](f: A => NodeSeq): Encoder[A] = new Encoder[A] {
override def toXml(a: A): NodeSeq = f(a)
}
def el(name: String, els: Seq[Node]) =
Elem(null, name, Null, TopScope, true, els: _*)
}
@imports[Encoder]
trait EncoderLowPriority {
import Encoder._
import Encoder.ops._
implicit val EncoderString: Encoder[String] = instance(new Text(_))
// special-case Option to remove redundancy
implicit def EncoderOption[A: Encoder]: Encoder[Option[A]] = instance {
case Some(a) => Group(a.toXml.theSeq)
case None => Group(Nil)
}
}
package xmlformat
import scala.xml._
import org.scalatest._
import Matchers._
class EncoderTests extends FreeSpec {
import Encoder.ops._
implicit class Helper[T: Encoder](t: T) {
def print: String = t.toXml.toString
}
"XML Encoder" - {
"should support generic products" in {
import examples._
import DerivedEncoder.exports._
implicit val e: Encoder[Foo] = shapeless.cachedImplicit
Foo("hello").print shouldBe "<s>hello</s>"
Baz.print shouldBe ""
}
}
}
package examples {
sealed trait SimpleTrait
final case class Foo(s: String) extends SimpleTrait
final case class Bar() extends SimpleTrait
case object Baz extends SimpleTrait
}
package xmlformat
import scala.xml._
import export._
import shapeless.{ :: => :*:, _ }
import shapeless.labelled._
import Encoder.el
trait DerivedEncoder[T] extends Encoder[T]
@exports
object DerivedEncoder {
private def instance[A](f: A => NodeSeq): DerivedEncoder[A] =
new DerivedEncoder[A] {
override def toXml(a: A): NodeSeq = f(a)
}
implicit def gen[T, Repr](
implicit
// LP: LowPriority,
G: LabelledGeneric.Aux[T, Repr],
LER: Cached[Strict[DerivedEncoder[Repr]]]
): DerivedEncoder[T] = instance { t =>
LER.value.value.toXml(G.to(t))
}
implicit val hnil: DerivedEncoder[HNil] = instance { _ =>
Group(Nil)
}
implicit def hcons[Key <: Symbol, Value, Remaining <: HList](
implicit Key: Witness.Aux[Key],
LEV: Lazy[DerivedEncoder[Value]],
LER: DerivedEncoder[Remaining]
): DerivedEncoder[FieldType[Key, Value] :*: Remaining] =
instance {
case head :*: tail =>
val entry = el(Key.value.name, LEV.value.toXml(head))
LER.toXml(tail) match {
case g: Group => Group(entry :: g.nodes.toList)
}
}
// never called (shapeless.Coproduct is badly designed)
@SuppressWarnings(Array("org.wartremover.warts.Null"))
implicit val cnil: DerivedEncoder[CNil] = null
implicit def ccons[Name <: Symbol, Instance, Remaining <: Coproduct](
implicit
Name: Witness.Aux[Name],
LEI: Lazy[DerivedEncoder[Instance]],
LER: DerivedEncoder[Remaining]
): DerivedEncoder[FieldType[Name, Instance] :+: Remaining] = instance {
case Inl(ins) => el(Name.value.name, LEI.value.toXml(ins))
case Inr(rem) => LER.toXml(rem)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment