Last active
November 15, 2016 23:06
-
-
Save eirirlar/efd3d06ab1b9fd923918f1a733421b14 to your computer and use it in GitHub Desktop.
Patch and circe decoder
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 com.kodeworks.clusterfuck.util | |
import language.experimental.macros | |
import language.dynamics | |
import cats.syntax.option._ | |
import io.circe._ | |
import shapeless._ | |
import shapeless.labelled._ | |
import shapeless.ops.hlist.{Intersection, Length, Mapped, Mapper, RemoveAll, Zip, ZipWithKeys} | |
import shapeless.ops.record.{SelectAll, UnzipFields, Values} | |
import shapeless.tag.{@@, Tagger} | |
object patch { | |
type PatchCoprodType[P] = P :+: Patch[P] :+: String :+: None.type :+: CNil | |
sealed trait Patch[Patchable] extends (Patchable => Patchable) { | |
type OptionalFields <: HList | |
def updates: OptionalFields | |
def apply(patchable: Patchable): Patchable | |
} | |
object dec { | |
implicit def decodePatchable[ | |
Patchable, | |
PatchableFields <: HList, | |
PatchableKeys <: HList, | |
PatchableValues <: HList, | |
PatchableValuesCoprod <: HList, | |
PatchableCoprod <: HList, | |
ZipPatchableCoprod <: HList | |
] | |
(implicit | |
patchableLG: LabelledGeneric.Aux[Patchable, PatchableFields] | |
, patchableUnzip: UnzipFields.Aux[PatchableFields, PatchableKeys, PatchableValues] | |
, patchableValuesCoprod: Mapped.Aux[PatchableValues, PatchCoprodType, PatchableValuesCoprod] | |
, patchableCoprod: ZipWithKeys.Aux[PatchableKeys, PatchableValuesCoprod, PatchableCoprod] | |
, patchableCoprodDecoder: Lazy[Decoder[PatchableCoprod]] | |
, patchableValuesCoprod0: Values.Aux[PatchableCoprod, PatchableValuesCoprod] | |
, zipPatchableCoprod: Zip.Aux[PatchableValues :: PatchableValuesCoprod :: HNil, ZipPatchableCoprod] | |
, mapZipPatchableCoprod: Mapper.Aux[patchApplyPatchable.type, ZipPatchableCoprod, PatchableValues] | |
, patchableZipWithKeys: ZipWithKeys.Aux[PatchableKeys, PatchableValues, PatchableFields] | |
) | |
: Decoder[Patch[Patchable]] = | |
patchableCoprodDecoder.value.map { | |
patchCoprod => new Patch[Patchable] { | |
type OptionalFields = PatchableCoprod | |
val updates: OptionalFields = patchCoprod | |
def apply(patchable: Patchable) = { | |
val patchableFields0: PatchableFields = patchableLG.to(patchable) | |
val patchableValues0 = patchableUnzip.values(patchableFields0) | |
val patchableValuesCoprod1 = patchableValuesCoprod0(patchCoprod) | |
val zipPatchableCoprod0 = zipPatchableCoprod(patchableValues0 :: patchableValuesCoprod1 :: HNil) | |
val mapZipPatchableCoprod0 = mapZipPatchableCoprod(zipPatchableCoprod0) | |
val patchableZipWithKeys0 = patchableZipWithKeys(mapZipPatchableCoprod0) | |
val patchable0 = patchableLG.from(patchableZipWithKeys0) | |
patchable0 | |
} | |
} | |
} | |
implicit def decodeCoprod[Patchable] | |
(implicit pd: Decoder[Patchable] | |
, ppd: Lazy[Decoder[Patch[Patchable]]] = null | |
) | |
: Decoder[PatchCoprodType[Patchable]] = { | |
import Decoder._ | |
withReattempt(c => | |
if (c.succeeded) { | |
if (c.any.focus.isNull) Right(Inr(Inr(Inr(Inl(None))))) | |
else pd(c.any) match { | |
case Right(a) => Right(Inl(a)) | |
case Left(df) if df.history.isEmpty => Right(Inr(Inr(Inr(Inl(None))))) | |
case Left(df) => | |
if (c.any.focus.isObject && null != ppd) { | |
ppd.value(c.any) match { | |
case Right(a) => Right(Inr(Inl(a))) | |
case Left(df) => Left(df) | |
} | |
} else | |
Decoder[String].apply(c.any) match { | |
case Right(a) => Right(Inr(Inr(Inl(a)))) | |
case Left(df) => Left(df) | |
} | |
} | |
} else if (!c.history.takeWhile(_.failed).exists(_.incorrectFocus)) Right(Inr(Inr(Inr(Inl(None))))) | |
else { | |
Left(DecodingFailure("[A]PatchCoprodType[A]", c.history)) | |
} | |
) | |
} | |
} | |
object Patch { | |
type Aux[Patchable, OptionalFields0 <: HList] = Patch[Patchable] {type OptionalFields = OptionalFields0} | |
object mergePatchPatchable extends Poly1 { | |
implicit def apply[Key <: Symbol, Value] = at[(FieldType[Key, Option[Value]], FieldType[Key, Value])] { | |
case (patch, patchable) => field[Key](patch.getOrElse(patchable)) | |
} | |
} | |
def apply[Patchable](patchable: Patchable): Patch[Patchable] = new Patch[Patchable] { | |
override type OptionalFields = Patchable :: HNil | |
override def updates: OptionalFields = patchable :: HNil | |
override def apply(p: Patchable): Patchable = patchable | |
} | |
def apply[T] = new MkPatch[T] | |
class MkPatch[Patchable] extends Dynamic { | |
def applyRecord[ | |
PatchableFields <: HList, | |
PatchableKeys <: HList, | |
PatchableValues <: HList, | |
PatchableValuesCoprod <: HList, | |
PatchableCoprod <: HList, | |
Patch0 <: HList, | |
PatchKeys <: HList, | |
PatchValues <: HList, | |
PatchValueTypes <: HList, | |
PatchValuesTagged <: HList, | |
PatchValuesCoprod <: HList, | |
PatchCoprod <: HList, | |
NotPatch <: HList, | |
NotPatchKeys <: HList, | |
NotPatchValues <: HList, | |
NotPatchValuesCoprod <: HList, | |
NotPatchCoprod <: HList, | |
ZipPatchableCoprod <: HList, | |
KeysIntersect <: HList, | |
KeysIntersectLength <: Nat | |
](patch: Patch0) | |
(implicit | |
patchableLG: LabelledGeneric.Aux[Patchable, PatchableFields] | |
, patchableUnzip: UnzipFields.Aux[PatchableFields, PatchableKeys, PatchableValues] | |
, patchableValuesCoprod: Mapped.Aux[PatchableValues, PatchCoprodType, PatchableValuesCoprod] | |
, patchableCoprod: ZipWithKeys.Aux[PatchableKeys, PatchableValuesCoprod, PatchableCoprod] | |
, patchableValuesCoprod0: Values.Aux[PatchableCoprod, PatchableValuesCoprod] | |
, patchUnzip: UnzipFields.Aux[Patch0, PatchKeys, PatchValues] | |
, keysIntersect: Intersection.Aux[PatchKeys, PatchableKeys, KeysIntersect] | |
, keysIntersectLength: Length.Aux[KeysIntersect, KeysIntersectLength] | |
, keysLength: Length.Aux[Patch0, KeysIntersectLength] | |
, patchKeysRemoveAll: RemoveAll.Aux[PatchableKeys, PatchKeys, (PatchKeys, NotPatchKeys)] | |
, patchValueTypes: SelectAll.Aux[PatchableFields, PatchKeys, PatchValueTypes] | |
, patchValuesTagged: Taggeds.Aux[PatchValueTypes, PatchValues, PatchValuesTagged] | |
, patchValuesCoprod: Mapper.Aux[coprod.type, PatchValuesTagged, PatchValuesCoprod] | |
, patchCoprod: ZipWithKeys.Aux[PatchKeys, PatchValuesCoprod, PatchCoprod] | |
, notPatchValuesCoprod: SelectAll.Aux[PatchableCoprod, NotPatchKeys, NotPatchValuesCoprod] | |
, notPatchNones: Nones[NotPatchValuesCoprod] | |
, notPatchCoprod: ZipWithKeys.Aux[NotPatchKeys, NotPatchValuesCoprod, NotPatchCoprod] | |
, buildPatchable: RemoveAll.Aux[PatchableCoprod, PatchCoprod, (PatchCoprod, NotPatchCoprod)] | |
, zipPatchableCoprod: Zip.Aux[PatchableValues :: PatchableValuesCoprod :: HNil, ZipPatchableCoprod] | |
, mapZipPatchableCoprod: Mapper.Aux[patchApplyPatchable.type, ZipPatchableCoprod, PatchableValues] | |
, patchableZipWithKeys: ZipWithKeys.Aux[PatchableKeys, PatchableValues, PatchableFields] | |
): Patch[Patchable] = | |
new Patch[Patchable] { | |
val patchValues = patchUnzip.values(patch) | |
val patchValuesTagged0 = patchValuesTagged(patchValues) | |
val patchValuesCoprod0 = patchValuesCoprod(patchValuesTagged0) | |
val patchCoprod0 = patchCoprod(patchValuesCoprod0) | |
val patchableKeys = patchableUnzip.keys() | |
val patchRemoveAll0 = patchKeysRemoveAll(patchableKeys) | |
val notPatchKeys = patchRemoveAll0._2 | |
val notPatchCoprod0 = notPatchCoprod(notPatchNones()) | |
val buildPatchable0 = buildPatchable.reinsert(patchCoprod0, notPatchCoprod0) | |
override type OptionalFields = PatchableCoprod | |
override def updates: OptionalFields = buildPatchable0 | |
override def apply(patchable: Patchable): Patchable = { | |
val patchableFields0 = patchableLG.to(patchable) | |
val patchableValues0 = patchableUnzip.values(patchableFields0) | |
val patchableValuesCoprod1 = patchableValuesCoprod0(buildPatchable0) | |
val zipPatchableCoprod0 = zipPatchableCoprod(patchableValues0 :: patchableValuesCoprod1 :: HNil) | |
val mapZipPatchableCoprod0 = mapZipPatchableCoprod(zipPatchableCoprod0) | |
val patchableZipWithKeys0 = patchableZipWithKeys(mapZipPatchableCoprod0) | |
val patchable0 = patchableLG.from(patchableZipWithKeys0) | |
patchable0 | |
} | |
} | |
def applyDynamicNamed(method: String)(rec: Any*): Patch[Patchable] = macro RecordMacros.forwardNamedImpl | |
} | |
} | |
object patchApplyPatchable extends Poly1 { | |
implicit def default[P] = | |
at[(P, PatchCoprodType[P])] { | |
case (_, Inl(value)) => | |
value | |
case (p, Inr(Inl(patch))) => | |
patch(p) | |
case (original, _) => | |
original | |
} | |
} | |
object toOption extends Poly1 { | |
implicit def apply[P] = at[P](_.some) | |
} | |
def taggeds[ | |
Tags <: HList, | |
Taggables <: HList | |
] | |
(implicit | |
taggeds: Taggeds[Tags, Taggables] | |
): taggeds.type = taggeds | |
sealed trait Taggeds[ | |
Tags <: HList, | |
Taggables <: HList | |
] { | |
type Taggeds <: HList | |
def apply(taggables: Taggables): Taggeds | |
} | |
object Taggeds { | |
final type Aux[ | |
Tags <: HList, | |
Taggables0 <: HList, | |
Taggeds0 <: HList | |
] = Taggeds[Tags, Taggables0] {type Taggeds = Taggeds0} | |
} | |
implicit def taggeds0[ | |
Tags <: HList, | |
Taggers0 <: HList, | |
Taggables0 <: HList, | |
Taggeds0 <: HList | |
] | |
(implicit | |
asTaggers: Mapped.Aux[Tags, Tagger, Taggers0], | |
taggers: Taggers[Taggers0], | |
tagApply: TagApply.Aux[Taggers0, Taggables0, Taggeds0] | |
) | |
: Taggeds.Aux[Tags, Taggables0, Taggeds0] = new Taggeds[Tags, Taggables0] { | |
override type Taggeds = Taggeds0 | |
override def apply(taggables: Taggables0): Taggeds0 = | |
tagApply(taggers(), taggables) | |
} | |
trait TagApply[FL <: HList, AL <: HList] extends DepFn2[FL, AL] with Serializable { | |
type Out <: HList | |
} | |
object TagApply { | |
def apply[ | |
TaggerList <: HList, | |
TaggableList <: HList | |
] | |
(implicit | |
tagApply: TagApply[TaggerList, TaggableList] | |
): tagApply.type = | |
tagApply | |
type Aux[FL <: HList, AL <: HList, Out0 <: HList] = TagApply[FL, AL] {type Out = Out0} | |
implicit def hnilTagApply: TagApply.Aux[HNil, HNil, HNil] = | |
new TagApply[HNil, HNil] { | |
type Out = HNil | |
def apply(fl: HNil, al: HNil): Out = HNil | |
} | |
implicit def hconsTagApply[ | |
Tag0, | |
Taggable, | |
Tags <: HList, | |
Taggables <: HList | |
] | |
(implicit | |
tagApply: TagApply[Tags, Taggables] | |
): TagApply.Aux[Tagger[Tag0] :: Tags, Taggable :: Taggables, (Taggable @@ Tag0) :: tagApply.Out] = | |
new TagApply[Tagger[Tag0] :: Tags, Taggable :: Taggables] { | |
type Out = (Taggable @@ Tag0) :: tagApply.Out | |
def apply(fl: Tagger[Tag0] :: Tags, al: Taggable :: Taggables): Out = fl.head(al.head) :: tagApply(fl.tail, al.tail) | |
} | |
} | |
final class Nones[Noneable <: HList](noneable: Noneable) { | |
def apply(): Noneable = noneable | |
} | |
object Nones { | |
def apply[Noneable <: HList](implicit nones: Nones[Noneable]): Nones[Noneable] = nones | |
implicit def nonesHNil: Nones[HNil] = new Nones(HNil) | |
implicit def nonesHCons[H, T <: HList](implicit t: Nones[T]): Nones[PatchCoprodType[H] :: T] = | |
new Nones(Inr(Inr(Inr(Inl(None)))) :: t()) | |
} | |
final class Taggers[Tags <: HList](tags: Tags) { | |
def apply(): Tags = tags | |
} | |
object Taggers { | |
def apply[Tags <: HList](implicit taggers: Taggers[Tags]): Taggers[Tags] = taggers | |
implicit def taggersHNil: Taggers[HNil] = new Taggers(HNil) | |
implicit def taggersHCons[H, T <: HList](implicit t: Taggers[T]): Taggers[Tagger[H] :: T] = | |
new Taggers(new Tagger[H] :: t()) | |
} | |
trait coprodLP { | |
this: Poly1 => | |
implicit def s[P]: Case.Aux[String @@ P, PatchCoprodType[P]] = | |
at[String @@ P] { s => | |
Inr(Inr(Inl(s))) | |
} | |
} | |
object coprod extends Poly1 with coprodLP { | |
implicit def p[P]: Case.Aux[P @@ P, PatchCoprodType[P]] = | |
at[P @@ P] { p => | |
Inl(p) | |
} | |
implicit def pp[P]: Case.Aux[Patch[P] @@ P, PatchCoprodType[P]] = | |
at[Patch[P] @@ P] { pp => | |
Inr(Inl(pp)) | |
} | |
} | |
} |
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
import patch._ | |
import dec._ | |
import io.circe._ | |
import parser._ | |
import generic.auto._ | |
import shapes._ | |
case class Pond( | |
name: String, | |
depth: Int = 4, | |
color: Double = 3.2 | |
) | |
case class Duck( | |
name: String, | |
pond: Pond, | |
age: Int = 0, | |
walkingStyle: Int = -1, | |
canQuack: Boolean = false, | |
canFly: Boolean = false | |
) | |
object Testy extends App { | |
//create programatically | |
val patch0 = Patch[Duck].apply( | |
name = "donald", | |
pond = Patch[Pond].apply( | |
name = "ponder", | |
depth = 41, | |
color = "red" //wrong type | |
) | |
) | |
//-- | |
//parse from json | |
val d = Duck("mrquackyer", Pond("deep pond")) | |
val json = parse("""{"name":"donald","pond":{"name":"ponder","depth":41,"color":"red"}}""").right.get | |
val i = Decoder[Patch[Duck]] | |
val patch = i.decodeJson(json).right.get | |
println("patch: " + patch.updates) | |
val upd = patch(d) | |
println("upd: " + upd) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment