no.kodeworks.kvarg.util.DomainRouter.scala Maven / Gradle / Ivy
package no.kodeworks.kvarg.util
import akka.actor.{Actor, ActorLogging, ActorRef}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.stream.{ActorMaterializer, Materializer}
import akka.util.Timeout
import no.kodeworks.kvarg.message._
import no.kodeworks.kvarg.model.{HasId, Page}
import no.kodeworks.kvarg.patch.{Patch, Pvalue}
import no.kodeworks.kvarg.util.AtLeastOnceDelivery.pattern
import io.circe.syntax._
import io.circe.{Decoder, Encoder, Json}
import shapeless.ops.hlist.Length
import shapeless.ops.record.UnzipFields
import shapeless._
import scala.concurrent.Future
import no.kodeworks.kvarg.util.Directives._
import cats.syntax.option._
import Crud._
import akka.http.scaladsl.marshalling.ToEntityMarshaller
import akka.http.scaladsl.unmarshalling.FromEntityUnmarshaller
import no.kodeworks.kvarg.util._
/*
This is neccessary when calling apply:
import io.circe.generic.auto._
*/
sealed trait DomainsRouter[Domain <: HList] {
type DomainRouters <: HList
type ServicesFields <: HList
def patchEncoders: Map[String, Encoder[Patch[_ <: Any]]]
def route(
actor: Actor with ActorLogging,
page: Page,
materializer: ActorMaterializer,
timeout: Timeout
): Route
}
object DomainsRouter {
def apply[Domains <: HList]
(implicit mkMkDomainsRouter: MkMkDomainsRouter[Domains]
): mkMkDomainsRouter.type = mkMkDomainsRouter
sealed trait MkMkDomainsRouter[Domains <: HList] {
type DomainRouters <: HList
type Out <: MkDomainsRouter[_]
def apply(domainRouters: DomainRouters): Out
}
object MkMkDomainsRouter {
type Aux[
Domains <: HList,
DomainRouters0 <: HList,
Out0 <: MkDomainsRouter[_]
] = MkMkDomainsRouter[Domains] {
type DomainRouters = DomainRouters0
type Out = Out0
}
}
implicit def mkMkDomainsRouterHNil: MkMkDomainsRouter.Aux[HNil, HNil, MkDomainsRouter.Aux[HNil, HNil]] =
new MkMkDomainsRouter[HNil] {
override type DomainRouters = HNil
override type Out = MkDomainsRouter.Aux[HNil, HNil]
override def apply(domainRouters: DomainRouters): Out = {
new MkDomainsRouter[HNil] {
override type DomainRouters0 = HNil
override def domainRouters0 = HNil
}
}
}
implicit def mkMkDomainsRouterHCons[DomainHead <: HList, DomainsTail <: HList]
(implicit
mkMkDomainsRouterTail: MkMkDomainsRouter[DomainsTail]
): MkMkDomainsRouter.Aux[
DomainHead :: DomainsTail,
DomainRouter[DomainHead] :: mkMkDomainsRouterTail.DomainRouters,
MkDomainsRouter.Aux[DomainHead :: DomainsTail, DomainRouter[DomainHead] :: mkMkDomainsRouterTail.DomainRouters]
]
= new MkMkDomainsRouter[DomainHead :: DomainsTail] {
override type DomainRouters = DomainRouter[DomainHead] :: mkMkDomainsRouterTail.DomainRouters
override type Out = MkDomainsRouter.Aux[DomainHead :: DomainsTail, DomainRouter[DomainHead] :: mkMkDomainsRouterTail.DomainRouters]
override def apply(domainRouters: DomainRouter[DomainHead] :: mkMkDomainsRouterTail.DomainRouters): Out = {
new MkDomainsRouter[DomainHead :: DomainsTail] {
override type DomainRouters0 = DomainRouter[DomainHead] :: mkMkDomainsRouterTail.DomainRouters
override def domainRouters0 = domainRouters
}
}
}
sealed trait MkDomainsRouter[Domains <: HList] {
type DomainRouters0 <: HList
def domainRouters0: DomainRouters0
def apply[
Services
, ServicesFields0 <: HList
, ServicesKeys0 <: HList
, KeysRouters0 <: HList
, Timeouts <: HList
, Actors <: HList
, Mats <: HList
, ServicesValues0 <: HList
, ServicesLength <: Nat
, ServicesDomainRouters0 <: HList
](services0: Services)
(implicit
fields: LabelledGeneric.Aux[Services, ServicesFields0]
, keysValues: UnzipFields.Aux[ServicesFields0, ServicesKeys0, ServicesValues0]
, servicesLength: Length.Aux[ServicesFields0, ServicesLength]
, domainsLength: Length.Aux[DomainRouters0, ServicesLength]
)
: DomainsRouter[Domains] = {
val routers = hlistToList[DomainRouter[_ <: HList]](domainRouters0)
val services = hlistToList[ActorRef](keysValues.values(fields.to(services0)))
val names = hlistToList[Symbol](keysValues.keys()).map(_.name)
val routersServicesNames: List[(DomainRouter[_ <: HList], ActorRef, String)] =
(routers zip services zip names) map { case ((r, s), n) => (r, s, n) }
new DomainsRouter[Domains] {
override type DomainRouters = DomainRouters0
override def patchEncoders: Map[String, Encoder[Patch[_]]] =
routers.map(_.patchEncoders).reduce(_ ++ _)
override def route(
actor: Actor with ActorLogging,
page: Page,
mat: ActorMaterializer,
timeout: Timeout
): Route = {
import actor.context.dispatcher
def getAllResult: Future[Json] = routersServicesNames.map {
case (router, service, name) =>
actor.log.debug("GetAllResult router {}, service {}, name {}", router, service.path.name, name)
import AtLeastOnceDelivery.pattern
import actor.context.dispatcher
val getAll: Future[GetAllReply[router.DomainList]] = (service.??(GetAll)(
actor.self, actor.context.system, timeout
)).mapTo[GetAllReply[router.DomainList]]
val toJson: Future[Json] = getAll.map(router.getAllReplyEncoder.apply)
toJson
} reduce ((f0, f1) =>
(f0 zip f1).map { case (j0, j1) => j0 deepMerge j1 })
val domainRoutes: Route = routersServicesNames.map {
case (router, service, _) => router.route(actor, service, page, timeout, mat)
} reduce (_ ~ _)
path("all") {
import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._
complete(getAllResult)
} ~ domainRoutes
}
}
}
}
object MkDomainsRouter {
type Aux[
Domains <: HList,
DomainRouters <: HList
] = MkDomainsRouter[Domains] {
type DomainRouters0 = DomainRouters
}
}
}
sealed trait DomainRouter[Domain <: HList] {
type DomainList <: HList
def patchEncoders: Map[String, Encoder[Patch[_ <: Any]]]
def getAllReplyEncoder: Encoder[GetAllReply[DomainList]]
def getAllRoute(actor: Actor with ActorLogging,
service: ActorRef,
timeout: Timeout,
mat: Materializer): Route
def eachRoute(actor: Actor with ActorLogging,
service: ActorRef,
page: Page,
timeout: Timeout,
mat: Materializer
): Route
def route(actor: Actor with ActorLogging,
service: ActorRef,
page: Page,
timeout: Timeout,
mat: Materializer): Route
}
object DomainRouter {
type Aux[
Domain <: HList,
DomainList0 <: HList] =
DomainRouter[Domain] {
type DomainList = DomainList0
}
def apply[Domain <: HList]
(implicit domainRouter: DomainRouter[Domain]
): domainRouter.type = domainRouter
implicit def domainRouterHNil: Aux[HNil, HNil] =
new DomainRouter[HNil] {
override type DomainList = HNil
override def toString = "HNilDomainRouter"
override def patchEncoders = Map.empty
override def getAllReplyEncoder: Encoder[GetAllReply[DomainList]] =
_ => Json.obj()
override def eachRoute(actor: Actor with ActorLogging,
service: ActorRef,
page: Page,
timeout: Timeout,
mat: Materializer): Route =
reject
override def getAllRoute(actor: Actor with ActorLogging,
service: ActorRef,
timeout: Timeout,
mat: Materializer): Route =
reject
override def route(actor: Actor with ActorLogging,
service: ActorRef,
page: Page,
timeout: Timeout,
mat: Materializer): Route =
reject
}
implicit def domainRouterHCons[ModelHead, ModelTail <: HList]
(implicit
domainRouterTail: DomainRouter[ModelTail]
, enc: Encoder[ModelHead]
, dec: Decoder[ModelHead]
, penc: Encoder[Patch[ModelHead]]
, pdec: Decoder[Patch[ModelHead]]
, unmarsh: FromEntityUnmarshaller[ModelHead]
, marsh: ToEntityMarshaller[ModelHead]
, pUnmarsh: FromEntityUnmarshaller[Patch[ModelHead]]
, pMarsh: ToEntityMarshaller[Patch[ModelHead]]
, listMarsh: ToEntityMarshaller[List[ModelHead]]
, tr0: ToEntityMarshaller[SaveInserted[ModelHead]]
, tr1: ToEntityMarshaller[SaveUpdated[ModelHead]]
, tr2: ToEntityMarshaller[IncompletelyUpdated[ModelHead]]
, tr3: ToEntityMarshaller[UpdateStashed[ModelHead]]
, tt: Typeable[ModelHead]
, hasId: HasId[ModelHead]
): Aux[ModelHead :: ModelTail, List[ModelHead] :: domainRouterTail.DomainList]
= new DomainRouter[ModelHead :: ModelTail] {
override type DomainList = List[ModelHead] :: domainRouterTail.DomainList
val singularName = typeableToSimpleName(tt)
val pluralName = singularName + "s"
override def toString = pluralName
override def eachRoute(actor: Actor with ActorLogging,
service: ActorRef,
page: Page,
timeout: Timeout,
mat: Materializer): Route =
extractRequestContext { rc =>
pathPrefix(singularName) {
get {
path(LongNumber ?) { id =>
import actor.context.dispatcher
processGet[ModelHead](service.??(Get[ModelHead](id)(tt))(
actor.self, actor.context.system, timeout
))
}
} ~ {
def doPatch(commit: Boolean, stash: Boolean) =
(pathEnd.tmap(_ => none[Long]) | path(LongNumber ?)) { id0 =>
entity(as[Patch[ModelHead]]) { pp =>
processSaveOrUpdate[ModelHead](service.??(
Update(
id0 match {
case Some(_) => pp.copy(pp.poptions + ('id -> Pvalue(id0)))
case _ => pp
}, page.some, commit, stash))(actor.self, actor.context.system, timeout))
}
}
parameters(
'commit.as[Boolean] ? commitDefault,
'stash.as[Boolean] ? stashDefault) { (commit, stash) =>
post {
entity(as[ModelHead]) { ent =>
processSaveOrUpdate[ModelHead](service.??(
Save(ent, page.some, commit, stash))(actor.self, actor.context.system, timeout))
} ~
doPatch(commit, stash)
} ~
(akka.http.scaladsl.server.Directives.patch | put) {
doPatch(commit, stash)
}
}
}
}
} ~ domainRouterTail.eachRoute(actor, service, page, timeout, mat)
override val patchEncoders =
domainRouterTail.patchEncoders + (typeableToSimpleName(tt) -> penc.asInstanceOf[Encoder[Patch[_ <: Any]]])
override val getAllReplyEncoder: Encoder[GetAllReply[DomainList]] = _.ts match {
case domainList0 :: domainList1 =>
domainRouterTail.getAllReplyEncoder(GetAllReply(domainList1)).asJson
.asObject.get.add(
pluralName, domainList0.asJson).asJson
}
override def getAllRoute(actor: Actor with ActorLogging,
service: ActorRef,
timeout: Timeout,
mat: Materializer): Route = {
implicit val enc = getAllReplyEncoder
(get & path(pluralName)) {
import de.heikoseeberger.akkahttpcirce.FailFastCirceSupport._
complete((service.??(GetAll)(
actor.self, actor.context.system, timeout
)).mapTo[GetAllReply[DomainList]])
}
}
override def route(actor: Actor with ActorLogging,
service: ActorRef,
page: Page,
timeout: Timeout,
mat: Materializer): Route =
getAllRoute(actor, service, timeout, mat) ~ eachRoute(actor, service, page, timeout, mat)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy