diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8de960e..faed62c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,7 +12,8 @@ jobs: strategy: matrix: scala: - - 2.13.10 + - 2.13.11 + - 3.3.0 java: - corretto@1.11 - corretto@1.17 diff --git a/README.md b/README.md index 545f819..11847c9 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/build.sbt b/build.sbt index df6e4a6..77080d0 100644 --- a/build.sbt +++ b/build.sbt @@ -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, @@ -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" ) ), @@ -26,19 +29,49 @@ 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 ------------------------- @@ -46,6 +79,7 @@ lazy val root = project Dependencies.TestLib, Dependencies.Circe, Dependencies.Refined, + Dependencies.NewTypes, Dependencies.Logging, Dependencies.Http4s, Dependencies.Slack, diff --git a/docs/tutorial.md b/docs/tutorial.md index e955341..f944927 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -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 @@ -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 @@ -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( diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 783b57a..6bf68f1 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -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", @@ -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" ) ) diff --git a/src/main/scala/io/laserdisc/slack4s/slack/internal/SlackAPIClient.scala b/src/main/scala/io/laserdisc/slack4s/slack/internal/SlackAPIClient.scala index 952edf9..dfc0ab5 100644 --- a/src/main/scala/io/laserdisc/slack4s/slack/internal/SlackAPIClient.scala +++ b/src/main/scala/io/laserdisc/slack4s/slack/internal/SlackAPIClient.scala @@ -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 @@ -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 { diff --git a/src/main/scala/io/laserdisc/slack4s/slack/internal/package.scala b/src/main/scala/io/laserdisc/slack4s/slack/internal/package.scala index e3c4030..f64bdb7 100644 --- a/src/main/scala/io/laserdisc/slack4s/slack/internal/package.scala +++ b/src/main/scala/io/laserdisc/slack4s/slack/internal/package.scala @@ -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 @@ -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 @@ -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( diff --git a/src/main/scala/io/laserdisc/slack4s/slack/package.scala b/src/main/scala/io/laserdisc/slack4s/slack/package.scala index 7c74a3f..1c69adf 100644 --- a/src/main/scala/io/laserdisc/slack4s/slack/package.scala +++ b/src/main/scala/io/laserdisc/slack4s/slack/package.scala @@ -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 { diff --git a/src/main/scala/io/laserdisc/slack4s/slashcmd/CommandMapper.scala b/src/main/scala/io/laserdisc/slack4s/slashcmd/CommandMapper.scala index a999c6e..d26c864 100644 --- a/src/main/scala/io/laserdisc/slack4s/slashcmd/CommandMapper.scala +++ b/src/main/scala/io/laserdisc/slack4s/slashcmd/CommandMapper.scala @@ -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 { @@ -21,7 +20,7 @@ object CommandMapper { ) .as(helloFromSlack4s(payload)), responseType = Immediate, - logId = "GETTING-STARTED" + logId = LogToken.unsafeFrom("GETTING-STARTED") ) } // $COVERAGE-ON$ diff --git a/src/main/scala/io/laserdisc/slack4s/slashcmd/Models.scala b/src/main/scala/io/laserdisc/slack4s/slashcmd/Models.scala index 45940a6..4eb0f6b 100644 --- a/src/main/scala/io/laserdisc/slack4s/slashcmd/Models.scala +++ b/src/main/scala/io/laserdisc/slack4s/slashcmd/Models.scala @@ -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. * @@ -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` diff --git a/src/main/scala/io/laserdisc/slack4s/slashcmd/SlashCommandBotBuilder.scala b/src/main/scala/io/laserdisc/slack4s/slashcmd/SlashCommandBotBuilder.scala index 54a8c8a..e76deb5 100644 --- a/src/main/scala/io/laserdisc/slack4s/slashcmd/SlashCommandBotBuilder.scala +++ b/src/main/scala/io/laserdisc/slack4s/slashcmd/SlashCommandBotBuilder.scala @@ -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} @@ -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] = @@ -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 @@ -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 = @@ -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) } diff --git a/src/main/scala/io/laserdisc/slack4s/slashcmd/internal/CommandRunner.scala b/src/main/scala/io/laserdisc/slack4s/slashcmd/internal/CommandRunner.scala index b7045db..77778ac 100644 --- a/src/main/scala/io/laserdisc/slack4s/slashcmd/internal/CommandRunner.scala +++ b/src/main/scala/io/laserdisc/slack4s/slashcmd/internal/CommandRunner.scala @@ -2,17 +2,17 @@ package io.laserdisc.slack4s.slashcmd.internal import cats.effect.Async import cats.effect.std.Queue -import cats.implicits._ +import cats.implicits.* import com.slack.api.app_backend.slash_commands.payload.SlashCommandPayload import com.slack.api.methods.request.chat.ChatPostMessageRequest import fs2.Stream -import io.circe.syntax._ -import io.laserdisc.slack4s.slack._ -import io.laserdisc.slack4s.slack.canned._ -import io.laserdisc.slack4s.slack.internal._ -import io.laserdisc.slack4s.slashcmd._ +import io.circe.syntax.* +import io.laserdisc.slack4s.slack.* +import io.laserdisc.slack4s.slack.canned.* +import io.laserdisc.slack4s.slack.internal.* +import io.laserdisc.slack4s.slashcmd.* import org.http4s.FormDataDecoder.formEntityDecoder -import org.http4s._ +import org.http4s.* import org.typelevel.log4cats.slf4j.Slf4jLogger object CommandRunner { @@ -37,10 +37,10 @@ class CommandRunnerImpl[F[_]: Async]( queue: Queue[F, (SlashCommandPayload, Command[F])] ) extends CommandRunner[F] { - private[this] val logger = Slf4jLogger.getLogger + private val logger = Slf4jLogger.getLogger // log but otherwise ignore failures to send messages to slack - private[this] def respond( + private def respond( payload: SlashCommandPayload, command: Command[F], response: ChatPostMessageRequest @@ -54,7 +54,7 @@ class CommandRunnerImpl[F[_]: Async]( respondSomethingWentWrong(payload) } - private[this] def respondSomethingWentWrong(payload: SlashCommandPayload): F[Unit] = { + private def respondSomethingWentWrong(payload: SlashCommandPayload): F[Unit] = { val response = somethingWentWrong(payload) diff --git a/src/main/scala/io/laserdisc/slack4s/slashcmd/internal/SignatureValidator.scala b/src/main/scala/io/laserdisc/slack4s/slashcmd/internal/SignatureValidator.scala index a68e80c..a88531f 100644 --- a/src/main/scala/io/laserdisc/slack4s/slashcmd/internal/SignatureValidator.scala +++ b/src/main/scala/io/laserdisc/slack4s/slashcmd/internal/SignatureValidator.scala @@ -1,14 +1,14 @@ package io.laserdisc.slack4s.slashcmd.internal import cats.data.{Kleisli, OptionT} import cats.effect.{Async, Sync} -import cats.implicits._ +import cats.implicits.* import com.slack.api.app_backend.SlackSignature -import com.slack.api.app_backend.SlackSignature.HeaderNames._ +import com.slack.api.app_backend.SlackSignature.HeaderNames.* import com.slack.api.app_backend.SlackSignature.Verifier import com.slack.api.app_backend.slash_commands.payload.SlashCommandPayload -import io.laserdisc.slack4s.slack.internal._ -import io.laserdisc.slack4s.slashcmd._ -import org.http4s._ +import io.laserdisc.slack4s.slack.internal.* +import io.laserdisc.slack4s.slashcmd.* +import org.http4s.* import org.http4s.server.AuthMiddleware import org.typelevel.ci.CIString import org.typelevel.log4cats.slf4j.Slf4jLogger @@ -18,17 +18,17 @@ object SignatureValidator { type Validated[T] = Either[AuthError, T] - private[this] def forbidden[F[_]: Sync]: AuthedRoutes[String, F] = + private def forbidden[F[_]: Sync]: AuthedRoutes[String, F] = Kleisli(_ => OptionT.pure(Response[F](Status.Unauthorized))) def withValidSignature[F[_]: Async]( - signingSecret: String + signingSecret: SigningSecret ): AuthMiddleware[F, SlackUser] = { val logger = Slf4jLogger.getLogger[F] val slackSignatureVerifier = new Verifier( - new SlackSignature.Generator(signingSecret) + new SlackSignature.Generator(signingSecret.value) ) def getRequiredHeader(req: Request[F], header: String): F[String] = diff --git a/src/main/scala/io/laserdisc/slack4s/slashcmd/package.scala b/src/main/scala/io/laserdisc/slack4s/slashcmd/package.scala index cb3dc9c..af45b35 100644 --- a/src/main/scala/io/laserdisc/slack4s/slashcmd/package.scala +++ b/src/main/scala/io/laserdisc/slack4s/slashcmd/package.scala @@ -5,9 +5,22 @@ import eu.timepit.refined.api.{Refined, RefinedTypeOps} import eu.timepit.refined.numeric.Positive import eu.timepit.refined.string.{MatchesRegex, Url} import eu.timepit.refined.types.string.NonEmptyString +import monix.newtypes.{BuildFailure, NewtypeValidated} package object slashcmd { + type EmailAddress = EmailAddress.Type + + object EmailAddress extends NewtypeValidated[String] { + def apply(v: String): Either[BuildFailure[Type], Type] = + if (v.contains("@")) + Right(unsafeCoerce(v)) + else + Left(BuildFailure("missing @")) + } + + val k = EmailAddress("yo hoo") + final type SigningSecret = NonEmptyString final type LogToken = String Refined MatchesRegex["[A-Za-z0-9\\-\\_]+"] final type URL = String Refined Url diff --git a/src/test/scala/examples/SpaceNewsExample.scala b/src/test/scala/examples/SpaceNewsExample.scala index 21090ff..7d9215d 100644 --- a/src/test/scala/examples/SpaceNewsExample.scala +++ b/src/test/scala/examples/SpaceNewsExample.scala @@ -3,13 +3,13 @@ package examples import cats.effect.{IO, IOApp} import com.slack.api.app_backend.slash_commands.payload.SlashCommandPayload import com.slack.api.model.block.LayoutBlock -import eu.timepit.refined.auto._ -import io.circe.generic.auto._ -import io.laserdisc.slack4s.slack._ -import io.laserdisc.slack4s.slashcmd._ +import io.circe.{Decoder, HCursor} +//import io.circe.generic.auto.* +import io.laserdisc.slack4s.slack.* +import io.laserdisc.slack4s.slashcmd.* +import org.http4s.* import org.http4s.Method.GET import org.http4s.Uri.unsafeFromString -import org.http4s._ import org.http4s.circe.CirceEntityCodec.circeEntityDecoder import org.http4s.ember.client.EmberClientBuilder @@ -17,7 +17,7 @@ import java.time.Instant object SpaceNewsExample extends IOApp.Simple { - val secret: SigningSecret = "7e162b0fd1bf1ca4537afa4246368c2c" // not a real secret + val secret: SigningSecret = SigningSecret.unsafeFrom("7e162b0fd1bf1ca4537afa4246368c2c") // not a real secret override def run: IO[Unit] = SlashCommandBotBuilder[IO](secret) @@ -87,4 +87,13 @@ object SpaceNewsExample extends IOApp.Simple { updatedAt: Instant ) + implicit val dec: Decoder[SpaceNewsArticle] = (c: HCursor) => + for { + a <- c.downField("title").as[String] + u <- c.downField("url").as[String] + i <- c.downField("imageUrl").as[String] + n <- c.downField("newsSite").as[String] + s <- c.downField("summary").as[String] + updated <- c.downField("updatedAt").as[Instant] + } yield new SpaceNewsArticle(a, u, i, n, s, updated) } diff --git a/src/test/scala/io/laserdisc/slack4s/slack/SlackMessageBuildersTest.scala b/src/test/scala/io/laserdisc/slack4s/slack/SlackMessageBuildersTest.scala index b054a80..2089154 100644 --- a/src/test/scala/io/laserdisc/slack4s/slack/SlackMessageBuildersTest.scala +++ b/src/test/scala/io/laserdisc/slack4s/slack/SlackMessageBuildersTest.scala @@ -1,11 +1,11 @@ package io.laserdisc.slack4s.slack -import diffson._ -import diffson.circe._ +import diffson.* +import diffson.circe.* import diffson.jsonpatch.JsonPatch -import diffson.jsonpatch.simplediff._ -import io.circe.parser._ -import io.circe.syntax._ +import diffson.jsonpatch.simplediff.* +import io.circe.parser.* +import io.circe.syntax.* import io.laserdisc.slack4s.slack.internal.postMsgReqCirceEncoder import io.laserdisc.slack4s.slashcmd.URL import munit.FunSuite diff --git a/src/test/scala/io/laserdisc/slack4s/slack/internal/SlackAPIClientTest.scala b/src/test/scala/io/laserdisc/slack4s/slack/internal/SlackAPIClientTest.scala index 9f43c22..1e67b0a 100644 --- a/src/test/scala/io/laserdisc/slack4s/slack/internal/SlackAPIClientTest.scala +++ b/src/test/scala/io/laserdisc/slack4s/slack/internal/SlackAPIClientTest.scala @@ -3,14 +3,13 @@ package io.laserdisc.slack4s.slack.internal import cats.effect.unsafe.implicits.global import cats.effect.{IO, Resource} import io.circe.Json -import io.circe.optics.JsonPath.root -import io.laserdisc.slack4s.slack._ +import io.laserdisc.slack4s.slack.* import io.laserdisc.slack4s.slashcmd.URL import munit.FunSuite import org.http4s.Response import org.http4s.circe.jsonDecoder import org.http4s.client.Client -import org.http4s.implicits.http4sLiteralsSyntax +import org.http4s.implicits.* class SlackAPIClientTest extends FunSuite { @@ -30,8 +29,9 @@ class SlackAPIClientTest extends FunSuite { /* we're not testing the whole set of message codecs here, but we do want to ensure that * the codecs are being invoked. We purposefully choose a nested value at particular * location that also ensures that slack's preferred JSON style (snake_case) is used. */ - val json = req.as[Json].unsafeRunSync() - val firstImgURL = root.blocks.each.accessory.image_url.string.getAll(json).headOption + val json = req.as[Json].unsafeRunSync() + + val firstImgURL = json.hcursor.downField("blocks").downArray.downField("accessory").downField("image_url").as[String].toOption assertEquals( firstImgURL, diff --git a/src/test/scala/io/laserdisc/slack4s/slashcmd/SlashCommandSuite.scala b/src/test/scala/io/laserdisc/slack4s/slashcmd/SlashCommandSuite.scala index 854d92f..f119441 100644 --- a/src/test/scala/io/laserdisc/slack4s/slashcmd/SlashCommandSuite.scala +++ b/src/test/scala/io/laserdisc/slack4s/slashcmd/SlashCommandSuite.scala @@ -4,21 +4,20 @@ import cats.effect.IO import cats.effect.unsafe.IORuntime import com.slack.api.app_backend.SlackSignature import com.slack.api.methods.request.chat.ChatPostMessageRequest -import eu.timepit.refined.auto._ import io.laserdisc.slack4s.slack.internal.SlackAPIClient import io.laserdisc.slack4s.slashcmd.internal.CommandRunner import munit.FunSuite +import org.http4s.* import org.http4s.Method.POST -import org.http4s._ -import org.http4s.client.dsl.io._ -import org.http4s.implicits.http4sLiteralsSyntax +import org.http4s.client.dsl.io.* +import org.http4s.implicits.* import org.typelevel.ci.CIString import scala.concurrent.duration.FiniteDuration trait SlashCommandSuite extends FunSuite { - val DefaultTestSigningSecret: SigningSecret = "aabbbcccdddeeefff111222333444555666" + val DefaultTestSigningSecret: SigningSecret = SigningSecret.unsafeFrom("aabbbcccdddeeefff111222333444555666") val DefaultResponseUrl: String = "http://localhost:1234/not/a/real/callback" val DefaultTeamID: String = "testSlackTeamID" val DefaultUserID: String = "testSlackUserID" @@ -70,7 +69,11 @@ trait SlashCommandSuite extends FunSuite { // The app starts `runner.processBGCommandQueue` in parallel to the http service, // so we run it briefly to capture any background invocations to the slack API as well _ <- waitForCallbacks.fold(IO.unit) { case (takeN, duration) => - runner.processBGCommandQueue.take(takeN).interruptAfter(duration).compile.drain + runner.processBGCommandQueue + .take(takeN.longValue) + .interruptAfter(duration) + .compile + .drain } // collect the invocations diff --git a/src/test/scala/io/laserdisc/slack4s/slashcmd/internal/CommandRunnerTest.scala b/src/test/scala/io/laserdisc/slack4s/slashcmd/internal/CommandRunnerTest.scala index a205265..e0f4aee 100644 --- a/src/test/scala/io/laserdisc/slack4s/slashcmd/internal/CommandRunnerTest.scala +++ b/src/test/scala/io/laserdisc/slack4s/slashcmd/internal/CommandRunnerTest.scala @@ -1,9 +1,9 @@ package io.laserdisc.slack4s.slashcmd.internal import cats.effect.IO -import io.laserdisc.slack4s.slack._ -import io.laserdisc.slack4s.slashcmd._ -import org.http4s.circe.CirceEntityCodec._ +import io.laserdisc.slack4s.slack.* +import io.laserdisc.slack4s.slashcmd.* +import org.http4s.circe.CirceEntityCodec.* import org.http4s.{Response, Status} import scala.concurrent.duration.{DurationInt, FiniteDuration} diff --git a/src/test/scala/io/laserdisc/slack4s/slashcmd/internal/SignatureValidatorTest.scala b/src/test/scala/io/laserdisc/slack4s/slashcmd/internal/SignatureValidatorTest.scala index 7a4281e..c723a3f 100644 --- a/src/test/scala/io/laserdisc/slack4s/slashcmd/internal/SignatureValidatorTest.scala +++ b/src/test/scala/io/laserdisc/slack4s/slashcmd/internal/SignatureValidatorTest.scala @@ -3,12 +3,12 @@ package io.laserdisc.slack4s.slashcmd.internal import cats.effect.IO import com.slack.api.app_backend.SlackSignature import com.slack.api.app_backend.SlackSignature.HeaderNames.{X_SLACK_REQUEST_TIMESTAMP, X_SLACK_SIGNATURE} -import io.laserdisc.slack4s.slack._ -import io.laserdisc.slack4s.slashcmd._ +import io.laserdisc.slack4s.slack.* +import io.laserdisc.slack4s.slashcmd.* import org.http4s.Method.{GET, POST} import org.http4s.{Charset, Header, Headers, Status, UrlForm} -import org.http4s.client.dsl.io._ -import org.http4s.implicits.http4sLiteralsSyntax +import org.http4s.client.dsl.io.* +import org.http4s.implicits.* import org.typelevel.ci.CIString class SignatureValidatorTest extends SlashCommandSuite { diff --git a/src/test/scala/io/laserdisc/slack4s/slashcmd/internal/package.scala b/src/test/scala/io/laserdisc/slack4s/slashcmd/internal/package.scala index 2bb887e..74cae9c 100644 --- a/src/test/scala/io/laserdisc/slack4s/slashcmd/internal/package.scala +++ b/src/test/scala/io/laserdisc/slack4s/slashcmd/internal/package.scala @@ -4,7 +4,7 @@ import cats.effect.IO import com.slack.api.methods.request.chat.ChatPostMessageRequest import io.circe.Decoder import org.http4s.Response -import org.http4s.circe.CirceEntityCodec._ +import org.http4s.circe.CirceEntityCodec.* package object internal {