Last active
August 7, 2017 11:30
-
-
Save fommil/5d3819c8cc89d1532afe6fdf24f61a26 to your computer and use it in GitHub Desktop.
XML Encoder - not able to handle recursive types
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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