diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 551e6ca..de733f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,8 +10,9 @@ jobs: strategy: matrix: scala: - - 2.13.8 - - 2.12.17 + - 2.13.13 + - 2.12.19 + - 3.3.3 steps: - uses: actions/checkout@v3 diff --git a/.gitignore b/.gitignore index 116e2ff..3e0beff 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,10 @@ project/plugins/project/ *.ipr # Mac -.DS_Store \ No newline at end of file +.DS_Store + +# Metals/Bloop/Vscode specific +.metals +metals.sbt +.bloop +.vscode \ No newline at end of file diff --git a/build.sbt b/build.sbt index 8c78cfd..7197317 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,12 @@ import Dependencies._ +def crossSettings[T](scalaVersion: String, if3: List[T], if2: List[T]) = + CrossVersion.partialVersion(scalaVersion) match { + case Some((3, _)) => if3 + case Some((2, 12 | 13)) => if2 + case _ => Nil + } + lazy val commonSettings = Seq( organization := "com.evolutiongaming", homepage := Some(new URL("http://github.com/evolution-gaming/scassandra")), @@ -7,50 +14,71 @@ lazy val commonSettings = Seq( organizationName := "Evolution", organizationHomepage := Some(url("http://evolution.com")), scalaVersion := crossScalaVersions.value.head, - crossScalaVersions := Seq("2.13.8", "2.12.17"), + crossScalaVersions := Seq("2.13.13", "3.3.3", "2.12.19"), Compile / doc / scalacOptions ++= Seq("-groups", "-implicits", "-no-link-warnings"), publishTo := Some(Resolver.evolutionReleases), licenses := Seq(("MIT", url("https://opensource.org/licenses/MIT"))), releaseCrossBuild := true, - scalacOptsFailOnWarn := Some(false)) + scalacOptsFailOnWarn := Some(false), + scalacOptions ++= crossSettings( + scalaVersion.value, + if3 = List("-Ykind-projector", "-language:implicitConversions", "-explain", "-deprecation"), + if2 = List("-Xsource:3"), + )) -lazy val root = (project in file(".") - settings (name := "scassandra") - settings commonSettings - settings ( +lazy val root = (project in file(".")) + .settings(name := "scassandra") + .settings(commonSettings) + .settings( publish / skip := true, - skip / publishArtifact := true) - aggregate(scassandra, tests)) + skip / publishArtifact := true + ) + .aggregate(scassandra, tests) -lazy val scassandra = (project in file("scassandra") - settings (name := "scassandra") - settings commonSettings - settings (libraryDependencies ++= Seq( - Cats.core, - Cats.effect, - `config-tools`, - `cats-helper`, - sstream, - scalatest % Test, - nel, - `cassandra-driver`, - `executor-tools`, - Pureconfig.pureconfig, - Pureconfig.cats))) +lazy val scassandra = (project in file("scassandra")) + .settings(name := "scassandra") + .settings(commonSettings) + .settings( + libraryDependencies ++= Seq( + Cats.core, + Cats.effect, + `config-tools`, + `cats-helper`, + sstream, + scalatest % Test, + nel, + `cassandra-driver`, + `executor-tools`, + Pureconfig.cats + ) + ) + .settings( + libraryDependencies ++= crossSettings( + scalaVersion.value, + if3 = List(Pureconfig.core), + if2 = List(Pureconfig.pureconfig), + ) + ) -lazy val tests = (project in file("tests") - settings (name := "tests") - settings commonSettings - settings Seq( - publish / skip := true, - skip / publishArtifact := true, - Test / fork := true, - Test / parallelExecution := false) - dependsOn scassandra % "test->test;compile->compile" - settings (libraryDependencies ++= Seq( - `cassandra-launcher` % Test, - Slf4j.api % Test, - Slf4j.`log4j-over-slf4j` % Test, - Logback.core % Test, - Logback.classic % Test, - scalatest % Test))) +lazy val tests = (project in file("tests")) + .settings(name := "tests") + .settings(commonSettings) + .settings( + Seq( + publish / skip := true, + skip / publishArtifact := true, + Test / fork := true, + Test / parallelExecution := false + ) + ) + .dependsOn(scassandra % "test->test;compile->compile") + .settings( + libraryDependencies ++= Seq( + `testcontainers-cassandra` % Test, + Slf4j.api % Test, + Slf4j.`log4j-over-slf4j` % Test, + Logback.core % Test, + Logback.classic % Test, + scalatest % Test + ) + ) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 8cd4487..2ddd149 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -2,34 +2,36 @@ import sbt._ object Dependencies { - val `cassandra-driver` = "com.datastax.cassandra" % "cassandra-driver-core" % "3.11.3" - val scalatest = "org.scalatest" %% "scalatest" % "3.2.17" - val `executor-tools` = "com.evolutiongaming" %% "executor-tools" % "1.0.3" - val `config-tools` = "com.evolutiongaming" %% "config-tools" % "1.0.4" - val nel = "com.evolutiongaming" %% "nel" % "1.3.4" - val `cassandra-launcher` = "com.evolutiongaming" %% "cassandra-launcher" % "0.0.4" - val `cats-helper` = "com.evolutiongaming" %% "cats-helper" % "3.2.0" - val sstream = "com.evolutiongaming" %% "sstream" % "1.0.1" + val `cassandra-driver` = "com.datastax.cassandra" % "cassandra-driver-core" % "3.11.3" + val scalatest = "org.scalatest" %% "scalatest" % "3.2.17" + val `executor-tools` = "com.evolutiongaming" %% "executor-tools" % "1.0.4" + val `config-tools` = "com.evolutiongaming" %% "config-tools" % "1.0.5" + val nel = "com.evolutiongaming" %% "nel" % "1.3.5" + val `testcontainers-cassandra` = "com.dimafeng" %% "testcontainers-scala-cassandra" % "0.40.17" + val `cats-helper` = "com.evolutiongaming" %% "cats-helper" % "3.9.0" + val sstream = "com.evolutiongaming" %% "sstream" % "1.0.2" object Logback { - private val version = "1.4.3" + private val version = "1.4.11" val core = "ch.qos.logback" % "logback-core" % version val classic = "ch.qos.logback" % "logback-classic" % version } object Slf4j { - private val version = "1.7.36" + private val version = "2.0.9" val api = "org.slf4j" % "slf4j-api" % version val `log4j-over-slf4j` = "org.slf4j" % "log4j-over-slf4j" % version } object Cats { - val core = "org.typelevel" %% "cats-core" % "2.8.0" + val core = "org.typelevel" %% "cats-core" % "2.10.0" val effect = "org.typelevel" %% "cats-effect" % "3.4.8" } object Pureconfig { - private val version = "0.17.3" + private val version = "0.17.6" + + val core = "com.github.pureconfig" %% "pureconfig-core" % version val pureconfig = "com.github.pureconfig" %% "pureconfig" % version val cats = "com.github.pureconfig" %% "pureconfig-cats" % version } diff --git a/project/plugins.sbt b/project/plugins.sbt index 1803115..5601feb 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.9") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.11") addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.3.11") diff --git a/scassandra/src/main/scala-3/com/evolutiongaming/util/ToJava.scala b/scassandra/src/main/scala-3/com/evolutiongaming/util/ToJava.scala new file mode 100644 index 0000000..f709c50 --- /dev/null +++ b/scassandra/src/main/scala-3/com/evolutiongaming/util/ToJava.scala @@ -0,0 +1,15 @@ +package com.evolutiongaming.util + +import scala.jdk.CollectionConverters._ + +object ToJava { + + def from[T](collection: List[T]): java.util.Collection[T] = + collection.asJavaCollection + + def from[T](set: Set[T]): java.util.Set[T] = + set.asJava + + def from[A, B](map: Map[A, B]): java.util.Map[A, B] = + map.asJava +} \ No newline at end of file diff --git a/scassandra/src/main/scala-3/com/evolutiongaming/util/ToScala.scala b/scassandra/src/main/scala-3/com/evolutiongaming/util/ToScala.scala new file mode 100644 index 0000000..33e2dad --- /dev/null +++ b/scassandra/src/main/scala-3/com/evolutiongaming/util/ToScala.scala @@ -0,0 +1,13 @@ +package com.evolutiongaming.util + +import scala.collection.mutable +import scala.jdk.CollectionConverters._ + +object ToScala { + + def from[T](collection: java.util.Collection[T]): Iterable[T] = + collection.asScala + + def from[A, B](map: java.util.Map[A, B]): mutable.Map[A, B] = + map.asScala +} \ No newline at end of file diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/AuthenticationConfig.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/AuthenticationConfig.scala index a314304..7cd888d 100644 --- a/scassandra/src/main/scala/com/evolutiongaming/scassandra/AuthenticationConfig.scala +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/AuthenticationConfig.scala @@ -1,17 +1,14 @@ package com.evolutiongaming.scassandra import com.typesafe.config.Config -import pureconfig.{ConfigReader, ConfigSource} -import pureconfig.generic.semiauto.deriveReader +import pureconfig.ConfigSource /** * See [[https://docs.datastax.com/en/developer/java-driver/3.5/manual/auth/]] */ final case class AuthenticationConfig(username: String, password: Masked[String]) -object AuthenticationConfig { - - implicit val configReaderAuthenticationConfig: ConfigReader[AuthenticationConfig] = deriveReader +object AuthenticationConfig extends AuthenticationConfigImplicits { def apply(username: String, password: String): AuthenticationConfig = { AuthenticationConfig(username, Masked(password)) diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/AuthenticationConfigImplicits.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/AuthenticationConfigImplicits.scala new file mode 100644 index 0000000..3c112ef --- /dev/null +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/AuthenticationConfigImplicits.scala @@ -0,0 +1,18 @@ +package com.evolutiongaming.scassandra + +import com.evolutiongaming.scassandra.util.PureconfigSyntax._ +import pureconfig.ConfigReader + +trait AuthenticationConfigImplicits { + implicit val configReaderAuthenticationConfig: ConfigReader[AuthenticationConfig] = + ConfigReader.fromCursor[AuthenticationConfig] { cursor => + for { + objCur <- cursor.asObjectCursor + username <- objCur.getAt[String]("username") + password <- objCur.getAt[String]("password") + } yield AuthenticationConfig( + username = username, + password = password + ) + } +} diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/CassandraConfig.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/CassandraConfig.scala index ac6dc75..9421b3f 100644 --- a/scassandra/src/main/scala/com/evolutiongaming/scassandra/CassandraConfig.scala +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/CassandraConfig.scala @@ -80,7 +80,7 @@ object CassandraConfig { implicit val configReaderProtocolVersion: ConfigReader[ProtocolVersion] = ConfigReaderFromEnum(ProtocolVersion.values()) implicit val configReaderCassandraConfig: ConfigReader[CassandraConfig] = { - cursor: ConfigCursor => { + (cursor: ConfigCursor) => { for { cursor <- cursor.asObjectCursor } yield { diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/EncodeByIdx.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/EncodeByIdx.scala index f3b1a31..2abde4a 100644 --- a/scassandra/src/main/scala/com/evolutiongaming/scassandra/EncodeByIdx.scala +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/EncodeByIdx.scala @@ -112,7 +112,7 @@ object EncodeByIdx { } implicit val localDateJEncodeByIdx: EncodeByIdx[LocalDateJ] = { - EncodeByIdx[LocalDate].contramap { a: LocalDateJ => + EncodeByIdx[LocalDate].contramap { (a: LocalDateJ) => LocalDate.fromDaysSinceEpoch(a.toEpochDay.toInt) } } diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/EncodeByName.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/EncodeByName.scala index 5961bd9..ac8c429 100644 --- a/scassandra/src/main/scala/com/evolutiongaming/scassandra/EncodeByName.scala +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/EncodeByName.scala @@ -125,7 +125,7 @@ object EncodeByName { } implicit val localDateJEncodeByName: EncodeByName[LocalDateJ] = { - EncodeByName[LocalDate].contramap { a: LocalDateJ => + EncodeByName[LocalDate].contramap { (a: LocalDateJ) => LocalDate.fromDaysSinceEpoch(a.toEpochDay.toInt) } } diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/LoadBalancingConfig.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/LoadBalancingConfig.scala index bcfad65..ecb2141 100644 --- a/scassandra/src/main/scala/com/evolutiongaming/scassandra/LoadBalancingConfig.scala +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/LoadBalancingConfig.scala @@ -2,8 +2,7 @@ package com.evolutiongaming.scassandra import com.datastax.driver.core.policies.{DCAwareRoundRobinPolicy, LoadBalancingPolicy, TokenAwarePolicy} import com.typesafe.config.Config -import pureconfig.{ConfigReader, ConfigSource} -import pureconfig.generic.semiauto.deriveReader +import pureconfig.ConfigSource /** * See [[https://docs.datastax.com/en/developer/java-driver/3.5/manual/load_balancing/]] @@ -25,13 +24,10 @@ final case class LoadBalancingConfig( } } -object LoadBalancingConfig { +object LoadBalancingConfig extends LoadBalancingConfigImplicits { val Default: LoadBalancingConfig = LoadBalancingConfig() - implicit val configReaderLoadBalancingConfig: ConfigReader[LoadBalancingConfig] = deriveReader - - @deprecated("use ConfigReader instead", "1.1.5") def apply(config: Config): LoadBalancingConfig = apply(config, Default) diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/LoadBalancingConfigImplicits.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/LoadBalancingConfigImplicits.scala new file mode 100644 index 0000000..a081ab2 --- /dev/null +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/LoadBalancingConfigImplicits.scala @@ -0,0 +1,20 @@ +package com.evolutiongaming.scassandra + +import com.evolutiongaming.scassandra.util.PureconfigSyntax._ +import pureconfig.ConfigReader + +trait LoadBalancingConfigImplicits { + implicit val configReaderLoadBalancingConfig: ConfigReader[LoadBalancingConfig] = ConfigReader.fromCursor[LoadBalancingConfig] { cursor => + val defaultConfig = LoadBalancingConfig() + + for { + objCur <- cursor.asObjectCursor + localDc <- objCur.getAtOpt[String]("local-dc").map(_.getOrElse(defaultConfig.localDc)) + allowRemoteDcsForLocalConsistencyLevel <- objCur.getAtOpt[Boolean]("allow-remote-dcs-for-local-consistency-level").map(_.getOrElse(defaultConfig.allowRemoteDcsForLocalConsistencyLevel)) + } yield LoadBalancingConfig( + localDc = localDc, + allowRemoteDcsForLocalConsistencyLevel = allowRemoteDcsForLocalConsistencyLevel + ) + } + +} \ No newline at end of file diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/PoolingConfig.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/PoolingConfig.scala index 7505a04..9f299f4 100644 --- a/scassandra/src/main/scala/com/evolutiongaming/scassandra/PoolingConfig.scala +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/PoolingConfig.scala @@ -35,7 +35,7 @@ object PoolingConfig { val Default: PoolingConfig = PoolingConfig() implicit val configReaderPoolingConfig: ConfigReader[PoolingConfig] = { - cursor: ConfigCursor => { + (cursor: ConfigCursor) => { for { cursor <- cursor.asObjectCursor } yield { diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/QueryConfig.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/QueryConfig.scala index 73f6100..4507212 100644 --- a/scassandra/src/main/scala/com/evolutiongaming/scassandra/QueryConfig.scala +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/QueryConfig.scala @@ -1,10 +1,8 @@ package com.evolutiongaming.scassandra import com.datastax.driver.core.{ConsistencyLevel, QueryOptions} -import com.evolutiongaming.scassandra.util.ConfigReaderFromEnum import com.typesafe.config.Config -import pureconfig.generic.semiauto.deriveReader -import pureconfig.{ConfigReader, ConfigSource} +import pureconfig.ConfigSource import scala.concurrent.duration._ @@ -44,14 +42,10 @@ final case class QueryConfig( } } -object QueryConfig { +object QueryConfig extends QueryConfigImplicits { val Default: QueryConfig = QueryConfig() - implicit val configReaderConsistencyLevel: ConfigReader[ConsistencyLevel] = ConfigReaderFromEnum(ConsistencyLevel.values()) - - implicit val configReaderQueryConfig: ConfigReader[QueryConfig] = deriveReader - @deprecated("use ConfigReader instead", "1.1.5") def apply(config: Config): QueryConfig = apply(config, Default) diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/QueryConfigImplicits.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/QueryConfigImplicits.scala new file mode 100644 index 0000000..efa7107 --- /dev/null +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/QueryConfigImplicits.scala @@ -0,0 +1,49 @@ +package com.evolutiongaming.scassandra + +import com.datastax.driver.core.ConsistencyLevel +import com.evolutiongaming.scassandra.util.ConfigReaderFromEnum +import com.evolutiongaming.scassandra.util.PureconfigSyntax._ +import pureconfig.ConfigReader + +import scala.concurrent.duration._ + +trait QueryConfigImplicits { + implicit val configReaderConsistencyLevel: ConfigReader[ConsistencyLevel] = ConfigReaderFromEnum(ConsistencyLevel.values()) + + implicit val configReaderQueryConfig: ConfigReader[QueryConfig] = ConfigReader.fromCursor[QueryConfig] { cursor => + val defaultConfig = QueryConfig() + + for { + objCur <- cursor.asObjectCursor + consistency <- objCur.getAtOpt[ConsistencyLevel]("consistency").map(_.getOrElse(defaultConfig.consistency)) + serialConsistency <- objCur.getAtOpt[ConsistencyLevel]("serial-consistency").map(_.getOrElse(defaultConfig.serialConsistency)) + fetchSize <- objCur.getAtOpt[Int]("fetch-size").map(_.getOrElse(defaultConfig.fetchSize)) + defaultIdempotence <- objCur.getAtOpt[Boolean]("default-idempotence").map(_.getOrElse(defaultConfig.defaultIdempotence)) + maxPendingRefreshNodeListRequests <- objCur.getAtOpt[Int]("max-pending-refresh-node-list-requests").map(_.getOrElse(defaultConfig.maxPendingRefreshNodeListRequests)) + maxPendingRefreshNodeRequests <- objCur.getAtOpt[Int]("max-pending-refresh-node-requests").map(_.getOrElse(defaultConfig.maxPendingRefreshNodeRequests)) + maxPendingRefreshSchemaRequests <- objCur.getAtOpt[Int]("max-pending-refresh-schema-requests").map(_.getOrElse(defaultConfig.maxPendingRefreshSchemaRequests)) + refreshNodeListInterval <- objCur.getAtOpt[FiniteDuration]("refresh-node-list-interval").map(_.getOrElse(defaultConfig.refreshNodeListInterval)) + refreshNodeInterval <- objCur.getAtOpt[FiniteDuration]("refresh-node-interval").map(_.getOrElse(defaultConfig.refreshNodeInterval)) + refreshSchemaInterval <- objCur.getAtOpt[FiniteDuration]("refresh-schema-interval").map(_.getOrElse(defaultConfig.refreshSchemaInterval)) + metadata <- objCur.getAtOpt[Boolean]("metadata").map(_.getOrElse(defaultConfig.metadata)) + rePrepareOnUp <- objCur.getAtOpt[Boolean]("re-prepare-on-up").map(_.getOrElse(defaultConfig.rePrepareOnUp)) + prepareOnAllHosts <- objCur.getAtOpt[Boolean]("prepare-on-all-hosts").map(_.getOrElse(defaultConfig.prepareOnAllHosts)) + + } yield QueryConfig( + consistency = consistency, + serialConsistency = serialConsistency, + fetchSize = fetchSize, + defaultIdempotence = defaultIdempotence, + maxPendingRefreshNodeListRequests = maxPendingRefreshNodeListRequests, + maxPendingRefreshNodeRequests = maxPendingRefreshNodeRequests, + maxPendingRefreshSchemaRequests = maxPendingRefreshSchemaRequests, + refreshNodeListInterval = refreshNodeListInterval, + refreshNodeInterval = refreshNodeInterval, + refreshSchemaInterval = refreshSchemaInterval, + metadata = metadata, + rePrepareOnUp = rePrepareOnUp, + prepareOnAllHosts = prepareOnAllHosts + ) + } + +} \ No newline at end of file diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/ReconnectionConfig.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/ReconnectionConfig.scala index b9f512f..fce33a9 100644 --- a/scassandra/src/main/scala/com/evolutiongaming/scassandra/ReconnectionConfig.scala +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/ReconnectionConfig.scala @@ -2,8 +2,7 @@ package com.evolutiongaming.scassandra import com.datastax.driver.core.policies.{ExponentialReconnectionPolicy, ReconnectionPolicy} import com.typesafe.config.Config -import pureconfig.{ConfigReader, ConfigSource} -import pureconfig.generic.semiauto.deriveReader +import pureconfig.ConfigSource import scala.concurrent.duration._ @@ -19,12 +18,10 @@ final case class ReconnectionConfig( } } -object ReconnectionConfig { +object ReconnectionConfig extends ReconnectionConfigImplicits { val Default: ReconnectionConfig = ReconnectionConfig() - implicit val configReaderReconnectionConfig: ConfigReader[ReconnectionConfig] = deriveReader - @deprecated("use ConfigReader instead", "1.1.5") def apply(config: Config): ReconnectionConfig = apply(config, Default) diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/ReconnectionConfigImplicits.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/ReconnectionConfigImplicits.scala new file mode 100644 index 0000000..638ade7 --- /dev/null +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/ReconnectionConfigImplicits.scala @@ -0,0 +1,22 @@ +package com.evolutiongaming.scassandra + +import scala.concurrent.duration.FiniteDuration +import com.evolutiongaming.scassandra.util.PureconfigSyntax._ +import pureconfig.ConfigReader + +trait ReconnectionConfigImplicits { + implicit val configReaderReconnectionConfig: ConfigReader[ReconnectionConfig] = + ConfigReader.fromCursor[ReconnectionConfig] { cursor => + val defaultConfig = ReconnectionConfig() + + for { + objCur <- cursor.asObjectCursor + minDelay <- objCur.getAtOpt[FiniteDuration]("min-delay").map(_.getOrElse(defaultConfig.minDelay)) + maxDelay <- objCur.getAtOpt[FiniteDuration]("max-delay").map(_.getOrElse(defaultConfig.maxDelay)) + } yield ReconnectionConfig( + minDelay = minDelay, + maxDelay = maxDelay + ) + } + +} diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/ReplicationStrategyConfig.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/ReplicationStrategyConfig.scala index 50a376a..71b8b4f 100644 --- a/scassandra/src/main/scala/com/evolutiongaming/scassandra/ReplicationStrategyConfig.scala +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/ReplicationStrategyConfig.scala @@ -4,7 +4,6 @@ import com.evolutiongaming.config.ConfigHelper._ import com.evolutiongaming.nel.Nel import com.evolutiongaming.scassandra.ConfigHelpers._ import com.typesafe.config.{Config, ConfigException} -import pureconfig.generic.semiauto.deriveReader import pureconfig.{ConfigCursor, ConfigReader, ConfigSource} /** @@ -17,7 +16,7 @@ object ReplicationStrategyConfig { val Default: ReplicationStrategyConfig = Simple.Default implicit val configReaderReplicationStrategyConfig: ConfigReader[ReplicationStrategyConfig] = { - cursor: ConfigCursor => { + (cursor: ConfigCursor) => { for { cursor <- cursor.asObjectCursor } yield { @@ -57,12 +56,10 @@ object ReplicationStrategyConfig { final case class Simple(replicationFactor: Int = 1) extends ReplicationStrategyConfig - object Simple { + object Simple extends ReplicationStrategyConfigSimpleImplicits { val Default: Simple = Simple() - implicit val configReaderSimple: ConfigReader[Simple] = deriveReader - @deprecated("use ConfigReader instead", "1.1.5") def apply(config: Config): Simple = apply(config, Default) @@ -84,7 +81,7 @@ object ReplicationStrategyConfig { val Default: NetworkTopology = NetworkTopology() implicit val configReaderNetworkTopology: ConfigReader[NetworkTopology] = { - cursor: ConfigCursor => { + (cursor: ConfigCursor) => { for { cursor <- cursor.asObjectCursor } yield { diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/ReplicationStrategyConfigSimpleImplicits.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/ReplicationStrategyConfigSimpleImplicits.scala new file mode 100644 index 0000000..3085316 --- /dev/null +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/ReplicationStrategyConfigSimpleImplicits.scala @@ -0,0 +1,18 @@ +package com.evolutiongaming.scassandra + +import com.evolutiongaming.scassandra.ReplicationStrategyConfig._ +import com.evolutiongaming.scassandra.util.PureconfigSyntax._ +import pureconfig.ConfigReader + +trait ReplicationStrategyConfigSimpleImplicits { + implicit val configReaderSimple: ConfigReader[Simple] = ConfigReader.fromCursor[Simple] { cursor => + val defaultConfig = Simple() + + for { + objCur <- cursor.asObjectCursor + replicationFactor <- objCur.getAtOpt[Int]("replication-factor").map(_.getOrElse(defaultConfig.replicationFactor)) + } yield Simple( + replicationFactor = replicationFactor + ) + } +} diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/SocketConfig.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/SocketConfig.scala index 28787ba..bf9af16 100644 --- a/scassandra/src/main/scala/com/evolutiongaming/scassandra/SocketConfig.scala +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/SocketConfig.scala @@ -2,8 +2,7 @@ package com.evolutiongaming.scassandra import com.datastax.driver.core.SocketOptions import com.typesafe.config.Config -import pureconfig.{ConfigReader, ConfigSource} -import pureconfig.generic.semiauto.deriveReader +import pureconfig.ConfigSource import scala.concurrent.duration._ @@ -36,13 +35,10 @@ final case class SocketConfig( } } -object SocketConfig { +object SocketConfig extends SocketConfigImplicits { val Default: SocketConfig = SocketConfig() - implicit val configReaderSocketConfig: ConfigReader[SocketConfig] = deriveReader - - @deprecated("use ConfigReader instead", "1.1.5") def apply(config: Config): SocketConfig = fromConfig(config, Default) diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/SocketConfigImplicits.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/SocketConfigImplicits.scala new file mode 100644 index 0000000..6d8602a --- /dev/null +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/SocketConfigImplicits.scala @@ -0,0 +1,32 @@ +package com.evolutiongaming.scassandra + +import scala.concurrent.duration.FiniteDuration +import com.evolutiongaming.scassandra.util.PureconfigSyntax._ +import pureconfig.ConfigReader + +trait SocketConfigImplicits { + implicit val configReaderSocketConfig: ConfigReader[SocketConfig] = ConfigReader.fromCursor[SocketConfig] { cursor => + val defaultConfig = SocketConfig() + + for { + objCur <- cursor.asObjectCursor + connectTimeout <- objCur.getAtOpt[FiniteDuration]("connect-timeout").map(_.getOrElse(defaultConfig.connectTimeout)) + readTimeout <- objCur.getAtOpt[FiniteDuration]("read-timeout").map(_.getOrElse(defaultConfig.readTimeout)) + keepAlive <- objCur.getAtOpt[Boolean]("keep-alive").map(_.orElse(defaultConfig.keepAlive)) + reuseAddress <- objCur.getAtOpt[Boolean]("reuse-address").map(_.orElse(defaultConfig.reuseAddress)) + soLinger <- objCur.getAtOpt[Int]("so-linger").map(_.orElse(defaultConfig.soLinger)) + tcpNoDelay <- objCur.getAtOpt[Boolean]("tcp-no-delay").map(_.orElse(defaultConfig.tcpNoDelay)) + receiveBufferSize <- objCur.getAtOpt[Int]("receive-buffer-size").map(_.orElse(defaultConfig.receiveBufferSize)) + sendBufferSize <- objCur.getAtOpt[Int]("send-buffer-size").map(_.orElse(defaultConfig.sendBufferSize)) + } yield SocketConfig( + connectTimeout = connectTimeout, + readTimeout = readTimeout, + keepAlive = keepAlive, + reuseAddress = reuseAddress, + soLinger = soLinger, + tcpNoDelay = tcpNoDelay, + receiveBufferSize = receiveBufferSize, + sendBufferSize = sendBufferSize + ) + } +} \ No newline at end of file diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/SpeculativeConfigImplicits.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/SpeculativeConfigImplicits.scala new file mode 100644 index 0000000..aa043e3 --- /dev/null +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/SpeculativeConfigImplicits.scala @@ -0,0 +1,20 @@ +package com.evolutiongaming.scassandra + +import scala.concurrent.duration.FiniteDuration +import com.evolutiongaming.scassandra.util.PureconfigSyntax._ +import pureconfig.ConfigReader + +trait SpeculativeConfigImplicits { + implicit val configReaderSpeculativeExecutionConfig: ConfigReader[SpeculativeExecutionConfig] = ConfigReader.fromCursor[SpeculativeExecutionConfig] { cursor => + val defaultConfig = SpeculativeExecutionConfig() + + for { + objCur <- cursor.asObjectCursor + delay <- objCur.getAtOpt[FiniteDuration]("delay").map(_.getOrElse(defaultConfig.delay)) + maxExecutions <- objCur.getAtOpt[Int]("max-executions").map(_.getOrElse(defaultConfig.maxExecutions)) + } yield SpeculativeExecutionConfig( + delay = delay, + maxExecutions = maxExecutions + ) + } +} diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/SpeculativeExecutionConfig.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/SpeculativeExecutionConfig.scala index a3074dc..423ddc9 100644 --- a/scassandra/src/main/scala/com/evolutiongaming/scassandra/SpeculativeExecutionConfig.scala +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/SpeculativeExecutionConfig.scala @@ -2,8 +2,7 @@ package com.evolutiongaming.scassandra import com.datastax.driver.core.policies.{ConstantSpeculativeExecutionPolicy, SpeculativeExecutionPolicy} import com.typesafe.config.Config -import pureconfig.generic.semiauto.deriveReader -import pureconfig.{ConfigReader, ConfigSource} +import pureconfig.ConfigSource import scala.concurrent.duration._ @@ -19,13 +18,10 @@ final case class SpeculativeExecutionConfig( } } -object SpeculativeExecutionConfig { +object SpeculativeExecutionConfig extends SpeculativeConfigImplicits { val Default: SpeculativeExecutionConfig = SpeculativeExecutionConfig() - implicit val configReaderSpeculativeExecutionConfig: ConfigReader[SpeculativeExecutionConfig] = deriveReader - - @deprecated("use ConfigReader instead", "1.1.5") def apply(config: Config): SpeculativeExecutionConfig = fromConfig(config, Default) @@ -34,7 +30,6 @@ object SpeculativeExecutionConfig { fromConfig(config, default) } - def fromConfig(config: Config, default: => SpeculativeExecutionConfig): SpeculativeExecutionConfig = { ConfigSource.fromConfig(config).load[SpeculativeExecutionConfig] getOrElse default } diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/syntax.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/syntax.scala index e4d2b8e..965b891 100644 --- a/scassandra/src/main/scala/com/evolutiongaming/scassandra/syntax.scala +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/syntax.scala @@ -8,8 +8,6 @@ import com.evolutiongaming.scassandra.util.FromGFuture import com.evolutiongaming.sstream.FoldWhile._ import com.evolutiongaming.sstream.Stream -import scala.language.implicitConversions - object syntax { implicit class ResultSetOps(val self: ResultSet) extends AnyVal { @@ -100,7 +98,7 @@ object syntax { } - implicit def toCqlOps[A](a: A) = new ToCql.Ops.IdOps(a) + implicit def toCqlOps[A](a: A): ToCql.Ops.IdOps[A] = new ToCql.Ops.IdOps(a) implicit class ScassandraStatementOps(val self: Statement) extends AnyVal { diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/util/ConfigReaderFromEnum.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/util/ConfigReaderFromEnum.scala index 005fdbb..69670ce 100644 --- a/scassandra/src/main/scala/com/evolutiongaming/scassandra/util/ConfigReaderFromEnum.scala +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/util/ConfigReaderFromEnum.scala @@ -30,7 +30,7 @@ object ConfigReaderFromEnum { * @param tag [[ClassTag]] needed to report a class name on failure. */ def apply[A <: Enum[A]](values: Array[A])(implicit tag: ClassTag[A]): ConfigReader[A] = { - cursor: ConfigCursor => { + (cursor: ConfigCursor) => { def fromString(str: String) = { values diff --git a/scassandra/src/main/scala/com/evolutiongaming/scassandra/util/PureconfigUtils.scala b/scassandra/src/main/scala/com/evolutiongaming/scassandra/util/PureconfigUtils.scala new file mode 100644 index 0000000..c8c4a5a --- /dev/null +++ b/scassandra/src/main/scala/com/evolutiongaming/scassandra/util/PureconfigUtils.scala @@ -0,0 +1,14 @@ +package com.evolutiongaming.scassandra.util + +import pureconfig.error.ConfigReaderFailures +import pureconfig._ + +private[scassandra] object PureconfigSyntax { + implicit final class ConfigObjectCursorSyntax(val objCur: ConfigObjectCursor) extends AnyVal { + def getAt[A: ConfigReader](key: String): Either[ConfigReaderFailures, A] = + objCur.atKey(key).flatMap(ConfigReader[A].from(_)) + + def getAtOpt[A: ConfigReader](key: String): Either[ConfigReaderFailures, Option[A]] = + ConfigReader[Option[A]].from(objCur.atKeyOrUndefined(key)) + } +} \ No newline at end of file diff --git a/scassandra/src/test/scala/com/evolutiongaming/scassandra/DataMock.scala b/scassandra/src/test/scala/com/evolutiongaming/scassandra/DataMock.scala index 8528638..1c8db2e 100644 --- a/scassandra/src/test/scala/com/evolutiongaming/scassandra/DataMock.scala +++ b/scassandra/src/test/scala/com/evolutiongaming/scassandra/DataMock.scala @@ -32,11 +32,11 @@ case class DataMock( def setUUID(i: Int, v: UUID) = copy(byIdx = byIdx.updated(i, v)) def setInet(i: Int, v: InetAddress) = copy(byIdx = byIdx.updated(i, v)) def setList[E](i: Int, v: ListJ[E]) = copy(byIdx = byIdx.updated(i, v)) - def setList[E](i: Int, v: ListJ[E], elementsClass: Class[E]) = notSupported() - def setList[E](i: Int, v: ListJ[E], elementsType: TypeToken[E]) = notSupported() + def setList[E](i: Int, v: ListJ[E], elementsClass: Class[E]): DataMock = notSupported() + def setList[E](i: Int, v: ListJ[E], elementsType: TypeToken[E]): DataMock = notSupported() def setMap[K, V](i: Int, v: MapJ[K, V]) = copy(byIdx = byIdx.updated(i, v)) - def setMap[K, V](i: Int, v: MapJ[K, V], keysClass: Class[K], valuesClass: Class[V]) = notSupported() - def setMap[K, V](i: Int, v: MapJ[K, V], keysType: TypeToken[K], valuesType: TypeToken[V]) = notSupported() + def setMap[K, V](i: Int, v: MapJ[K, V], keysClass: Class[K], valuesClass: Class[V]): DataMock = notSupported() + def setMap[K, V](i: Int, v: MapJ[K, V], keysType: TypeToken[K], valuesType: TypeToken[V]): DataMock = notSupported() def setSet[E](i: Int, v: SetJ[E]) = copy(byIdx = byIdx.updated(i, v)) def setSet[E](i: Int, v: SetJ[E], elementsClass: Class[E]) = copy(byIdx = byIdx.updated(i, v)) def setSet[E](i: Int, v: SetJ[E], elementsType: TypeToken[E]) = copy(byIdx = byIdx.updated(i, v)) @@ -98,18 +98,18 @@ case class DataMock( def getDecimal(name: String) = byName.getOrElse(name, null).asInstanceOf[BigDecimalJ] def getUUID(name: String) = byName.getOrElse(name, null).asInstanceOf[UUID] def getInet(name: String) = byName.getOrElse(name, null).asInstanceOf[InetAddress] - def getList[T](name: String, elementsClass: Class[T]) = notSupported() - def getList[T](name: String, elementsType: TypeToken[T]) = notSupported() + def getList[T](name: String, elementsClass: Class[T]): ListJ[T] = notSupported() + def getList[T](name: String, elementsType: TypeToken[T]): ListJ[T] = notSupported() def getSet[T](name: String, elementsClass: Class[T]) = byName.getOrElse(name, null).asInstanceOf[SetJ[T]] - def getSet[T](name: String, elementsType: TypeToken[T]) = notSupported() - def getMap[K, V](name: String, keysClass: Class[K], valuesClass: Class[V]) = notSupported() - def getMap[K, V](name: String, keysType: TypeToken[K], valuesType: TypeToken[V]) = notSupported() - def getUDTValue(name: String) = notSupported() - def getTupleValue(name: String) = notSupported() - def getObject(name: String) = notSupported() - def get[T](name: String, targetClass: Class[T]) = notSupported() - def get[T](name: String, targetType: TypeToken[T]) = notSupported() - def get[T](name: String, codec: TypeCodec[T]) = byName.getOrElse(name, null).asInstanceOf[T] + def getSet[T](name: String, elementsType: TypeToken[T]): SetJ[T] = notSupported() + def getMap[K, V](name: String, keysClass: Class[K], valuesClass: Class[V]): MapJ[K, V] = notSupported() + def getMap[K, V](name: String, keysType: TypeToken[K], valuesType: TypeToken[V]): MapJ[K, V] = notSupported() + def getUDTValue(name: String): UDTValue = notSupported() + def getTupleValue(name: String): TupleValue = notSupported() + def getObject(name: String): Object = notSupported() + def get[T](name: String, targetClass: Class[T]): T = notSupported() + def get[T](name: String, targetType: TypeToken[T]): T = notSupported() + def get[T](name: String, codec: TypeCodec[T]): T = byName.getOrElse(name, null).asInstanceOf[T] def isNull(i: Int) = !byIdx.contains(i) def getBool(i: Int) = byIdx.getOrElse(i, null).asInstanceOf[Boolean] @@ -129,16 +129,16 @@ case class DataMock( def getDecimal(i: Int) = byIdx.getOrElse(i, null).asInstanceOf[BigDecimalJ] def getUUID(i: Int) = byIdx.getOrElse(i, null).asInstanceOf[UUID] def getInet(i: Int) = byIdx.getOrElse(i, null).asInstanceOf[InetAddress] - def getList[T](i: Int, elementsClass: Class[T]) = notSupported() - def getList[T](i: Int, elementsType: TypeToken[T]) = notSupported() + def getList[T](i: Int, elementsClass: Class[T]): ListJ[T] = notSupported() + def getList[T](i: Int, elementsType: TypeToken[T]): ListJ[T] = notSupported() def getSet[T](i: Int, elementsClass: Class[T]) = byIdx.getOrElse(i, null).asInstanceOf[SetJ[T]] def getSet[T](i: Int, elementsType: TypeToken[T]) = byIdx.getOrElse(i, null).asInstanceOf[SetJ[T]] - def getMap[K, V](i: Int, keysClass: Class[K], valuesClass: Class[V]) = notSupported() - def getMap[K, V](i: Int, keysType: TypeToken[K], valuesType: TypeToken[V]) = notSupported() - def getUDTValue(i: Int) = notSupported() - def getTupleValue(i: Int) = notSupported() - def getObject(i: Int) = notSupported() - def get[T](i: Int, targetClass: Class[T]) = notSupported() - def get[T](i: Int, targetType: TypeToken[T]) = notSupported() - def get[T](i: Int, codec: TypeCodec[T]) = byIdx.getOrElse(i, null).asInstanceOf[T] + def getMap[K, V](i: Int, keysClass: Class[K], valuesClass: Class[V]): MapJ[K, V] = notSupported() + def getMap[K, V](i: Int, keysType: TypeToken[K], valuesType: TypeToken[V]): MapJ[K, V] = notSupported() + def getUDTValue(i: Int): UDTValue = notSupported() + def getTupleValue(i: Int): TupleValue = notSupported() + def getObject(i: Int): Object = notSupported() + def get[T](i: Int, targetClass: Class[T]): T = notSupported() + def get[T](i: Int, targetType: TypeToken[T]): T = notSupported() + def get[T](i: Int, codec: TypeCodec[T]): T = byIdx.getOrElse(i, null).asInstanceOf[T] } diff --git a/scassandra/src/test/scala/com/evolutiongaming/scassandra/ReplicationStrategyConfigSpec.scala b/scassandra/src/test/scala/com/evolutiongaming/scassandra/ReplicationStrategyConfigSpec.scala index cb4377b..214d762 100644 --- a/scassandra/src/test/scala/com/evolutiongaming/scassandra/ReplicationStrategyConfigSpec.scala +++ b/scassandra/src/test/scala/com/evolutiongaming/scassandra/ReplicationStrategyConfigSpec.scala @@ -24,6 +24,12 @@ class ReplicationStrategyConfigSpec extends AnyFunSuite with Matchers { ConfigSource.fromConfig(config.getConfig("simple")).load[ReplicationStrategyConfig] shouldEqual expected.asRight } + test("apply from empty simple config") { + val config = ConfigFactory.empty() + val expected = Simple() + ConfigSource.fromConfig(config).load[Simple] shouldEqual expected.asRight + } + test("apply from network topology config") { val config = ConfigFactory.parseURL(getClass.getResource("replication-strategy.conf")) val expected = NetworkTopology(Nel(DcFactor("dc1", 2), DcFactor("dc2", 3))) diff --git a/tests/src/test/scala/com/evolutiongaming/scassandra/CassandraSpec.scala b/tests/src/test/scala/com/evolutiongaming/scassandra/CassandraSpec.scala index bb2512d..4bae091 100644 --- a/tests/src/test/scala/com/evolutiongaming/scassandra/CassandraSpec.scala +++ b/tests/src/test/scala/com/evolutiongaming/scassandra/CassandraSpec.scala @@ -5,13 +5,15 @@ import cats.effect.unsafe.implicits import cats.effect.{IO, Resource} import cats.implicits._ import com.datastax.driver.core.{Duration, Row} -import com.evolutiongaming.cassandra.StartCassandra +import com.dimafeng.testcontainers.CassandraContainer +import org.testcontainers.utility.DockerImageName import com.evolutiongaming.catshelper.CatsHelper._ import com.evolutiongaming.catshelper.ToTry import com.evolutiongaming.scassandra.IOSuite._ import com.evolutiongaming.scassandra.syntax._ import org.scalatest.BeforeAndAfterAll import com.evolutiongaming.sstream.Stream._ +import com.evolutiongaming.nel.Nel import scala.util.Try import org.scalatest.matchers.should.Matchers @@ -19,10 +21,18 @@ import org.scalatest.wordspec.AnyWordSpec class CassandraSpec extends AnyWordSpec with BeforeAndAfterAll with Matchers { + private lazy val cassandraContainer = CassandraContainer( + dockerImageNameOverride = DockerImageName.parse("cassandra:3.11.7"), + ) - private val config = CassandraConfig.Default + private lazy val config = + CassandraConfig.Default.copy( + contactPoints = Nel(cassandraContainer.containerIpAddress), + port = cassandraContainer.mappedPort(9042), + ) - private lazy val shutdownCassandra = StartCassandra() + // due to test structure we need to start the container before the test suite + cassandraContainer.start() implicit val toTry: ToTry[IO] = ToTry.ioToTry(implicits.global) @@ -40,14 +50,8 @@ class CassandraSpec extends AnyWordSpec with BeforeAndAfterAll with Matchers { private lazy val (session, sessionRelease) = cluster.connect.allocated.toTry.get - override def beforeAll() = { - super.beforeAll() - shutdownCassandra - () - } - override def afterAll() = { - shutdownCassandra() + cassandraContainer.stop() super.afterAll() } diff --git a/version.sbt b/version.sbt index 5fae3e3..e302093 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -ThisBuild / version := "5.0.2-SNAPSHOT" +ThisBuild / version := "5.1.0-SNAPSHOT"