Skip to content

Commit

Permalink
Merge pull request #875 from yisraelU/URI-Interpolator
Browse files Browse the repository at this point in the history
add scala3 and scala 2 redis uri interpolators
  • Loading branch information
yisraelU committed May 24, 2024
2 parents b760f1a + e696044 commit d32e8ad
Show file tree
Hide file tree
Showing 10 changed files with 196 additions and 44 deletions.
6 changes: 6 additions & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,9 @@ rewriteTokens {
"→" = "->"
"←" = "<-"
}

project {
excludeFilters = [
"/scala-3/"
]
}
5 changes: 5 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ lazy val `redis4cats-root` = project
lazy val `redis4cats-core` = project
.in(file("modules/core"))
.settings(commonSettings: _*)
.settings(libraryDependencies += Libraries.literally)
.settings(
libraryDependencies ++=
pred(scalaVersion.value.startsWith("3"), t = Seq.empty, f = Seq(Libraries.reflect(scalaVersion.value)))
)
.settings(isMimaEnabled := true)
.settings(Test / parallelExecution := false)
.enablePlugins(AutomateHeaderPlugin)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2018-2021 ProfunKtor
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dev.profunktor.redis4cats.syntax

import dev.profunktor.redis4cats.connection.RedisURI

class RedisURIOps(val sc: StringContext) extends AnyVal {
def redis(args: Any*): RedisURI = macro macros.RedisLiteral.make
}

trait RedisSyntax {
implicit def toRedisURIOps(sc: StringContext): RedisURIOps =
new RedisURIOps(sc)
}

object literals extends RedisSyntax


Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2018-2021 ProfunKtor
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dev.profunktor.redis4cats.syntax

import dev.profunktor.redis4cats.connection.RedisURI
import org.typelevel.literally.Literally

