package io.circe.generic.extras.codec import io.circe.{ Codec, Decoder, DecodingFailure, HCursor, Json } import io.circe.generic.extras.Configuration import scala.annotation.implicitNotFound import shapeless.{ :+:, CNil, Coproduct, HNil, Inl, Inr, LabelledGeneric, Witness } import shapeless.labelled.{ FieldType, field } @implicitNotFound( """Could not find EnumerationCodec for type ${A}. Some possible causes for this: - ${A} isn't a case class or sealed trat - some of ${A}'s members don't have codecs of their own - missing implicit Configuration""" ) abstract class EnumerationCodec[A] extends Codec[A] object EnumerationCodec { implicit val codecForEnumerationCNil: EnumerationCodec[CNil] = new EnumerationCodec[CNil] { def apply(c: HCursor): Decoder.Result[CNil] = Left(DecodingFailure("Enumeration", c.history)) def apply(a: CNil): Json = sys.error("Cannot encode CNil") } implicit def codecForEnumerationCCons[K <: Symbol, V, R <: Coproduct](implicit witK: Witness.Aux[K], gen: LabelledGeneric.Aux[V, HNil], codecForR: EnumerationCodec[R], config: Configuration = Configuration.default ): EnumerationCodec[FieldType[K, V] :+: R] = new EnumerationCodec[FieldType[K, V] :+: R] { def apply(c: HCursor): Decoder.Result[FieldType[K, V] :+: R] = c.as[String] match { case Right(s) if s == config.transformConstructorNames(witK.value.name) => Right(Inl(field[K](gen.from(HNil)))) case Right(_) => codecForR.apply(c) match { case Right(v) => Right(Inr(v)) case Left(err) => Left(err) } case Left(err) => Left(DecodingFailure("Enumeration", c.history)) } def apply(a: FieldType[K, V] :+: R): Json = a match { case Inl(l) => Json.fromString(config.transformConstructorNames(witK.value.name)) case Inr(r) => codecForR(r) } } implicit def codecForEnumeration[A, R <: Coproduct](implicit gen: LabelledGeneric.Aux[A, R], codecForR: EnumerationCodec[R] ): EnumerationCodec[A] = new EnumerationCodec[A] { def apply(c: HCursor): Decoder.Result[A] = codecForR(c) match { case Right(v) => Right(gen.from(v)) case Left(err) => Left(err) } def apply(a: A): Json = codecForR(gen.to(a)) } }