Skip to content

Commit d2b35a4

Browse files
authored
Merge pull request #114 from laserdisc-io/upgrades
scala 3 support
2 parents 04e64dc + fe33cf0 commit d2b35a4

File tree

21 files changed

+169
-110
lines changed

21 files changed

+169
-110
lines changed

.github/workflows/ci.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ jobs:
1212
strategy:
1313
matrix:
1414
scala:
15-
- 2.13.10
15+
- 2.13.11
16+
- 3.3.0
1617
java:
1718
- corretto@1.11
1819
- corretto@1.17

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,11 @@ With your [signing secret](https://api.slack.com/authentication/verifying-reques
3535

3636
```scala
3737
import cats.effect.{IO, IOApp}
38-
import eu.timepit.refined.auto._
39-
import io.laserdisc.slack4s.slashcmd._
38+
import io.laserdisc.slack4s.slashcmd.*
4039

4140
object MySlackBot extends IOApp.Simple {
4241

43-
val secret: SigningSecret = "your-signing-secret" // demo purposes - please don't hardcode secrets
42+
val secret: SigningSecret = SigningSecret.unsafeFrom("your-signing-secret") // demo purposes - please don't hardcode secrets
4443

4544
override def run: IO[Unit] = SlashCommandBotBuilder[IO](secret).serve
4645

build.sbt

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
ThisBuild / scalaVersion := "2.13.11"
1+
lazy val scala213 = "2.13.11"
2+
lazy val scala3 = "3.3.0"
3+
lazy val supportedScalaVersions = List(scala213, scala3)
4+
ThisBuild / crossScalaVersions := supportedScalaVersions
5+
ThisBuild / scalaVersion := scala213
26

37
lazy val publishSettings = Seq(
48
Test / publishArtifact := false,
@@ -11,7 +15,6 @@ lazy val publishSettings = Seq(
1115
scmInfo := Some(
1216
ScmInfo(
1317
url("https://github.com/laserdisc-io/slack4s/tree/master"),
14-
"scm:git:git@github.com:laserdisc-io/slack4s.git",
1518
"scm:git:git@github.com:laserdisc-io/slack4s.git"
1619
)
1720
),
@@ -26,26 +29,57 @@ lazy val root = project
2629
name := "slack4s",
2730
publishSettings,
2831
Seq(
29-
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1"),
32+
libraryDependencies ++= Seq(
33+
compilerPlugin(("org.typelevel" %% "kind-projector" % "0.13.2").cross(CrossVersion.full)),
34+
compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")
35+
).filterNot(_ => scalaVersion.value.startsWith("3.")),
3036
scalacOptions ++= Seq(
37+
"-deprecation",
3138
"-encoding",
3239
"UTF-8",
33-
"-deprecation",
34-
"-unchecked",
3540
"-feature",
36-
"-language:higherKinds",
37-
"-language:implicitConversions",
38-
"-language:postfixOps",
39-
"-Xlint:_,-byname-implicit", // see https://github.com/scala/bug/issues/12072
41+
"-language:existentials,experimental.macros,higherKinds,implicitConversions,postfixOps",
42+
"-unchecked",
4043
"-Xfatal-warnings"
41-
)
44+
),
45+
scalacOptions ++= {
46+
CrossVersion.partialVersion(scalaVersion.value) match {
47+
case Some((2, minor)) if minor >= 13 =>
48+
Seq(
49+
"-Xlint:-unused,_",
50+
"-Ywarn-numeric-widen",
51+
"-Ywarn-value-discard",
52+
"-Ywarn-unused:implicits",
53+
"-Ywarn-unused:imports",
54+
"-Xsource:3",
55+
"-Xlint:-byname-implicit",
56+
"-P:kind-projector:underscore-placeholders",
57+
"-Xlint",
58+
"-Ywarn-macros:after"
59+
)
60+
case _ => Seq.empty
61+
}
62+
},
63+
scalacOptions ++= {
64+
CrossVersion.partialVersion(scalaVersion.value) match {
65+
case Some((3, _)) =>
66+
Seq(
67+
"-Ykind-projector:underscores",
68+
"-source:future",
69+
"-language:adhocExtensions",
70+
"-Wconf:msg=`= _` has been deprecated; use `= uninitialized` instead.:s"
71+
)
72+
case _ => Seq.empty
73+
}
74+
}
4275
),
4376
Test / fork := true,
4477
// ------------------------- deps -------------------------
4578
excludeDependencies += "commons-logging",
4679
Dependencies.TestLib,
4780
Dependencies.Circe,
4881
Dependencies.Refined,
82+
Dependencies.NewTypes,
4983
Dependencies.Logging,
5084
Dependencies.Http4s,
5185
Dependencies.Slack,

docs/tutorial.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,12 @@ Following the instructions on the [main README](../README.md), let's create a sl
104104
105105
```scala
106106
import cats.effect.{IO, IOApp}
107-
import eu.timepit.refined.auto._
108-
import io.laserdisc.slack4s.slashcmd._
107+
import io.laserdisc.slack4s.slashcmd.*
109108
110109
object MySlackBot extends IOApp.Simple {
111110
112111
// please don't hardcode secrets, this is just a demo
113-
val secret: SigningSecret = "7e16-----redacted------68c2c"
112+
val secret: SigningSecret = SigningSecret.unsfeFrom("7e16-----redacted------68c2c")
114113
115114
override def run: IO[Unit] = SlashCommandBotBuilder[IO](secret).serve
116115
@@ -145,10 +144,10 @@ If you're still getting `dispatch_failed` errors:
145144
We're going to use a simple [http4s](https://http4s.org/) client to make the API call, and [circe](https://circe.github.io/circe/) to decode the result.
146145

147146
```scala
148-
import io.circe.generic.auto._
147+
import io.circe.generic.auto.*
149148
import org.http4s.Method.GET
150149
import org.http4s.Uri.unsafeFromString
151-
import org.http4s._
150+
import org.http4s.*
152151
import org.http4s.blaze.client.BlazeClientBuilder
153152
import org.http4s.circe.CirceEntityCodec.circeEntityDecoder
154153

@@ -185,7 +184,7 @@ as well as an interactive tool for quickly prototyping layouts.
185184
```scala
186185

187186
// helper functions for building the various block types in the slack LayoutBlock SDK
188-
import io.laserdisc.slack4s.slack._
187+
import io.laserdisc.slack4s.slack.*
189188

190189
def formatNewsArticle(article: SpaceNewsArticle): Seq[LayoutBlock] =
191190
Seq(

project/Dependencies.scala

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ object Dependencies {
2121
libraryDependencies += "eu.timepit" %% "refined" % "0.11.0"
2222
)
2323

24+
val NewTypes = Seq(
25+
libraryDependencies += "io.monix" %% "newtypes-core" % "0.2.3"
26+
)
27+
2428
val Logging = Seq(
2529
libraryDependencies ++= Seq(
2630
"org.typelevel" %% "log4cats-slf4j" % "2.6.0",
@@ -44,11 +48,10 @@ object Dependencies {
4448
val CirceVersion = "0.14.5"
4549
val Circe = Seq(
4650
libraryDependencies ++= Seq(
47-
"io.circe" %% "circe-core" % CirceVersion,
48-
"io.circe" %% "circe-parser" % CirceVersion,
49-
"io.circe" %% "circe-generic" % CirceVersion,
50-
"io.circe" %% "circe-generic-extras" % "0.14.3",
51-
"io.circe" %% "circe-optics" % "0.14.1"
51+
"io.circe" %% "circe-core" % CirceVersion,
52+
"io.circe" %% "circe-parser" % CirceVersion
53+
// "io.circe" %% "circe-generic" % CirceVersion,
54+
// "io.circe" %% "circe-optics" % "0.14.1"
5255
)
5356
)
5457

src/main/scala/io/laserdisc/slack4s/slack/internal/SlackAPIClient.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.laserdisc.slack4s.slack.internal
22

33
import cats.effect.{Async, Ref, Resource}
4-
import cats.implicits._
4+
import cats.implicits.*
55
import com.slack.api.methods.request.chat.ChatPostMessageRequest
66
import fs2.io.net.Network
77
import org.http4s.client.Client
@@ -32,7 +32,7 @@ case class SlackResponseAccepted()
3232

3333
case class SlackAPIClientImpl[F[_]: Async](httpClient: Client[F]) extends SlackAPIClient[F] {
3434

35-
private[this] val logger = Slf4jLogger.getLogger[F]
35+
private val logger = Slf4jLogger.getLogger[F]
3636

3737
override def respond(url: String, input: ChatPostMessageRequest): F[Unit] =
3838
for {

src/main/scala/io/laserdisc/slack4s/slack/internal/package.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.laserdisc.slack4s.slack
22

33
import cats.effect.{Async, Sync}
4-
import cats.implicits._
4+
import cats.implicits.*
55
import com.google.gson.{FieldNamingPolicy, Gson, GsonBuilder}
66
import com.slack.api.app_backend.slash_commands.payload.SlashCommandPayload
77
import com.slack.api.methods.request.chat.ChatPostMessageRequest
@@ -10,12 +10,12 @@ import com.slack.api.model.block.composition.TextObject
1010
import com.slack.api.model.block.element.{BlockElement, RichTextElement}
1111
import com.slack.api.model.block.{ContextBlockElement, LayoutBlock}
1212
import com.slack.api.model.event.MessageChangedEvent.PreviousMessage
13-
import com.slack.api.util.json._
14-
import io.circe.parser._
13+
import com.slack.api.util.json.*
14+
import io.circe.parser.*
1515
import io.circe.{Decoder, Encoder}
16-
import org.http4s._
16+
import org.http4s.*
1717
import org.http4s.circe.jsonEncoderOf
18-
import org.http4s.FormDataDecoder._
18+
import org.http4s.FormDataDecoder.*
1919

2020
import scala.util.Try
2121

@@ -24,7 +24,7 @@ package object internal {
2424
/* The message classes that the slack SDK provides are intended for use with lombok & Gson. Rather
2525
* than build an entire family of circe codecs by hand, we delegate to Gson and use the gson factory
2626
* classes that are available in the slack SDK library. */
27-
private[this] val gson: Gson = {
27+
private val gson: Gson = {
2828
val gsonBuilder = new GsonBuilder
2929
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
3030
Map(

src/main/scala/io/laserdisc/slack4s/slack/package.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ package io.laserdisc.slack4s
22

33
import com.slack.api.app_backend.slash_commands.payload.SlashCommandPayload
44
import com.slack.api.methods.request.chat.ChatPostMessageRequest
5-
import com.slack.api.model.block._
6-
import com.slack.api.model.block.composition._
7-
import com.slack.api.model.block.element._
5+
import com.slack.api.model.block.*
6+
import com.slack.api.model.block.composition.*
7+
import com.slack.api.model.block.element.*
88
import io.laserdisc.slack4s.slashcmd.URL
99

10-
import scala.jdk.CollectionConverters._
10+
import scala.jdk.CollectionConverters.*
1111
import scala.util.matching.Regex
1212

1313
package object slack {

src/main/scala/io/laserdisc/slack4s/slashcmd/CommandMapper.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
package io.laserdisc.slack4s.slashcmd
22

33
import cats.effect.Sync
4-
import cats.implicits._
4+
import cats.implicits.*
55
import com.slack.api.app_backend.slash_commands.payload.SlashCommandPayload
6-
import eu.timepit.refined.auto._
76
import io.laserdisc.slack4s.internal.ProjectRepo
8-
import io.laserdisc.slack4s.slack.canned._
7+
import io.laserdisc.slack4s.slack.canned.*
98
import org.typelevel.log4cats.slf4j.Slf4jLogger.getLogger
109

1110
object CommandMapper {
@@ -21,7 +20,7 @@ object CommandMapper {
2120
)
2221
.as(helloFromSlack4s(payload)),
2322
responseType = Immediate,
24-
logId = "GETTING-STARTED"
23+
logId = LogToken.unsafeFrom("GETTING-STARTED")
2524
)
2625
}
2726
// $COVERAGE-ON$

src/main/scala/io/laserdisc/slack4s/slashcmd/Models.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.laserdisc.slack4s.slashcmd
22

33
import com.slack.api.methods.request.chat.ChatPostMessageRequest
4-
import eu.timepit.refined.auto._
54

65
/** The description of a Command - an effect to be evaluated, providing a response (along with instructions on how to deliver the response.
76
*
@@ -19,7 +18,7 @@ import eu.timepit.refined.auto._
1918
case class Command[F[_]](
2019
handler: F[ChatPostMessageRequest],
2120
responseType: ResponseType = Delayed,
22-
logId: LogToken = "NA"
21+
logId: LogToken = LogToken.unsafeFrom("NA")
2322
)
2423

2524
/** Used by the http4s middleware when building a validated `AuthedRequest`

src/main/scala/io/laserdisc/slack4s/slashcmd/SlashCommandBotBuilder.scala

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
package io.laserdisc.slack4s.slashcmd
22

3-
import cats.effect._
4-
import cats.implicits._
5-
import com.comcast.ip4s.{IpAddress, IpLiteralSyntax, Port}
6-
import eu.timepit.refined.auto._
3+
import cats.effect.*
4+
import cats.implicits.*
5+
import com.comcast.ip4s.*
76
import fs2.io.net.Network
87
import io.laserdisc.slack4s.slack.internal.SlackAPIClient
98
import io.laserdisc.slack4s.slashcmd.SlashCommandBotBuilder.Defaults
10-
import io.laserdisc.slack4s.slashcmd.internal.SignatureValidator._
11-
import io.laserdisc.slack4s.slashcmd.internal._
12-
import org.http4s._
9+
import io.laserdisc.slack4s.slashcmd.internal.*
10+
import io.laserdisc.slack4s.slashcmd.internal.SignatureValidator.*
11+
import org.http4s.*
12+
import org.http4s.Uri.Path
1313
import org.http4s.dsl.Http4sDsl
1414
import org.http4s.ember.server.EmberServerBuilder
1515
import org.http4s.server.{Router, Server}
@@ -19,9 +19,9 @@ import org.typelevel.log4cats.slf4j.Slf4jLogger
1919
object SlashCommandBotBuilder {
2020

2121
object Defaults {
22-
val BindPort: Port = port"8080"
23-
val BindAddress: IpAddress = ipv4"0.0.0.0"
24-
val EndpointRoot: EndpointRoot = "/"
22+
val BindPort: Port = port"8080"
23+
val BindAddress: IpAddress = ipv4"0.0.0.0"
24+
val EndpointRoot: Path = Path.Root
2525
}
2626

2727
def apply[F[_]: Async: Network](signingSecret: SigningSecret): SlashCommandBotBuilder[F] =
@@ -32,23 +32,23 @@ class SlashCommandBotBuilder[F[_]: Async: Network] private[slashcmd] (
3232
signingSecret: SigningSecret,
3333
bindPort: Port = Defaults.BindPort,
3434
bindAddress: IpAddress = Defaults.BindAddress,
35-
endpointRoot: EndpointRoot = Defaults.EndpointRoot,
35+
endpointRoot: Path = Defaults.EndpointRoot,
3636
commandParser: Option[CommandMapper[F]] = None,
3737
additionalRoutes: Option[HttpRoutes[F]] = None,
3838
http4sBuilder: EmberServerBuilder[F] => EmberServerBuilder[F] = (b: EmberServerBuilder[F]) => b
3939
) {
4040
type Self = SlashCommandBotBuilder[F]
4141

42-
private[this] val logger: Logger[F] = Slf4jLogger.getLogger[F]
42+
private val logger: Logger[F] = Slf4jLogger.getLogger[F]
4343

44-
private[this] val dsl = Http4sDsl[F]
45-
import dsl._
44+
private val dsl = Http4sDsl[F]
45+
import dsl.*
4646

47-
private[this] def copy(
47+
private def copy(
4848
signingSecret: SigningSecret = signingSecret,
4949
bindPort: Port = bindPort,
5050
bindAddress: IpAddress = bindAddress,
51-
endpointRoot: EndpointRoot = endpointRoot,
51+
endpointRoot: Path = endpointRoot,
5252
commandParser: Option[CommandMapper[F]] = commandParser,
5353
additionalRoutes: Option[HttpRoutes[F]] = additionalRoutes,
5454
http4sBuilder: EmberServerBuilder[F] => EmberServerBuilder[F] = http4sBuilder
@@ -66,7 +66,7 @@ class SlashCommandBotBuilder[F[_]: Async: Network] private[slashcmd] (
6666
def withBindOptions(port: Port, address: IpAddress = Defaults.BindAddress): Self =
6767
copy(bindPort = port, bindAddress = address)
6868

69-
def withEndpointRoot(root: EndpointRoot): Self =
69+
def withEndpointRoot(root: Path): Self =
7070
copy(endpointRoot = root)
7171

7272
def withAdditionalRoutes(routes: HttpRoutes[F]): Self =
@@ -120,10 +120,10 @@ class SlashCommandBotBuilder[F[_]: Async: Network] private[slashcmd] (
120120
def buildHttpApp(cmdRunner: CommandRunner[F], additionalRoutes: Option[HttpRoutes[F]] = None): HttpApp[F] = {
121121

122122
val botRoutes = Router(
123-
s"${endpointRoot.value}healthCheck" -> HttpRoutes.of[F] { case GET -> Root =>
123+
s"${endpointRoot}healthCheck" -> HttpRoutes.of[F] { case GET -> Root =>
124124
Ok.apply(s"OK")
125125
},
126-
s"${endpointRoot.value}slack" -> withValidSignature(signingSecret).apply(
126+
s"${endpointRoot}slack" -> withValidSignature(signingSecret).apply(
127127
AuthedRoutes.of[SlackUser, F] { case req @ POST -> Root / "slashCmd" as _ =>
128128
cmdRunner.processRequest(req)
129129
}

0 commit comments

Comments
 (0)