Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scala 3 support #114

Merged
merged 1 commit into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:
- [email protected]
- [email protected]
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:[email protected]:laserdisc-io/slack4s.git",
"scm:git:[email protected]: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
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
Loading