Skip to content

Commit

Permalink
scala 3 support
Browse files Browse the repository at this point in the history
  • Loading branch information
barryoneill committed Jul 30, 2023
1 parent 04e64dc commit fe33cf0
Show file tree
Hide file tree
Showing 21 changed files with 169 additions and 110 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ jobs:
strategy:
matrix:
scala:
- 2.13.10
- 2.13.11
- 3.3.0
java:
- corretto@1.11
- corretto@1.17
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,11 @@ With your [signing secret](https://api.slack.com/authentication/verifying-reques

```scala
import cats.effect.{IO, IOApp}
import eu.timepit.refined.auto._
import io.laserdisc.slack4s.slashcmd._
import io.laserdisc.slack4s.slashcmd.*

object MySlackBot extends IOApp.Simple {

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

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

Expand Down
54 changes: 44 additions & 10 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
ThisBuild / scalaVersion := "2.13.11"
lazy val scala213 = "2.13.11"
lazy val scala3 = "3.3.0"
lazy val supportedScalaVersions = List(scala213, scala3)
ThisBuild / crossScalaVersions := supportedScalaVersions
ThisBuild / scalaVersion := scala213

lazy val publishSettings = Seq(
Test / publishArtifact := false,
Expand All @@ -11,7 +15,6 @@ lazy val publishSettings = Seq(
scmInfo := Some(
ScmInfo(
url("https://github.com/laserdisc-io/slack4s/tree/master"),
"scm:git:git@github.com:laserdisc-io/slack4s.git",
"scm:git:git@github.com:laserdisc-io/slack4s.git"
)
),
Expand All @@ -26,26 +29,57 @@ lazy val root = project
name := "slack4s",
publishSettings,
Seq(
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1"),
libraryDependencies ++= Seq(
compilerPlugin(("org.typelevel" %% "kind-projector" % "0.13.2").cross(CrossVersion.full)),
compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")
).filterNot(_ => scalaVersion.value.startsWith("3.")),
scalacOptions ++= Seq(
"-deprecation",
"-encoding",
"UTF-8",
"-deprecation",
"-unchecked",
"-feature",
"-language:higherKinds",
"-language:implicitConversions",
"-language:postfixOps",
"-Xlint:_,-byname-implicit", // see https://github.com/scala/bug/issues/12072
"-language:existentials,experimental.macros,higherKinds,implicitConversions,postfixOps",
"-unchecked",
"-Xfatal-warnings"
)
),
scalacOptions ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, minor)) if minor >= 13 =>
Seq(
"-Xlint:-unused,_",
"-Ywarn-numeric-widen",
"-Ywarn-value-discard",
"-Ywarn-unused:implicits",
"-Ywarn-unused:imports",
"-Xsource:3",
"-Xlint:-byname-implicit",
"-P:kind-projector:underscore-placeholders",
"-Xlint",
"-Ywarn-macros:after"
)
case _ => Seq.empty
}
},
scalacOptions ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((3, _)) =>
Seq(
"-Ykind-projector:underscores",
"-source:future",
"-language:adhocExtensions",
"-Wconf:msg=`= _` has been deprecated; use `= uninitialized` instead.:s"
)
case _ => Seq.empty
}
}
),
Test / fork := true,
// ------------------------- deps -------------------------
excludeDependencies += "commons-logging",
Dependencies.TestLib,
Dependencies.Circe,
Dependencies.Refined,
Dependencies.NewTypes,
Dependencies.Logging,
Dependencies.Http4s,
Dependencies.Slack,
Expand Down
11 changes: 5 additions & 6 deletions docs/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,12 @@ Following the instructions on the [main README](../README.md), let's create a sl
```scala
import cats.effect.{IO, IOApp}
import eu.timepit.refined.auto._
import io.laserdisc.slack4s.slashcmd._
import io.laserdisc.slack4s.slashcmd.*
object MySlackBot extends IOApp.Simple {
// please don't hardcode secrets, this is just a demo
val secret: SigningSecret = "7e16-----redacted------68c2c"
val secret: SigningSecret = SigningSecret.unsfeFrom("7e16-----redacted------68c2c")
override def run: IO[Unit] = SlashCommandBotBuilder[IO](secret).serve
Expand Down Expand Up @@ -145,10 +144,10 @@ If you're still getting `dispatch_failed` errors:
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.

```scala
import io.circe.generic.auto._
import io.circe.generic.auto.*
import org.http4s.Method.GET
import org.http4s.Uri.unsafeFromString
import org.http4s._
import org.http4s.*
import org.http4s.blaze.client.BlazeClientBuilder
import org.http4s.circe.CirceEntityCodec.circeEntityDecoder

Expand Down Expand Up @@ -185,7 +184,7 @@ as well as an interactive tool for quickly prototyping layouts.
```scala

// helper functions for building the various block types in the slack LayoutBlock SDK
import io.laserdisc.slack4s.slack._
import io.laserdisc.slack4s.slack.*

def formatNewsArticle(article: SpaceNewsArticle): Seq[LayoutBlock] =
Seq(
Expand Down
13 changes: 8 additions & 5 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ object Dependencies {
libraryDependencies += "eu.timepit" %% "refined" % "0.11.0"
)

val NewTypes = Seq(
libraryDependencies += "io.monix" %% "newtypes-core" % "0.2.3"
)

val Logging = Seq(
libraryDependencies ++= Seq(
"org.typelevel" %% "log4cats-slf4j" % "2.6.0",
Expand All @@ -44,11 +48,10 @@ object Dependencies {
val CirceVersion = "0.14.5"
val Circe = Seq(
libraryDependencies ++= Seq(
"io.circe" %% "circe-core" % CirceVersion,
"io.circe" %% "circe-parser" % CirceVersion,
"io.circe" %% "circe-generic" % CirceVersion,
"io.circe" %% "circe-generic-extras" % "0.14.3",
"io.circe" %% "circe-optics" % "0.14.1"
"io.circe" %% "circe-core" % CirceVersion,
"io.circe" %% "circe-parser" % CirceVersion
// "io.circe" %% "circe-generic" % CirceVersion,
// "io.circe" %% "circe-optics" % "0.14.1"
)
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.laserdisc.slack4s.slack.internal

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

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

private[this] val logger = Slf4jLogger.getLogger[F]
private val logger = Slf4jLogger.getLogger[F]

override def respond(url: String, input: ChatPostMessageRequest): F[Unit] =
for {
Expand Down
12 changes: 6 additions & 6 deletions src/main/scala/io/laserdisc/slack4s/slack/internal/package.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.laserdisc.slack4s.slack

import cats.effect.{Async, Sync}
import cats.implicits._
import cats.implicits.*
import com.google.gson.{FieldNamingPolicy, Gson, GsonBuilder}
import com.slack.api.app_backend.slash_commands.payload.SlashCommandPayload
import com.slack.api.methods.request.chat.ChatPostMessageRequest
Expand All @@ -10,12 +10,12 @@ import com.slack.api.model.block.composition.TextObject
import com.slack.api.model.block.element.{BlockElement, RichTextElement}
import com.slack.api.model.block.{ContextBlockElement, LayoutBlock}
import com.slack.api.model.event.MessageChangedEvent.PreviousMessage
import com.slack.api.util.json._
import io.circe.parser._
import com.slack.api.util.json.*
import io.circe.parser.*
import io.circe.{Decoder, Encoder}
import org.http4s._
import org.http4s.*
import org.http4s.circe.jsonEncoderOf
import org.http4s.FormDataDecoder._
import org.http4s.FormDataDecoder.*

import scala.util.Try

Expand All @@ -24,7 +24,7 @@ package object internal {
/* The message classes that the slack SDK provides are intended for use with lombok & Gson. Rather
* than build an entire family of circe codecs by hand, we delegate to Gson and use the gson factory
* classes that are available in the slack SDK library. */
private[this] val gson: Gson = {
private val gson: Gson = {
val gsonBuilder = new GsonBuilder
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
Map(
Expand Down
8 changes: 4 additions & 4 deletions src/main/scala/io/laserdisc/slack4s/slack/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package io.laserdisc.slack4s

import com.slack.api.app_backend.slash_commands.payload.SlashCommandPayload
import com.slack.api.methods.request.chat.ChatPostMessageRequest
import com.slack.api.model.block._
import com.slack.api.model.block.composition._
import com.slack.api.model.block.element._
import com.slack.api.model.block.*
import com.slack.api.model.block.composition.*
import com.slack.api.model.block.element.*
import io.laserdisc.slack4s.slashcmd.URL

import scala.jdk.CollectionConverters._
import scala.jdk.CollectionConverters.*
import scala.util.matching.Regex

package object slack {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package io.laserdisc.slack4s.slashcmd

import cats.effect.Sync
import cats.implicits._
import cats.implicits.*
import com.slack.api.app_backend.slash_commands.payload.SlashCommandPayload
import eu.timepit.refined.auto._
import io.laserdisc.slack4s.internal.ProjectRepo
import io.laserdisc.slack4s.slack.canned._
import io.laserdisc.slack4s.slack.canned.*
import org.typelevel.log4cats.slf4j.Slf4jLogger.getLogger

object CommandMapper {
Expand All @@ -21,7 +20,7 @@ object CommandMapper {
)
.as(helloFromSlack4s(payload)),
responseType = Immediate,
logId = "GETTING-STARTED"
logId = LogToken.unsafeFrom("GETTING-STARTED")
)
}
// $COVERAGE-ON$
Expand Down
3 changes: 1 addition & 2 deletions src/main/scala/io/laserdisc/slack4s/slashcmd/Models.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.laserdisc.slack4s.slashcmd

import com.slack.api.methods.request.chat.ChatPostMessageRequest
import eu.timepit.refined.auto._

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

/** Used by the http4s middleware when building a validated `AuthedRequest`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package io.laserdisc.slack4s.slashcmd

import cats.effect._
import cats.implicits._
import com.comcast.ip4s.{IpAddress, IpLiteralSyntax, Port}
import eu.timepit.refined.auto._
import cats.effect.*
import cats.implicits.*
import com.comcast.ip4s.*
import fs2.io.net.Network
import io.laserdisc.slack4s.slack.internal.SlackAPIClient
import io.laserdisc.slack4s.slashcmd.SlashCommandBotBuilder.Defaults
import io.laserdisc.slack4s.slashcmd.internal.SignatureValidator._
import io.laserdisc.slack4s.slashcmd.internal._
import org.http4s._
import io.laserdisc.slack4s.slashcmd.internal.*
import io.laserdisc.slack4s.slashcmd.internal.SignatureValidator.*
import org.http4s.*
import org.http4s.Uri.Path
import org.http4s.dsl.Http4sDsl
import org.http4s.ember.server.EmberServerBuilder
import org.http4s.server.{Router, Server}
Expand All @@ -19,9 +19,9 @@ import org.typelevel.log4cats.slf4j.Slf4jLogger
object SlashCommandBotBuilder {

object Defaults {
val BindPort: Port = port"8080"
val BindAddress: IpAddress = ipv4"0.0.0.0"
val EndpointRoot: EndpointRoot = "/"
val BindPort: Port = port"8080"
val BindAddress: IpAddress = ipv4"0.0.0.0"
val EndpointRoot: Path = Path.Root
}

def apply[F[_]: Async: Network](signingSecret: SigningSecret): SlashCommandBotBuilder[F] =
Expand All @@ -32,23 +32,23 @@ class SlashCommandBotBuilder[F[_]: Async: Network] private[slashcmd] (
signingSecret: SigningSecret,
bindPort: Port = Defaults.BindPort,
bindAddress: IpAddress = Defaults.BindAddress,
endpointRoot: EndpointRoot = Defaults.EndpointRoot,
endpointRoot: Path = Defaults.EndpointRoot,
commandParser: Option[CommandMapper[F]] = None,
additionalRoutes: Option[HttpRoutes[F]] = None,
http4sBuilder: EmberServerBuilder[F] => EmberServerBuilder[F] = (b: EmberServerBuilder[F]) => b
) {
type Self = SlashCommandBotBuilder[F]

private[this] val logger: Logger[F] = Slf4jLogger.getLogger[F]
private val logger: Logger[F] = Slf4jLogger.getLogger[F]

private[this] val dsl = Http4sDsl[F]
import dsl._
private val dsl = Http4sDsl[F]
import dsl.*

private[this] def copy(
private def copy(
signingSecret: SigningSecret = signingSecret,
bindPort: Port = bindPort,
bindAddress: IpAddress = bindAddress,
endpointRoot: EndpointRoot = endpointRoot,
endpointRoot: Path = endpointRoot,
commandParser: Option[CommandMapper[F]] = commandParser,
additionalRoutes: Option[HttpRoutes[F]] = additionalRoutes,
http4sBuilder: EmberServerBuilder[F] => EmberServerBuilder[F] = http4sBuilder
Expand All @@ -66,7 +66,7 @@ class SlashCommandBotBuilder[F[_]: Async: Network] private[slashcmd] (
def withBindOptions(port: Port, address: IpAddress = Defaults.BindAddress): Self =
copy(bindPort = port, bindAddress = address)

def withEndpointRoot(root: EndpointRoot): Self =
def withEndpointRoot(root: Path): Self =
copy(endpointRoot = root)

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

val botRoutes = Router(
s"${endpointRoot.value}healthCheck" -> HttpRoutes.of[F] { case GET -> Root =>
s"${endpointRoot}healthCheck" -> HttpRoutes.of[F] { case GET -> Root =>
Ok.apply(s"OK")
},
s"${endpointRoot.value}slack" -> withValidSignature(signingSecret).apply(
s"${endpointRoot}slack" -> withValidSignature(signingSecret).apply(
AuthedRoutes.of[SlackUser, F] { case req @ POST -> Root / "slashCmd" as _ =>
cmdRunner.processRequest(req)
}
Expand Down
Loading

0 comments on commit fe33cf0

Please sign in to comment.