object macros {

object RedisLiteral extends Literally[RedisURI] {

override def validate(c: Context)(s: String): Either[String, c.Expr[RedisURI]] = {
import c.universe._
RedisURI.fromString(s) match {
case Left(e) => Left(e.getMessage)
case Right(_) => Right(c.Expr(q"dev.profunktor.redis4cats.connection.RedisURI.unsafeFromString($s)"))
}
}

def make(c: Context)(args: c.Expr[Any]*): c.Expr[RedisURI] = apply(c)(args: _*)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2018-2021 ProfunKtor
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package dev.profunktor.redis4cats.syntax

import dev.profunktor.redis4cats.connection.RedisURI
import org.typelevel.literally.Literally
import scala.language.`3.0`

object literals {
extension (inline ctx: StringContext){
inline def redis(inline args: Any*):RedisURI = ${RedisLiteral('ctx, 'args)}
}

object RedisLiteral extends Literally[RedisURI]{
def validate(s: String)(using Quotes) =
RedisURI.fromString(s) match {
case Left(e) => Left(e.getMessage)
case Right(_) => Right('{RedisURI.unsafeFromString(${ Expr(s) })})
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,26 @@
package dev.profunktor.redis4cats.connection

import cats.ApplicativeThrow
import cats.implicits.toBifunctorOps
import io.lettuce.core.{ RedisURI => JRedisURI }

sealed abstract case class RedisURI private (underlying: JRedisURI)
import scala.util.Try
import scala.util.control.NoStackTrace

sealed abstract class RedisURI private (val underlying: JRedisURI)

object RedisURI {
def make[F[_]: ApplicativeThrow](uri: => String): F[RedisURI] =
ApplicativeThrow[F].catchNonFatal(new RedisURI(JRedisURI.create(uri)) {})

def fromUnderlying(j: JRedisURI): RedisURI = new RedisURI(j) {}

def fromString(uri: String): Either[InvalidRedisURI, RedisURI] =
Try(JRedisURI.create(uri)).toEither.bimap(InvalidRedisURI(uri, _), new RedisURI(_) {})

def unsafeFromString(uri: String): RedisURI = new RedisURI(JRedisURI.create(uri)) {}
}

final case class InvalidRedisURI(uri: String, throwable: Throwable) extends NoStackTrace {
override def getMessage: String = Option(throwable.getMessage).getOrElse(s"Invalid Redis URI: $uri")
}
4 changes: 4 additions & 0 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ object Dependencies {

val redisClient = "io.lettuce" % "lettuce-core" % V.lettuce

val literally = "org.typelevel" %% "literally" % "1.2.0"

def reflect(version: String): ModuleID = "org.scala-lang" % "scala-reflect" % version

// Examples libraries
val catsEffect = "org.typelevel" %% "cats-effect" % V.catsEffect
val circeCore = "io.circe" %% "circe-core" % V.circe
Expand Down
84 changes: 41 additions & 43 deletions project/MimaVersionPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ object MimaVersionPlugin extends AutoPlugin {
override def trigger = allRequirements

object autoImport {
val ReleaseTag = """^v((?:\d+\.){2}\d+(?:-.*)?)$""".r
val ReleaseTag = """^v((?:\d+\.){2}\d+(?:-.*)?)$""".r
lazy val mimaBaseVersion = git.baseVersion
lazy val mimaReportBinaryIssuesIfRelevant = taskKey[Unit](
"A wrapper around the mima task which ensures publishArtifact is set to true"
Expand All @@ -45,44 +45,45 @@ object MimaVersionPlugin extends AutoPlugin {

override def buildSettings: Seq[Setting[_]] =
GitPlugin.autoImport.versionWithGit ++ Seq(
git.gitTagToVersionNumber := {
case ReleaseTag(version) => Some(version)
case _ => None
},
git.formattedShaVersion := {
val suffix = git.makeUncommittedSignifierSuffix(
git.gitUncommittedChanges.value,
git.uncommittedSignifier.value
git.gitTagToVersionNumber := {
case ReleaseTag(version) => Some(version)
case _ => None
},
git.formattedShaVersion := {
val suffix = git.makeUncommittedSignifierSuffix(
git.gitUncommittedChanges.value,
git.uncommittedSignifier.value
)

val description = Try("git describe --tags --match v*".!!.trim).toOption
val optDistance = description collect {
case Description(distance) =>
distance + "-"
}

val distance = optDistance.getOrElse("")

git.gitHeadCommit.value map { _.substring(0, 7) } map { sha =>
autoImport.mimaBaseVersion.value + "-" + distance + sha + suffix
}
},
git.gitUncommittedChanges := Try("git status -s".!!.trim.length > 0)
.getOrElse(true),
git.gitHeadCommit := Try("git rev-parse HEAD".!!.trim).toOption,
git.gitCurrentTags := Try(
"git tag --contains HEAD".!!.trim.split("\\s+").toList.filter(_ != "")
).toOption.toList.flatten
)

val description = Try("git describe --tags --match v*".!!.trim).toOption
val optDistance = description collect { case Description(distance) =>
distance + "-"
}

val distance = optDistance.getOrElse("")

git.gitHeadCommit.value map { _.substring(0, 7) } map { sha =>
autoImport.mimaBaseVersion.value + "-" + distance + sha + suffix
}
},
git.gitUncommittedChanges := Try("git status -s".!!.trim.length > 0)
.getOrElse(true),
git.gitHeadCommit := Try("git rev-parse HEAD".!!.trim).toOption,
git.gitCurrentTags := Try(
"git tag --contains HEAD".!!.trim.split("\\s+").toList.filter(_ != "")
).toOption.toList.flatten
)

override def projectSettings: Seq[Setting[_]] = Seq(
isMimaEnabled := false,
mimaReportBinaryIssuesIfRelevant := filterTaskWhereRelevant(
mimaReportBinaryIssues
).value,
mimaPreviousArtifacts := {
mimaReportBinaryIssues
).value,
mimaPreviousArtifacts := {
val current = version.value
val org = organization.value
val n = moduleName.value
val org = organization.value
val n = moduleName.value

val FullTag = """^(\d+)\.(\d+)\.(\d+).*""" r
val TagBase = """^(\d+)\.(\d+).*""" r
Expand All @@ -100,18 +101,17 @@ object MimaVersionPlugin extends AutoPlugin {
val tags = scala.util
.Try("git tag --list".!!.split("\n").map(_.trim))
.getOrElse(new Array[String](0))
println(tags.mkString("\n"))

// in semver, we allow breakage in minor releases if major is 0, otherwise not
val Pattern =
if (isPre)
s"^v($major\\.$minor\\.\\d+)$$".r
else
s"^v($major\\.\\d+\\.\\d+)$$".r

val versions = tags collect { case Pattern(version) =>
version
}
val versions = tags collect {
case Pattern(version) =>
version
}

def lessThanPatch(patch: String): String => Boolean = { tagVersion =>
val FullTag(_, _, tagPatch) = tagVersion
Expand All @@ -123,17 +123,15 @@ object MimaVersionPlugin extends AutoPlugin {
.filterNot {
val patchPredicate =
maybePatch
// if mimaBaseVersion has a patch version, exclude this version if the patch is smaller
// if mimaBaseVersion has a patch version, exclude this version if the patch is smaller
.map(lessThanPatch(_))
// else keep the version
.getOrElse { (_: String) => false }
.getOrElse((_: String) => false)
v => patchPredicate(v)
}

notCurrent
.map(v =>
projectID.value.withRevision(v).withExplicitArtifacts(Vector.empty)
)
.map(v => projectID.value.withRevision(v).withExplicitArtifacts(Vector.empty))
.toSet
}
}
Expand Down
22 changes: 22 additions & 0 deletions site/docs/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,28 @@ val configuredApi: Resource[IO, StringCommands[IO, String, String]] =
} yield redis
```

A RedisURI can also be created using the `redis` string interpolator:

```scala mdoc:silent
import dev.profunktor.redis4cats.syntax.literals._
val uri = redis"redis://localhost"

val secure = redis"rediss://localhost"

val withPassword = redis"redis://:password@localhost"

val withDatabase = redis"redis://localhost/1"

val sentinel = redis"redis-sentinel://localhost:26379,localhost:26380?sentinelMasterId=m"

val `redis+ssl` = redis"redis+ssl://localhost"

val `redis+tls` = redis"redis+tls://localhost"

val `redis-socket` = redis"redis-socket:///tmp/redis.sock"

```

## Single node connection

For those who only need a simple API access to Redis commands, there are a few ways to acquire a connection:
Expand Down

0 comments on commit d32e8ad

Please sign in to comment.