diff --git a/.bsp/sbt.json b/.bsp/sbt.json new file mode 100644 index 0000000..51af022 --- /dev/null +++ b/.bsp/sbt.json @@ -0,0 +1 @@ +{"name":"sbt","version":"1.6.2","bspVersion":"2.0.0-M5","languages":["scala"],"argv":["/Users/anastasia/.sdkman/candidates/java/11.0.14-zulu/zulu-11.jdk/Contents/Home/bin/java","-Xms100m","-Xmx100m","-classpath","/Users/anastasia/Library/Application Support/JetBrains/IdeaIC2021.3/plugins/Scala/launcher/sbt-launch.jar","xsbt.boot.Boot","-bsp","--sbt-launch-jar=/Users/anastasia/Library/Application%20Support/JetBrains/IdeaIC2021.3/plugins/Scala/launcher/sbt-launch.jar"]} \ No newline at end of file diff --git a/.github/workflows/major.yml b/.github/workflows/major.yml new file mode 100644 index 0000000..e9769a1 --- /dev/null +++ b/.github/workflows/major.yml @@ -0,0 +1,33 @@ +# This is a basic workflow to help you get started with Actions + +name: release/major + +on: + workflow_dispatch: + +jobs: + sbt: + name: sbt publish + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + # we don't know what commit the last tag was it's safer to get entire repo so previousStableVersion resolves + fetch-depth: 0 + - name: Set up JDK 11 + uses: olafurpg/setup-scala@v10 + with: + java-version: adopt@1.11.0-9 + - name: Publish + if: github.event.pusher.name != 'GitHub Actions Bot' + run: |- + git config user.name "GitHub Actions Bot" + git config user.email "<>" + sbt "versionBump major" + git add version.sbt + git commit -m "Bump version" + git push origin main + sbt publish + env: + PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} diff --git a/.github/workflows/minor.yml b/.github/workflows/minor.yml new file mode 100644 index 0000000..8df7ce6 --- /dev/null +++ b/.github/workflows/minor.yml @@ -0,0 +1,33 @@ +# This is a basic workflow to help you get started with Actions + +name: release/minor + +on: + workflow_dispatch: + +jobs: + sbt: + name: sbt publish + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + # we don't know what commit the last tag was it's safer to get entire repo so previousStableVersion resolves + fetch-depth: 0 + - name: Set up JDK 11 + uses: olafurpg/setup-scala@v10 + with: + java-version: adopt@1.11.0-9 + - name: Publish + if: github.event.pusher.name != 'GitHub Actions Bot' + run: |- + git config user.name "GitHub Actions Bot" + git config user.email "<>" + sbt "versionBump minor" + git add version.sbt + git commit -m "Bump version" + git push origin main + sbt publish + env: + PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml new file mode 100644 index 0000000..1d234c2 --- /dev/null +++ b/.github/workflows/patch.yml @@ -0,0 +1,35 @@ +name: release/patch + +on: + workflow_dispatch: + push: + branches: + - main + tags: ["*"] + +jobs: + sbt: + name: sbt publish + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + # we don't know what commit the last tag was it's safer to get entire repo so previousStableVersion resolves + fetch-depth: 0 + - name: Set up JDK 11 + uses: olafurpg/setup-scala@v10 + with: + java-version: adopt@1.11.0-9 + - name: Publish + if: github.event.pusher.name != 'GitHub Actions Bot' + run: |- + git config user.name "GitHub Actions Bot" + git config user.email "<>" + sbt "versionBump patch" + git add version.sbt + git commit -m "Bump version" + git push origin main + sbt publish + env: + PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} \ No newline at end of file diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000..834f2d2 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1 @@ +version = 2.7.5 \ No newline at end of file diff --git a/build.sbt b/build.sbt index 8f70910..9eaa8e8 100644 --- a/build.sbt +++ b/build.sbt @@ -1,13 +1,26 @@ -name := "workshop" - -version := "0.1" - -scalaVersion := "2.13.6" - -libraryDependencies ++= List( - "com.softwaremill.sttp.client3" %% "async-http-client-backend-monix" % "3.3.15", - "com.softwaremill.sttp.client3" %% "circe" % "3.3.15", - "io.circe" %% "circe-generic" % "0.14.1" -) - -libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.11" % Runtime +name := "boring" +scalaVersion := "2.13.3" +organization := "Prom3th3us" + +/* +import sbtghpackages.TokenSource.{Environment, GitConfig} +githubOwner := "Prom3th3us" +githubRepository := "boring" +githubTokenSource := TokenSource.Or( + GitConfig("github.token"), + Environment("PUBLISH_TOKEN") +)*/ + +libraryDependencies += "org.typelevel" %% "cats-effect" % "3.3.11" + +libraryDependencies ++= List( + "com.softwaremill.sttp.client3" %% "circe" % "3.3.15", + "io.circe" %% "circe-generic" % "0.14.1" +) + +libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.2.11" % Runtime +// https://mvnrepository.com/artifact/com.typesafe/config +libraryDependencies += "com.typesafe" % "config" % "1.4.2" + +// https://mvnrepository.com/artifact/org.slf4j/slf4j-api +libraryDependencies += "org.slf4j" % "slf4j-api" % "1.7.36" diff --git a/project/VersionBump.scala b/project/VersionBump.scala new file mode 100644 index 0000000..12ec08e --- /dev/null +++ b/project/VersionBump.scala @@ -0,0 +1,57 @@ +import sbt.Keys.version + +import scala.util.Try + +class VersionBump(currentVersion: String) { + + def apply(arg: Option[String]): Option[Unit] = + for { + argument <- arg // + done <- argument match { + case "patch" => + Versioning.increasePatchVersion + case "minor" => + Versioning.increaseMinorVersion + case "major" => + Versioning.increaseMajorVersion + case _ => + None + } + } yield done + + case class Version(major: Int, minor: Int, patch: Int) { + override def toString = s"""version := "$major.$minor.$patch"""" + def majorRelease = copy(major = major + 1, minor = 0, patch = 0) + def minorRelease = copy(minor = minor + 1, patch = 0) + def patchRelease = copy(patch = patch + 1) + } + object Version { + def apply(version: String): Option[Version] = { + version.split('.').toList match { + case major :: minor :: patch :: ignored => + for { + major <- Try(major.toInt).toOption + minor <- Try(minor.toInt).toOption + patch <- Try(patch.toInt).toOption + } yield Version(major, minor, patch) + case _ => None + } + } + } + + object Versioning { + import java.io.PrintWriter + private def write(filename: String, text: String) = + new PrintWriter(filename) { write(text); close() } + + private def perform(upgrade: Version => Version): Option[Unit] = + Version(currentVersion).map { version => + write("version.sbt", s"${upgrade(version)}") + } + + def increaseMajorVersion: Option[Unit] = perform(_.majorRelease) + def increaseMinorVersion: Option[Unit] = perform(_.minorRelease) + def increasePatchVersion: Option[Unit] = perform(_.patchRelease) + } + +} diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..006867b --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1 @@ +//addSbtPlugin("com.codecommit" % "sbt-github-packages" % "0.5.2") diff --git a/src/main/scala/arch/app/Runner.scala b/src/main/scala/arch/app/Runner.scala deleted file mode 100644 index dfbeb93..0000000 --- a/src/main/scala/arch/app/Runner.scala +++ /dev/null @@ -1,43 +0,0 @@ -package arch.app - -import arch.common.ProgramLive -import arch.domain.modules.user.service.UserAction -import com.typesafe.config.{Config, ConfigFactory} - -object Runner { - import arch.common.ProgramBuilder._ - import scala.jdk.CollectionConverters._ - - val prod = true - val config: Config = ConfigFactory.parseMap(Map( - "user.flag" -> true, - "user.value" -> 1 - ).asJava) - - def main(args: Array[String]): Unit = { - val result = if(prod) { - println(s"RUNNING ENV=prod") - // DEFINITIONS - import scala.concurrent.Await - import scala.concurrent.duration._ - type Env[A] = ProgramLive.App[A] - // EXECUTION - val router = implicitly[ProgramBuilder[Env]].buildApp(config) - val actionResult = router.publish(UserAction(1)) - // OUTPUT - val scheduler = monix.execution.Scheduler.Implicits.global - Await.result(actionResult.value.runToFuture(scheduler), 1.second) - } else { - println(s"RUNNING ENV=test") - // DEFINITIONS - type Env[A] = ProgramLive.Test[A] - // EXECUTION - val router = implicitly[ProgramBuilder[Env]].buildApp(config) - val actionResult = router.publish(UserAction(1)) - // OUTPUT - actionResult - } - - println(result) - } -} diff --git a/src/main/scala/arch/common/Program.scala b/src/main/scala/arch/common/Program.scala index b3b378f..ef522c9 100644 --- a/src/main/scala/arch/common/Program.scala +++ b/src/main/scala/arch/common/Program.scala @@ -10,16 +10,38 @@ object Program { type MError[F[_]] = MonadError[F, ProgramError] case class Context(name: String, metadata: Map[String, Any] = Map.empty) - case class ProgramError(error: Any, msg: String, ctx: Context, errorCode: Int = unknownErrorCode, stackTrace: Seq[String] = Seq.empty) + case class ProgramError( + error: Any, + msg: String, + ctx: Context, + errorCode: Int = unknownErrorCode, + stackTrace: Seq[String] = Seq.empty + ) object ProgramError { - def fromThrowable(exception: Throwable): Context => Int => ProgramError = ctx => errorCode => { - val stackTrace = Seq.empty // exception.getStackTrace.map(_.toString).take(10) - ProgramError(exception, "error message: " + exception.getMessage, ctx, errorCode, stackTrace) - } - def fromError[E](e: E): Context => Int => ProgramError = ctx => errorCode => { - ProgramError(e, "error message: " + e.toString, ctx, errorCode, stackTrace = Seq.empty) - } + def fromThrowable(exception: Throwable): Context => Int => ProgramError = + ctx => + errorCode => { + val stackTrace = + Seq.empty // exception.getStackTrace.map(_.toString).take(10) + ProgramError( + exception, + "error message: " + exception.getMessage, + ctx, + errorCode, + stackTrace + ) + } + def fromError[E](e: E): Context => Int => ProgramError = ctx => + errorCode => { + ProgramError( + e, + "error message: " + e.toString, + ctx, + errorCode, + stackTrace = Seq.empty + ) + } } object MError { @@ -27,22 +49,32 @@ object Program { } object App { - def fromEither[F[_]: MError, E, A](ctx: Context)(errorCode: => Int = unknownErrorCode)(a: => Either[E, A]): F[A] = + def fromEither[F[_]: MError, E, A]( + ctx: Context + )(errorCode: => Int = unknownErrorCode)(a: => Either[E, A]): F[A] = MError[F].fromEither[A]( a match { - case Left(e) => Left(ProgramError.fromError(e)(ctx)(errorCode)) + case Left(e) => Left(ProgramError.fromError(e)(ctx)(errorCode)) case Right(value) => Right(value) } ) - def fromTry[F[_]: MError, A](ctx: Context)(errorCode: => Int = unknownErrorCode)(a: => Try[A]): F[A] = + def fromTry[F[_]: MError, A]( + ctx: Context + )(errorCode: => Int = unknownErrorCode)(a: => Try[A]): F[A] = MError[F].fromEither[A]( a match { - case Failure(exception) => Left(ProgramError.fromThrowable(exception)(ctx)(errorCode)) + case Failure(exception) => + Left(ProgramError.fromThrowable(exception)(ctx)(errorCode)) case Success(value) => Right(value) } ) - def lift[F[_]: MError, A](ctx: Context)(errorCode: => Int = unknownErrorCode)(a: => A = ()): F[A] = fromTry(ctx)(errorCode)(Try(a)) - def apply[F[_]: MError, A](ctx: Context, errorCode: => Int = unknownErrorCode)(a: => A): F[A] = lift(ctx)(errorCode)(a) - def unit[F[_]: MError](ctx: Context): F[Unit] = apply(ctx)() + def lift[F[_]: MError, A](ctx: Context)( + errorCode: => Int = unknownErrorCode + )(a: => A = ()): F[A] = fromTry(ctx)(errorCode)(Try(a)) + def apply[F[_]: MError, A]( + ctx: Context, + errorCode: => Int = unknownErrorCode + )(a: => A): F[A] = lift(ctx)(errorCode)(a) + def unit[F[_]: MError](ctx: Context): F[Unit] = apply[F, Unit](ctx)(()) } -} \ No newline at end of file +} diff --git a/src/main/scala/arch/common/ProgramBuilder.scala b/src/main/scala/arch/common/ProgramBuilder.scala deleted file mode 100644 index 1784308..0000000 --- a/src/main/scala/arch/common/ProgramBuilder.scala +++ /dev/null @@ -1,52 +0,0 @@ -package arch.common - -import arch.common.ProgramLive.{App, Test} -import arch.domain.modules.user.UserConfig.{UserConfigApp, UserConfigF, UserConfigTest} -import arch.domain.modules.user.UserRepoF -import arch.domain.modules.user.UserRepoLive.{UserRepoApp, UserRepoTest} -import arch.domain.modules.user.service.UserAction.UserActionHandler -import arch.domain.modules.user.service.UserServiceLive.{UserServiceApp, UserServiceTest} -import arch.domain.modules.user.service.{UserAction, UserServiceF} -import arch.infra.json.JsonLibraryF -import arch.infra.json.JsonLibraryLive.{JsonLibraryApp, JsonLibraryTest} -import arch.infra.logging.LoggingLibrary -import arch.infra.logging.LoggingLive.{LoggingApp, LoggingTest} -import arch.infra.monitoring.MonitoringLibrary -import arch.infra.monitoring.MonitoringLive.{MonitoringApp, MonitoringTest} -import arch.infra.router.RouterF -import arch.infra.router.RouterLive.{RouterApp, RouterTest} -import com.typesafe.config.Config - -object ProgramBuilder { - trait ProgramBuilder[F[_]] { - def buildApp(config: Config): RouterF[F] - } - - implicit lazy val production: ProgramBuilder[App] = (config: Config) => { - import ProgramLive.App - implicit lazy val userRepoLive: UserRepoF[App] = UserRepoApp - implicit lazy val jsonLibraryLive: JsonLibraryF[App] = JsonLibraryApp - implicit lazy val monitoring: MonitoringLibrary[App] = MonitoringApp - implicit lazy val userConfigF: UserConfigF[App] = UserConfigApp - implicit lazy val runService: UserServiceF[App] = new UserServiceApp(config.getConfig("user")) - implicit lazy val logger: LoggingLibrary[App] = LoggingApp - implicit lazy val router: RouterF[App] = new RouterApp() - .subscribe[UserAction](new UserActionHandler[App]()) - .subscribe[UserAction](new UserActionHandler[App]()) // this is only added for debugging purposes - router - } - - implicit lazy val testing: ProgramBuilder[Test] = (config: Config) => { - import ProgramLive.Test - implicit lazy val userRepoLive: UserRepoF[Test] = UserRepoTest - implicit lazy val jsonLibraryLive: JsonLibraryF[Test] = JsonLibraryTest - implicit lazy val monitoring: MonitoringLibrary[Test] = MonitoringTest - implicit lazy val userConfigF: UserConfigF[Test] = UserConfigTest - implicit lazy val runService: UserServiceF[Test] = new UserServiceTest(config.getConfig("user")) - implicit lazy val logger: LoggingLibrary[Test] = LoggingTest - implicit lazy val router: RouterF[Test] = new RouterTest() - .subscribe[UserAction](new UserActionHandler[Test]()) - .subscribe[UserAction](new UserActionHandler[Test]()) // this is only added for debugging purposes - router - } -} diff --git a/src/main/scala/arch/common/ProgramLive.scala b/src/main/scala/arch/common/ProgramLive.scala deleted file mode 100644 index 2e3db8a..0000000 --- a/src/main/scala/arch/common/ProgramLive.scala +++ /dev/null @@ -1,12 +0,0 @@ -package arch.common - -import arch.common.Program.ProgramError -import cats.data.EitherT -import monix.eval.Task - -object ProgramLive { - - // each type should be a subtype of MonadError - type App[A] = EitherT[Task, ProgramError, A] - type Test[A] = Either[ProgramError, A] -} diff --git a/src/main/scala/arch/domain/Model.scala b/src/main/scala/arch/domain/Model.scala deleted file mode 100644 index 99f8507..0000000 --- a/src/main/scala/arch/domain/Model.scala +++ /dev/null @@ -1,9 +0,0 @@ -package arch.domain - -trait Model { - type Entity - type Id - trait Identifiable[E <: Entity] { - def id(a: Entity): Id - } -} \ No newline at end of file diff --git a/src/main/scala/arch/domain/Repo.scala b/src/main/scala/arch/domain/Repo.scala deleted file mode 100644 index ed1ec28..0000000 --- a/src/main/scala/arch/domain/Repo.scala +++ /dev/null @@ -1,7 +0,0 @@ -package arch.domain - -trait Repo[F[_]] { - type M <: Model - def set(a: M#Entity)(implicit id: M#Identifiable[M#Entity]): F[Unit] - def get(id: M#Id): F[Option[M#Entity]] -} diff --git a/src/main/scala/arch/domain/modules/user/UserConfig.scala b/src/main/scala/arch/domain/modules/user/UserConfig.scala deleted file mode 100644 index dfdfb1f..0000000 --- a/src/main/scala/arch/domain/modules/user/UserConfig.scala +++ /dev/null @@ -1,23 +0,0 @@ -package arch.domain.modules.user - -import arch.common.Program.MError -import arch.common.ProgramLive.{App, Test} -import arch.infra.config.ConfigF -import cats.implicits._ -import com.typesafe.config.Config - -case class UserConfig(flag: Boolean, value: Option[Int]) - -object UserConfig { - class UserConfigF[F[_]: MError] extends ConfigF[UserConfig, F] { - override def fromConfig(config: Config): F[UserConfig] = { - for { - flag <- ConfigF.parse[Boolean, F](config)(_.getBoolean("flag")) - value <- ConfigF.parseOpt[Int, F](config)(_.getInt("value")) - } yield UserConfig(flag, value) - } - } - - object UserConfigApp extends UserConfigF[App] - object UserConfigTest extends UserConfigF[Test] -} \ No newline at end of file diff --git a/src/main/scala/arch/domain/modules/user/UserRepoF.scala b/src/main/scala/arch/domain/modules/user/UserRepoF.scala deleted file mode 100644 index aafa954..0000000 --- a/src/main/scala/arch/domain/modules/user/UserRepoF.scala +++ /dev/null @@ -1,28 +0,0 @@ -package arch.domain.modules.user - -import arch.common.Program -import arch.common.Program.{Context, MError} -import arch.domain.Repo -import arch.domain.modules.user.model.UserModel -import arch.domain.modules.user.model.UserModel.User - -// IMPL class on top Map -class UserRepoF[F[_]: MError] extends Repo[F] { - - type M = UserModel.type - - private val module = Context("user") - private val userRepoErrorCode = 1 - - private var users: Map[M#Id, User] = Map.empty[M#Id, User] - - override def set(a: User)(implicit id: M#Identifiable[User]): F[Unit] = - Program.App[F, Unit](module, userRepoErrorCode) { - users = users.updated(id.id(a), a) - } - - override def get(id: M#Id): F[Option[User]] = - Program.App[F, Option[User]](module, userRepoErrorCode) { - users.get(id) - } -} diff --git a/src/main/scala/arch/domain/modules/user/UserRepoLive.scala b/src/main/scala/arch/domain/modules/user/UserRepoLive.scala deleted file mode 100644 index 2d8e736..0000000 --- a/src/main/scala/arch/domain/modules/user/UserRepoLive.scala +++ /dev/null @@ -1,9 +0,0 @@ -package arch.domain.modules.user - -import arch.common.ProgramLive.{App, Test} - -object UserRepoLive { - object UserRepoApp extends UserRepoF[App] - object UserRepoTest extends UserRepoF[Test] -} - diff --git a/src/main/scala/arch/domain/modules/user/model/UserModel.scala b/src/main/scala/arch/domain/modules/user/model/UserModel.scala deleted file mode 100644 index d2f8f75..0000000 --- a/src/main/scala/arch/domain/modules/user/model/UserModel.scala +++ /dev/null @@ -1,18 +0,0 @@ -package arch.domain.modules.user.model - -import arch.domain.Model -import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder} -import io.circe.{Decoder, Encoder} - -object UserModel extends Model { - type Id = String - type Entity = User - - case class User(id: Id) - - object User { - implicit val userDecoder: Decoder[User] = deriveDecoder[User] - implicit val userEncoder: Encoder[User] = deriveEncoder[User] - implicit val identifier: Identifiable[User] = (user: User) => user.id - } -} diff --git a/src/main/scala/arch/domain/modules/user/service/UserAction.scala b/src/main/scala/arch/domain/modules/user/service/UserAction.scala deleted file mode 100644 index c685930..0000000 --- a/src/main/scala/arch/domain/modules/user/service/UserAction.scala +++ /dev/null @@ -1,28 +0,0 @@ -package arch.domain.modules.user.service - -import arch.common.Program.MError -import arch.domain.modules.user.UserConfig.UserConfigF -import arch.domain.modules.user.UserRepoF -import arch.domain.modules.user.model.UserModel.User -import arch.infra.json.JsonLibraryF -import arch.infra.json.JsonLibraryLive.JsonLibraryTest -import arch.infra.monitoring.MonitoringLibrary -import arch.infra.router.{Action, ActionHandler} -import io.circe.Json - -case class UserAction(id: Int) extends Action { - type ReturnType = (Option[String], Option[JsonLibraryTest.JsonType], User) -} - -object UserAction { - class UserActionHandler[F[_]: MError]( - implicit - service: UserServiceF[F], - userRepo: UserRepoF[F], - jsonLibrary: JsonLibraryF[F], - userConfig: UserConfigF[F], - monitoring: MonitoringLibrary[F] - ) extends ActionHandler[F, UserAction] { - override def handle(a: UserAction): F[(Option[String], Option[Json], User)] = service.run(a) - } -} diff --git a/src/main/scala/arch/domain/modules/user/service/UserService.scala b/src/main/scala/arch/domain/modules/user/service/UserService.scala deleted file mode 100644 index b856fdb..0000000 --- a/src/main/scala/arch/domain/modules/user/service/UserService.scala +++ /dev/null @@ -1,17 +0,0 @@ -package arch.domain.modules.user.service - -import arch.domain.modules.user.UserConfig.UserConfigF -import arch.domain.modules.user.UserRepoF -import arch.infra.json.JsonLibraryF -import arch.infra.monitoring.MonitoringLibrary -import arch.infra.router.Action - -trait UserService[F[_], A <: Action] { - def run(args: A)( - // @TODO revisit if this should go into constructor - implicit userRepo: UserRepoF[F], - jsonLibrary: JsonLibraryF[F], - userConfig: UserConfigF[F], - monitoring: MonitoringLibrary[F] - ): F[A#ReturnType] -} diff --git a/src/main/scala/arch/domain/modules/user/service/UserServiceF.scala b/src/main/scala/arch/domain/modules/user/service/UserServiceF.scala deleted file mode 100644 index 9560ee9..0000000 --- a/src/main/scala/arch/domain/modules/user/service/UserServiceF.scala +++ /dev/null @@ -1,42 +0,0 @@ -package arch.domain.modules.user.service - -import arch.common.Program.{Context, MError, ProgramError} -import arch.domain.modules.user.UserConfig.UserConfigF -import arch.domain.modules.user.UserRepoF -import arch.domain.modules.user.model.UserModel.User -import arch.infra.json.JsonLibraryF -import arch.infra.json.JsonLibraryLive.JsonLibraryTest -import arch.infra.monitoring.MonitoringLibrary -import cats.implicits._ -import com.typesafe.config.Config - -class UserServiceF[F[_]: MError](c: Config) extends UserService[F, UserAction] { - def run(args: UserAction)( - implicit userRepo: UserRepoF[F], - jsonLibrary: JsonLibraryF[F], - userConfig: UserConfigF[F], - monitoring: MonitoringLibrary[F] - ): F[(Option[String], Option[JsonLibraryTest.JsonType], User)] = { - val ctx = Context("run") - - val user = User("Franco") - val userJsonFromTo = jsonLibrary.jsonFromTo[User] - val counter = monitoring.counter("counter") - for { - config <- userConfig.fromConfig(c) - _ <- counter.increment() - _ = println(s"user config = $config") - _ <- userRepo.set(user) - u1 <- userRepo.get(user.id) - u2 <- userRepo.get("Euge") - js1 = u1.map(userJsonFromTo.from).map(jsonLibrary.prettyPrint) - parsedJson <- { - js1.map(jsonLibrary.parse) match { - case Some(maybeParsed) => maybeParsed.flatMap(userJsonFromTo.to) - case None => MError[F].raiseError(ProgramError((), "js1 missing", ctx)) - } - } - js2 = u2.map(userJsonFromTo.from) - } yield (js1, js2, parsedJson) - } -} \ No newline at end of file diff --git a/src/main/scala/arch/domain/modules/user/service/UserServiceLive.scala b/src/main/scala/arch/domain/modules/user/service/UserServiceLive.scala deleted file mode 100644 index 82d023d..0000000 --- a/src/main/scala/arch/domain/modules/user/service/UserServiceLive.scala +++ /dev/null @@ -1,9 +0,0 @@ -package arch.domain.modules.user.service - -import arch.common.ProgramLive.{App, Test} -import com.typesafe.config.Config - -object UserServiceLive { - class UserServiceApp(c: Config) extends UserServiceF[App](c) - class UserServiceTest(c: Config) extends UserServiceF[Test](c) -} diff --git a/src/main/scala/arch/infra/config/ConfigF.scala b/src/main/scala/arch/infra/config/ConfigF.scala index 48b4ce8..08569a2 100644 --- a/src/main/scala/arch/infra/config/ConfigF.scala +++ b/src/main/scala/arch/infra/config/ConfigF.scala @@ -14,7 +14,8 @@ object ConfigF { def parse[A, F[_]: MError](cfg: Config)(fn: Config => A): F[A] = { val either = Try(fn(cfg)) match { - case Failure(exception) => Left(ProgramError(exception, exception.getMessage, ctx)) + case Failure(exception) => + Left(ProgramError(exception, exception.getMessage, ctx)) case Success(value) => Right(value) } MError[F].fromEither(either) diff --git a/src/main/scala/arch/infra/json/JsonLibraryF.scala b/src/main/scala/arch/infra/json/JsonLibraryF.scala index 8b04e4f..9987d5f 100644 --- a/src/main/scala/arch/infra/json/JsonLibraryF.scala +++ b/src/main/scala/arch/infra/json/JsonLibraryF.scala @@ -14,7 +14,9 @@ class JsonLibraryF[F[_]: MError] extends JsonLibrary[F] { class To[A](implicit d: Decoder[A]) extends JsonTo[A] { override def to(json: JsonType): F[A] = - Program.App.fromEither[F, Throwable, A](ctx)(jsonDecodeErrorCode)(d.decodeJson(json)) + Program.App.fromEither[F, Throwable, A](ctx)(jsonDecodeErrorCode)( + d.decodeJson(json) + ) } class From[A](implicit e: Encoder[A]) extends JsonFrom[A] { override def from(from: A): JsonType = e.apply(from) @@ -28,10 +30,13 @@ class JsonLibraryF[F[_]: MError] extends JsonLibrary[F] { } object JsonParserF extends JsonParser { override def parse(str: String): F[Json] = - Program.App.fromEither[F, Throwable, Json](ctx)(jsonParseErrorCode)(parser.parse(str)) + Program.App.fromEither[F, Throwable, Json](ctx)(jsonParseErrorCode)( + parser.parse(str) + ) } - def jsonFromTo[A](implicit d: Decoder[A], e: Encoder[A]): JsonFromTo[A] = new JsonFromToF[A](new To(), new From()) + def jsonFromTo[A](implicit d: Decoder[A], e: Encoder[A]): JsonFromTo[A] = + new JsonFromToF[A](new To(), new From()) def prettyPrint(json: Json): String = PrettyPrinter.prettyPrint(json) def parse(json: String): F[Json] = JsonParserF.parse(json) } diff --git a/src/main/scala/arch/infra/json/JsonLibraryLive.scala b/src/main/scala/arch/infra/json/JsonLibraryLive.scala deleted file mode 100644 index eb13eec..0000000 --- a/src/main/scala/arch/infra/json/JsonLibraryLive.scala +++ /dev/null @@ -1,8 +0,0 @@ -package arch.infra.json - -import arch.common.ProgramLive.{App, Test} - -object JsonLibraryLive { - object JsonLibraryApp extends JsonLibraryF[App] - object JsonLibraryTest extends JsonLibraryF[Test] -} diff --git a/src/main/scala/arch/infra/logging/LoggingLive.scala b/src/main/scala/arch/infra/logging/LoggingLive.scala deleted file mode 100644 index 9271d48..0000000 --- a/src/main/scala/arch/infra/logging/LoggingLive.scala +++ /dev/null @@ -1,8 +0,0 @@ -package arch.infra.logging - -import arch.common.ProgramLive.{App, Test} - -object LoggingLive { - object LoggingApp extends LoggingF[App] - object LoggingTest extends LoggingF[Test] -} diff --git a/src/main/scala/arch/infra/monitoring/MonitoringF.scala b/src/main/scala/arch/infra/monitoring/MonitoringF.scala index 86f9976..0a57eef 100644 --- a/src/main/scala/arch/infra/monitoring/MonitoringF.scala +++ b/src/main/scala/arch/infra/monitoring/MonitoringF.scala @@ -2,11 +2,13 @@ package arch.infra.monitoring import arch.common.Program.MError import arch.infra.monitoring.MonitoringLibrary.{CounterF, GaugeF, HistogramF} -import cats.implicits._ // @TODO implement on top of some dependency (ie: grafana, kamon) class MonitoringF[F[_]: MError] extends MonitoringLibrary[F] { - override def counter(name: String, context: Map[String, String]): CounterF[F] = new CounterF[F] { + override def counter( + name: String, + context: Map[String, String] + ): CounterF[F] = new CounterF[F] { override def increment(): F[Unit] = MError[F].pure(println(s"counter increment")) diff --git a/src/main/scala/arch/infra/monitoring/MonitoringLibrary.scala b/src/main/scala/arch/infra/monitoring/MonitoringLibrary.scala index 3c72685..3c3ca71 100644 --- a/src/main/scala/arch/infra/monitoring/MonitoringLibrary.scala +++ b/src/main/scala/arch/infra/monitoring/MonitoringLibrary.scala @@ -5,7 +5,10 @@ import cats.implicits._ trait MonitoringLibrary[F[_]] { import MonitoringLibrary._ - def counter(name: String, context: Map[String, String] = Map.empty): CounterF[F] + def counter( + name: String, + context: Map[String, String] = Map.empty + ): CounterF[F] def gauge(name: String): GaugeF[F] def histogram(name: String): HistogramF[F] } @@ -42,7 +45,7 @@ object MonitoringLibrary { for { result <- codeToBenchmark _ = record(System.currentTimeMillis() - before) - } yield result + } yield result } } } diff --git a/src/main/scala/arch/infra/monitoring/MonitoringLive.scala b/src/main/scala/arch/infra/monitoring/MonitoringLive.scala deleted file mode 100644 index 26255a2..0000000 --- a/src/main/scala/arch/infra/monitoring/MonitoringLive.scala +++ /dev/null @@ -1,8 +0,0 @@ -package arch.infra.monitoring - -import arch.common.ProgramLive - -object MonitoringLive { - object MonitoringApp extends MonitoringF[ProgramLive.App] - object MonitoringTest extends MonitoringF[ProgramLive.Test] -} \ No newline at end of file diff --git a/src/main/scala/arch/infra/router/Action.scala b/src/main/scala/arch/infra/router/Action.scala index 6a1ad58..51967e5 100644 --- a/src/main/scala/arch/infra/router/Action.scala +++ b/src/main/scala/arch/infra/router/Action.scala @@ -1,5 +1,5 @@ package arch.infra.router -trait Action { - type ReturnType +trait Action[R] { + type ReturnType = R } diff --git a/src/main/scala/arch/infra/router/ActionHandler.scala b/src/main/scala/arch/infra/router/ActionHandler.scala deleted file mode 100644 index 3729f3c..0000000 --- a/src/main/scala/arch/infra/router/ActionHandler.scala +++ /dev/null @@ -1,5 +0,0 @@ -package arch.infra.router - -trait ActionHandler[F[_], A <: Action] { - def handle(a: A): F[A#ReturnType] -} diff --git a/src/main/scala/arch/infra/router/DispatcherF.scala b/src/main/scala/arch/infra/router/DispatcherF.scala new file mode 100644 index 0000000..7c32bbc --- /dev/null +++ b/src/main/scala/arch/infra/router/DispatcherF.scala @@ -0,0 +1,9 @@ +package arch.infra.router + +trait DispatcherF[F[_]] { + def dispatch[A <: Action[Any]]( + a: A + )(implicit fn: A => F[A#ReturnType]): F[A#ReturnType] = { + fn(a) + } +} diff --git a/src/main/scala/arch/infra/router/Router.scala b/src/main/scala/arch/infra/router/Router.scala index 905712d..63e132a 100644 --- a/src/main/scala/arch/infra/router/Router.scala +++ b/src/main/scala/arch/infra/router/Router.scala @@ -3,6 +3,8 @@ package arch.infra.router import scala.reflect.ClassTag trait Router[F[_]] { - def subscribe[A <: Action: ClassTag](handler: ActionHandler[F, A]): RouterF[F] - def publish[A <: Action](action: A): F[A#ReturnType] + def subscribe[A <: Action[Any]: ClassTag]( + handler: A => F[A#ReturnType] + ): Unit + def publish[A <: Action[Any]](action: A): F[A#ReturnType] } diff --git a/src/main/scala/arch/infra/router/RouterF.scala b/src/main/scala/arch/infra/router/RouterF.scala index 65f841e..3bdf56a 100644 --- a/src/main/scala/arch/infra/router/RouterF.scala +++ b/src/main/scala/arch/infra/router/RouterF.scala @@ -3,52 +3,86 @@ package arch.infra.router import arch.common.Program.{Context, MError, ProgramError} import arch.infra.logging.LoggingLibrary +import scala.collection.mutable import scala.reflect.ClassTag class RouterF[F[_]: MError]( - onSuccess: String => Unit = _ => { println("success") }, - onFailure: ProgramError => Unit = _ => { println("failure") }, - recordLatencyInMillis: (String, Long, Long) => Unit = (_, _, _) => { println("recording latency") }, - handlers: Map[Class[_], Action => F[Any]] = Map.empty[Class[_], Action => F[Any]] -)(implicit logger: LoggingLibrary[F]) extends Router[F] { + onSuccess: String => Unit = _ => { println("success") }, + onFailure: ProgramError => Unit = _ => { println("failure") }, + recordLatencyInMillis: (String, Long, Long) => Unit = (_, _, _) => { + println("recording latency") + } +)(implicit logger: LoggingLibrary[F]) + extends Router[F] { private val context: Context = Context("router") private val actionNotFoundErrorCode = 1 - override def publish[A <: Action](action: A): F[A#ReturnType] = + private val handlers: mutable.HashMap[Class[_], Action[Any] => F[Any]] = + mutable.HashMap.empty + + override def publish[A <: Action[Any]](action: A): F[A#ReturnType] = handlers .get(action.getClass) match { case Some(handler) => handleAction(action, handler) - case None => MError[F].raiseError( - ProgramError("action not found", s"action ${action.getClass.getSimpleName} not found", context, actionNotFoundErrorCode) - ) + case None => + MError[F].raiseError( + ProgramError( + "action not found", + s"action ${action.getClass.getSimpleName} not found", + context, + actionNotFoundErrorCode + ) + ) } - override def subscribe[A <: Action : ClassTag](handler: ActionHandler[F, A]): RouterF[F] = { + override def subscribe[A <: Action[Any]: ClassTag]( + handler: A => F[A#ReturnType] + ): Unit = { val classTag = implicitly[ClassTag[A]] if (handlers.contains(classTag.runtimeClass)) { - logger.logWarn("handler already subscribed")(context.copy( - metadata = context.metadata + ("handler_name" -> handler.getClass.getSimpleName) - )) - new RouterF(onSuccess, onFailure, recordLatencyInMillis, handlers) + logger.logWarn("handler already subscribed")( + context.copy( + metadata = + context.metadata + ("handler_name" -> classTag.runtimeClass.getClass.getSimpleName) + ) + ) + () } else { - val transformed: Action => F[Any] = (t: Action) => MError[F].map(handler.handle(t.asInstanceOf[A]))(_.asInstanceOf[Any]) - new RouterF(onSuccess, onFailure, recordLatencyInMillis, handlers + (classTag.runtimeClass -> transformed)) + val transformed: Action[_] => F[Any] = (t: Action[_]) => + MError[F].map(handler(t.asInstanceOf[A]))(_.asInstanceOf[Any]) + handlers.addOne((classTag.runtimeClass -> transformed)) } } - private def handleAction[A <: Action](action: A, handler: A => F[Any]): F[A#ReturnType] = { + private def handleAction[A <: Action[Any]]( + action: A, + handler: A => F[Any] + ): F[A#ReturnType] = { val before = System.currentTimeMillis() - val maybeResponse: F[A#ReturnType] = MError[F].map(handler(action))(_.asInstanceOf[A#ReturnType]) + val maybeResponse: F[A#ReturnType] = + MError[F].map(handler(action))(_.asInstanceOf[A#ReturnType]) val recoverable = MError[F].recoverWith(maybeResponse) { case error: ProgramError => onFailure(error) - recordLatencyInMillis(action.getClass.getSimpleName, before, System.currentTimeMillis()) + recordLatencyInMillis( + action.getClass.getSimpleName, + before, + System.currentTimeMillis() + ) maybeResponse } MError[F].map(recoverable) { result => onSuccess(action.getClass.getSimpleName) - recordLatencyInMillis(action.getClass.getSimpleName, before, System.currentTimeMillis()) + recordLatencyInMillis( + action.getClass.getSimpleName, + before, + System.currentTimeMillis() + ) result } } } + +object RouterF { + def apply[F[_]](implicit router: RouterF[F]): RouterF[F] = router +} diff --git a/src/main/scala/arch/infra/router/RouterLive.scala b/src/main/scala/arch/infra/router/RouterLive.scala deleted file mode 100644 index b06db79..0000000 --- a/src/main/scala/arch/infra/router/RouterLive.scala +++ /dev/null @@ -1,9 +0,0 @@ -package arch.infra.router - -import arch.common.ProgramLive.{App, Test} -import arch.infra.logging.LoggingLibrary - -object RouterLive { - class RouterApp(implicit logger: LoggingLibrary[App]) extends RouterF[App] - class RouterTest(implicit logger: LoggingLibrary[Test]) extends RouterF[Test] -} diff --git a/version.sbt b/version.sbt new file mode 100644 index 0000000..0f83621 --- /dev/null +++ b/version.sbt @@ -0,0 +1 @@ +version := "1.0.14" \ No newline at end of file diff --git a/versionBump.sbt b/versionBump.sbt new file mode 100644 index 0000000..daad65b --- /dev/null +++ b/versionBump.sbt @@ -0,0 +1,16 @@ +import sbt._ +import sbt.inputKey +import complete.DefaultParsers._ + +val versionBump = inputKey[Unit](""" + |ie.: sbt "versionBump major" + |ie.: sbt "versionBump minor" + |ie.: sbt "versionBump patch" + |""".stripMargin) +versionBump := { + new VersionBump( + currentVersion = version.value + ).apply( + arg = spaceDelimited("").parsed.headOption + ) +}