By Ivanov Alexander / @4lex1v
type Route = RequestContext => Unit
trait Marshaller[-T] { def apply(value: T, ctx: MarshallingContext) }
lazy val registration: Route = { (path("register") & put) { register { completeAs(Created) } } }
lazy val authentication: Route = { (path("authenticate") & post) { authenticate { session => setCookie(session.asCookie) { complete(Accepted) } } } }
val modules: Route = registration ~ authentication ~ ...
trait RepositoryModule[M[+_]] { type Repository <: RepositoryLike implicit val M: Monad[M] val repo: Repository trait RepositoryLike { def getAllUsers(): M[List[Users]] } } trait RemoteRepositoryModule extends RepositoryModule[Task] { trait Repository extends RepositoryLike { def getAllUsers(): Task[List[Users]] = { ... } } } trait InMemoRepoModule extends RepositoryModule[Id] { trait Repository extends RepositoryLike { def getAllUsers(): Id[List[Users]] = { ... } } }
trait ServiceStackBase[M[+_]] extends RepositoryModule[M] class Service extends ServiceStackBase[Task] with RemoteRepositoryModule { object repo extends Repository } class ServiceSpec extends ServiceStackBase[Id] with InMemoRepoModule { object repo extends Repository }
trait ActivationModule[M[+_]] extends ServiceStorageModule[M] with MessengerModule[M] with SchedulingServiceModule[M] with StatisticsESModule with LoggingSupport { private implicit lazy val repo = collections.medikit.activations def initActivation(program: Program): D1[(ActivationKey, Activation)] = for { checked <- checkLimit(program) activation = Activation(checked) result <- storeActivation(activation) } yield result def findActivation(activationId: String): D1[Activation] = unwrap { findById[Activation](activationId).runLast.attempt >>= { case \/-(Some(res)) => Task.now(res) case _ => Task.fail(WrongActivationKeyRejection) } } def validKey: Directive1[String] = path(Segment) flatMap { case key if ObjectId.isValid(key) => provide(key) case _ => reject(WrongActivationKeyRejection) } def validateCode(submission: ConfirmationCode, activation: Activation): D0 = { if (activation.code == submission.code) pass else reject(ConfirmationCodeRejection) } def closeActivation(activationId: String): D0 = removeActivation(activationId).asD0 private def rejectIfExceeded(jobs: List[JobDetail]): D0 = { val exceeded = jobs.length < maxJobs if (exceeded) pass else reject(ProgramLimitExceededRejection) } private def checkLimit(program: Program): D1[Program] = rejectIfExceeded { jobFactory findJobDetails program.phone } asD1 program def storeActivation(activation: Activation): D1[(ActivationKey, Activation)] = unwrap { store[Activation](activation).runLast >>= { case None => Task.fail(DOR("Couldn't store activation request")) case Some(result) => result.fold(Task.fail(_), Task.now) map { case (id, act) => ActivationKey(id) -> act } } } def removeActivation(id: String): D1[Boolean] = unwrap { removeById(id).runLast >>= { case Some(\/-(res)) => Task.now(res) case _ => Task.fail(DOR("Couldn't remove activation")) } } lazy val activation = { (pathPrefix("activation") & validKey) { implicit (key: ApiKey) => (post & pathEnd) { (entityAs[Program] >>= initActivation) { case (key: ActivationKey, activation: Activation) => sendMessage(ActivationCode(activation)) { complete(Created, key) } } } ~ (post & path(ProgramIdSegment)) { (id: ProgramId) => (entityAs[ConfirmationCode] & findActivation(apiKey)) { case (submitCode: ConfirmationCode, activation: Activation) => validateCode(submitCode, activation) { (closeActivation(id) & scheduleProgram(activation.program)) { (sendMessage(Activated(activation)) & emitEvent(ProgramActivated(activation))) { completeAs(Accepted) { "Program activated" } } } } } } ~ (get & path(ProgramIdSegment)) { (id: ProgramId) => findActivation(id) { (activation: Activation) => sendMessage(ActivationCode(activation)) { completeAs(OK) { ActivationKey(activation.code) } } } } } } }
trait ActivationRoute[M[+_]] { module: ActivationModule[M] => private val activate = { (post & pathEnd) { (entityAs[Program] >>= initActivation) { case (key: ActivationKey, activation: Activation) => sendMessage(ActivationCode(activation)) { complete(Created, key) } } } } private val submit = { (post & validKey) { (aid: String) => (entityAs[ConfirmationCode] & findActivation(aid)) { case (submitCode: ConfirmationCode, activation: Activation) => validateCode(submitCode, activation) { (closeActivation(aid) & scheduleProgram(activation.program)) { (sendMessage(Activated(activation)) & emitEvent(ProgramActivated(activation))) { completeAs(Accepted) { "Program activated" } } } } } } } private val repeat = { (get & validKey) { (aid: String) => findActivation(aid) { (activation: Activation) => sendMessage(ActivationCode(activation)) { completeAs(OK) { ActivationKey(activation.code) } } } } } lazy val activation = pathPrefix("activation") { activate ~ submit ~ repeat } }
trait ActivationManager[M[+_]] { module: ActivationModule[M] => import RemoteApi._ import constraints.maxJobs private implicit lazy val repo = collections.medikit.activations def initActivation(program: Program): D1[(ActivationKey, Activation)] = for { checked <- checkLimit(program) activation = Activation(checked) result <- storeActivation(activation) } yield result def findActivation(activationId: String): D1[Activation] = unwrap { findById[Activation](activationId).runLast.attempt >>= { case \/-(Some(res)) => Task.now(res) case _ => Task.fail(WrongActivationKeyRejection) } } def validKey: Directive1[String] = path(Segment) flatMap { case key if ObjectId.isValid(key) => provide(key) case _ => reject(WrongActivationKeyRejection) } def validateCode(submission: ConfirmationCode, activation: Activation): D0 = { if (activation.code == submission.code) pass else reject(ConfirmationCodeRejection) } def closeActivation(activationId: String): D0 = removeActivation(activationId).asD0 private def rejectIfExceeded(jobs: List[JobDetail]): D0 = { val exceeded = jobs.length < maxJobs if (exceeded) pass else reject(ProgramLimitExceededRejection) } private def checkLimit(program: Program): D1[Program] = rejectIfExceeded { jobFactory findJobDetails program.phone } asD1 program def storeActivation(activation: Activation): D1[(ActivationKey, Activation)] = unwrap { store[Activation](activation).runLast >>= { case None => Task.fail(DOR("Couldn't store activation request")) case Some(result) => result.fold(Task.fail(_), Task.now) map { case (id, act) => ActivationKey(id) -> act } } } def removeActivation(id: String): D1[Boolean] = unwrap { removeById(id).runLast >>= { case Some(\/-(res)) => Task.now(res) case _ => Task.fail(DOR("Couldn't remove activation")) } } }
trait ActivationModule[M[+_]] extends ActivationManager[M] with ActivationRoutes[M] with ServiceStorageModule[M] with MessengerModule[M] with SchedulingServiceModule[M] with StatisticsESModule with LoggingSupport
trait StorageModule[M[+_]] { type Storage <: StorageLike implicit val M: Monad[M] val storage: Storage trait StorageLike { def store[A: Serializer](object: A): M[Key] } }
def register = for { creds <- credentials _ <- check(creds) account <- newAccount(creds) userId <- storage store account // returns M[Key] } yield userId
def unwrap[T](magnet: MonadMagnet[T]): D1[T] = magnet()
trait MonadMagnet[T] { def apply(): D1[T] }
object MonadMagnet { implicit def apply[M[_], V](monad: M[V]) (implicit mmh: MonadMagnetHandler[M]) = { new MonadMagnet[V] { def apply(): D1[V] = mmh handle monad } } }
implicit val taskMHandler: MonadMagnetHandler[Task] = { new MonadMagnetHandler[Task] { def handle[V](monad: Task[V]): D1[V] = onSuccess(monad) } }
implicit val idMHandler: MonadMagnetHandler[Id] = { new MonadMagnetHandler[Id] { def handle[V](value: V): D1[V] = provide(monad) } }
def register = for { creds <- credentials _ <- check(creds) account <- newAccount(creds) userId <- unwrap { storage store account } // returns D1[Key] } yield userId
trait ExtMonad[M[+_]] extends Monad[M] { def fork[A](monad: => M[A]): M[A] def withResult[A](monad: M[A]) (callback: (Throwable \/ A) => Unit): Unit }
implicit def apply[M[+_], A](monad: M[A]) (implicit M: ExtMonad[M]) = { new MonadUnwrapper[A] { def apply(): D1[A] = new D1[A] { def happly(f: (A :: HNil) => Route): Route = { ctx => M.withResult(M.fork(monad)) { case \/-(result) => f(result :: HNil)(ctx) case -\/(ex) => ctx.failWith(ex) } } } } }
abstract class Directive[L <: HList] { self ⇒ def happly(f: L ⇒ Route): Route def hflatMap[R <: HList](f: L ⇒ Directive[R]) = { new Directive[R] { def happly(g: R ⇒ Route) = self.happly { values ⇒ f(values).happly(g) } } } ... } type Directive0 = Directive[HNil] type Directive1[A] = Directive[A :: HNil]
formFields("firstName", "lastName", "userName", "pwd") { (fn, ln, un, pwd) => // Directive[HList] // implementation }Extract into a case class:
case class Credentials(firstName: String, lastName: String, userName: String, pwd: String) val data = formFields("firstName", "lastName", "userName", "pwd") data.as(Credentials) { credentials => // Directive1[Credentials] }
implicit val d1Monad: Monad[D1] = new Monad[D1] { def point[A](a: => A): D1[A] = provide(a) def bind[A, B](fa: D1[A])(f: (A) => D1[B]): D1[B] = { fa hflatMap { case value :: HNil => f(value) } } }
implicit val d1Monad: Monad[D1] = new Monad[D1] { def point[A](a: => A): D1[A] = provide(a) def bind[A, B](fa: D1[A])(f: (A) => D1[B]): D1[B] = { fa flatMap f } }
def point[A](a: => A): D0 = new D0 { def happly(f: HNil => Route): Route = { ctx => a; f(HNil)(ctx) } }
def bind[A, B](fa: D0)(f: A => D0): D0 = ???
val withSession: D1[Session] = for { creds <- credentials _ <- authenticate(creds) session <- initSession } yield session
implicit class Dir0Modifier(dir0: Directive0) { def flatMap(func: HNil => D1[Unit]): D1[Unit] = { new D1[Unit] { def happly(f: (::[Unit, HNil]) => Route): Route = { f(() :: HNil) } } } }
def tasksByUserId(id: String) = for { user <- userById(id) tasks <- scheduledTasks } yield userTasks(user, tasks)
trait Monad[F[_]] extends Applicative[F]
def tasksByUserId(id: String) = { (userById(id) |@| scheduledTasks) { userTasks } }
def tasksByUserId(id: String) = { ^(userById(id), scheduledTasks) { userTasks } }