Skip to content

Commit ad4072d

Browse files
authored
feat!: migrate to sttp 4 (#516)
1 parent 27dced1 commit ad4072d

24 files changed

Lines changed: 98 additions & 92 deletions

build.sc

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ object library {
1616
val catsEffect3 = "3.6.2"
1717
val zhttp = "2.0.0-RC10"
1818
val zioInteropCats = "23.1.0.5"
19-
val sttp = "3.11.0"
19+
val sttp = "4.0.13"
2020
val scalaTest = "3.2.19"
2121
val scalaMockScalaTest = "7.4.0"
2222
val scalaLogging = "3.9.5"
@@ -38,14 +38,15 @@ object library {
3838
val akkaTestkit = mvn"com.typesafe.akka::akka-testkit::${Version.akkaTestkit}"
3939
val akkaActor = mvn"com.typesafe.akka::akka-actor::${Version.akkaActor}"
4040
val akkaStream = mvn"com.typesafe.akka::akka-stream::${Version.akkaStream}"
41+
4142
val asyncHttpClientBackendCats =
42-
mvn"com.softwaremill.sttp.client3::async-http-client-backend-cats-ce2::${Version.sttp}"
43+
mvn"com.softwaremill.sttp.client4::fs2ce2::${Version.sttp}"
4344
val asyncHttpClientBackendCats3 =
44-
mvn"com.softwaremill.sttp.client3::async-http-client-backend-cats::${Version.sttp}"
45+
mvn"com.softwaremill.sttp.client4::cats::${Version.sttp}"
4546
val asyncHttpClientBackendZio =
46-
mvn"com.softwaremill.sttp.client3::async-http-client-backend-zio::${Version.sttp}"
47+
mvn"com.softwaremill.sttp.client4::zio::${Version.sttp}"
4748

48-
val asyncHttpClientBackendMonix = mvn"com.softwaremill.sttp.client3::async-http-client-backend-monix::${Version.sttp}"
49+
val asyncHttpClientBackendMonix = mvn"com.softwaremill.sttp.client4::monix::${Version.sttp}"
4950
val scalajHttp = mvn"org.scalaj::scalaj-http::${Version.scalajHttp}"
5051
val scalaLogging = mvn"com.typesafe.scala-logging::scala-logging::${Version.scalaLogging}"
5152
val scalaMockScalaTest = mvn"org.scalamock::scalamock::${Version.scalaMockScalaTest}"
@@ -64,9 +65,9 @@ object library {
6465
val catsEffect = mvn"org.typelevel::cats-effect::${Version.catsEffect}"
6566
val catsEffect3 = mvn"org.typelevel::cats-effect::${Version.catsEffect3}"
6667
val monix = mvn"io.monix::monix::${Version.monix}"
67-
val sttpCore = mvn"com.softwaremill.sttp.client3::core::${Version.sttp}"
68-
val sttpCirce = mvn"com.softwaremill.sttp.client3::circe::${Version.sttp}"
69-
val sttpOkHttp = mvn"com.softwaremill.sttp.client3::okhttp-backend::${Version.sttp}"
68+
val sttpCore = mvn"com.softwaremill.sttp.client4::core::${Version.sttp}"
69+
val sttpCirce = mvn"com.softwaremill.sttp.client4::circe::${Version.sttp}"
70+
val sttpOkHttp = mvn"com.softwaremill.sttp.client4::okhttp-backend::${Version.sttp}"
7071
val hammock = mvn"com.pepegar::hammock-core::${Version.hammock}"
7172
val zio = mvn"dev.zio::zio::${Version.zio}"
7273
val zhttp = mvn"io.d11::zhttp::${Version.zhttp}"

core/src/com/bot4s/telegram/cats/TelegramBot.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@ package com.bot4s.telegram.cats
33
import cats.MonadError
44
import com.bot4s.telegram.api.BotBase
55
import com.bot4s.telegram.clients.SttpClient
6-
import sttp.client3.SttpBackend
6+
import sttp.client4.Backend
77

88
class TelegramBot[F[_]](
99
token: String,
10-
backend: SttpBackend[F, Any],
10+
backend: Backend[F],
1111
telegramHost: String = "api.telegram.org"
1212
)(implicit monadError: MonadError[F, Throwable])
1313
extends BotBase[F] {
1414

1515
override val monad = monadError
1616

17-
implicit private val b: SttpBackend[F, Any] = backend
18-
val client = new SttpClient[F](token, telegramHost)
17+
implicit private val b: Backend[F] = backend
18+
val client = new SttpClient[F](token, telegramHost)
1919
}

core/src/com/bot4s/telegram/clients/FutureSttpClient.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import cats.instances.future._
44

55
import scala.concurrent.ExecutionContext
66
import scala.concurrent.Future
7-
import sttp.client3.SttpBackend
7+
import sttp.client4.Backend
88

99
class FutureSttpClient(token: String, telegramHost: String = "api.telegram.org")(implicit
10-
backend: SttpBackend[Future, Any],
10+
backend: Backend[Future],
1111
ec: ExecutionContext
1212
) extends SttpClient[Future](token, telegramHost)

core/src/com/bot4s/telegram/clients/SttpClient.scala

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@ import io.circe.{ Decoder, Encoder }
1313
import com.typesafe.scalalogging.StrictLogging
1414

1515
import scala.concurrent.duration._
16-
import sttp.client3._
16+
import sttp.client4._
1717

18-
import sttp.client3.ResponseAs
18+
import sttp.client4.ResponseAs
1919
import sttp.model.MediaType
20-
import sttp.client3.BodySerializer
21-
import sttp.client3.StringBody
22-
import sttp.client3.{ Request => SttpRequest }
20+
import sttp.client4.StringBody
21+
import sttp.client4.{ Request => SttpRequest }
2322

2423
/**
2524
* Sttp HTTP client.
@@ -28,17 +27,17 @@ import sttp.client3.{ Request => SttpRequest }
2827
* @param token Bot token
2928
*/
3029
class SttpClient[F[_]](token: String, telegramHost: String = "api.telegram.org")(implicit
31-
backend: SttpBackend[F, Any],
30+
backend: Backend[F],
3231
monadError: MonadError[F, Throwable]
3332
) extends RequestHandler[F]()(using monadError)
3433
with StrictLogging {
3534

3635
val readTimeout: Duration = 50.seconds
3736

38-
private implicit def circeBodySerializer[B: Encoder]: BodySerializer[B] =
39-
b => StringBody(marshalling.toJson[B](b), "utf-8", MediaType.ApplicationJson)
37+
private def asJson[B: Encoder](b: B): StringBody =
38+
StringBody(marshalling.toJson[B](b), "utf-8", MediaType.ApplicationJson)
4039

41-
private def asJson[B: Decoder]: ResponseAs[B, Any] =
40+
private def asJson[B: Decoder]: ResponseAs[B] =
4241
asStringAlways("utf-8").map(s => marshalling.fromJson[B](s))
4342

4443
private val apiBaseUrl = s"https://$telegramHost/bot$token/"
@@ -48,9 +47,9 @@ class SttpClient[F[_]](token: String, telegramHost: String = "api.telegram.org")
4847
)(implicit d: Decoder[request.Response]): F[request.Response] = {
4948
val url = apiBaseUrl + request.methodName
5049

51-
val sttpRequest: Either[IllegalArgumentException, SttpRequest[String, Any]] = request match {
50+
val sttpRequest: Either[IllegalArgumentException, SttpRequest[String]] = request match {
5251
case r: JsonRequest =>
53-
Right(quickRequest.post(uri"$url").body(request))
52+
Right(quickRequest.post(uri"$url").body(asJson(request)))
5453

5554
case r: MultipartRequest =>
5655
val parts = r.getFiles.flatMap { case (camelKey, inputFile) =>

core/test/src/com/bot4s/telegram/clients/SttpClientSuite.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.bot4s.telegram.clients
22

33
import org.scalatest.flatspec.AsyncFlatSpec
4-
import sttp.client3.testing.SttpBackendStub
4+
import sttp.client4.testing.BackendStub
55
import com.bot4s.telegram.methods.GetMe
66
import scala.concurrent.ExecutionContext
77
import io.circe.ParsingFailure
@@ -12,7 +12,7 @@ class SttpClientSuite extends AsyncFlatSpec {
1212
behavior of "STTP client"
1313

1414
it should "fail with a ParsingFailure in case of server error" in {
15-
val backend = SttpBackendStub.asynchronousFuture
15+
val backend = BackendStub.asynchronousFuture
1616
.whenRequestMatches(_.uri.path.contains("GetMe"))
1717
.thenRespondServerError()
1818
val client = new FutureSttpClient("")(backend, implicitly[ExecutionContext])
@@ -23,9 +23,9 @@ class SttpClientSuite extends AsyncFlatSpec {
2323
}
2424

2525
it should "fail with a TelegramApiException the API returned an error" in {
26-
val backend = SttpBackendStub.asynchronousFuture
26+
val backend = BackendStub.asynchronousFuture
2727
.whenRequestMatches(_.uri.path.contains("GetMe"))
28-
.thenRespond("""{"ok":false,"error_code":401,"description":"Unauthorized"}""")
28+
.thenRespondAdjust("""{"ok":false,"error_code":401,"description":"Unauthorized"}""")
2929
val client = new FutureSttpClient("")(backend, implicitly[ExecutionContext])
3030

3131
recoverToSucceededIf[TelegramApiException] {

examples/src-cats/CommandsBot.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import com.bot4s.telegram.cats.Polling
1010

1111
import scala.concurrent.duration._
1212
import scala.util.Try
13+
import sttp.client4.Backend
14+
import com.bot4s.telegram.cats.TelegramBot
1315

1416
/**
1517
* Showcases different ways to declare commands (Commands + RegexCommands).
@@ -18,8 +20,8 @@ import scala.util.Try
1820
*
1921
* @param token Bot's token.
2022
*/
21-
class CommandsBot[F[_]: Concurrent: Timer: ContextShift](token: String)
22-
extends ExampleBot[F](token)
23+
class CommandsBot[F[_]: Concurrent: Timer: ContextShift](token: String, backend: Backend[F])
24+
extends TelegramBot[F](token, backend)
2325
with Polling[F]
2426
with Commands[F]
2527
with RegexCommands[F] {

examples/src-cats/EchoBot.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ import cats.syntax.functor._
44
import com.bot4s.telegram.cats.Polling
55
import com.bot4s.telegram.methods._
66
import com.bot4s.telegram.models._
7+
import com.bot4s.telegram.cats.TelegramBot
8+
import sttp.client4.Backend
79

8-
class EchoBot[F[_]: Concurrent: ContextShift](token: String) extends ExampleBot[F](token) with Polling[F] {
10+
class EchoBot[F[_]: Concurrent: ContextShift](token: String, backend: Backend[F])
11+
extends TelegramBot[F](token, backend)
12+
with Polling[F] {
913

1014
override def receiveMessage(msg: Message): F[Unit] =
1115
msg.text.fold(unit) { text =>

examples/src-cats/ExampleBot.scala

Lines changed: 0 additions & 7 deletions
This file was deleted.

examples/src-cats/Launcher.scala

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
11
import cats.effect.ExitCode
22
import cats.effect.IO
33
import cats.effect.IOApp
4+
import sttp.client4.httpclient.fs2.HttpClientFs2Backend
5+
import cats.effect.Blocker
46

57
object Launcher extends IOApp {
68

79
def run(args: List[String]): IO[ExitCode] =
8-
args match {
9-
case List("EchoBot", token) =>
10-
new EchoBot[IO](token).startPolling().map(_ => ExitCode.Success)
11-
case List("CommandsBot", token) =>
12-
new CommandsBot[IO](token).startPolling().map(_ => ExitCode.Success)
13-
case List(name, _) =>
14-
IO.raiseError(new Exception(s"Unknown bot $name"))
15-
case _ =>
16-
IO.raiseError(new Exception("Usage:\nLauncher $botName $token"))
10+
Blocker[IO].use { blocker =>
11+
args match {
12+
case List("EchoBot", token) =>
13+
HttpClientFs2Backend
14+
.resource[IO](blocker)
15+
.use(backend => new EchoBot[IO](token, backend).startPolling())
16+
.as(ExitCode.Success)
17+
case List("CommandsBot", token) =>
18+
HttpClientFs2Backend
19+
.resource[IO](blocker)
20+
.use(backend => new CommandsBot[IO](token, backend).startPolling())
21+
.as(ExitCode.Success)
22+
case List(name, _) =>
23+
IO.raiseError(new Exception(s"Unknown bot $name"))
24+
case _ =>
25+
IO.raiseError(new Exception("Usage:\nLauncher $botName $token"))
26+
}
1727
}
1828
}

examples/src-cats3/CommandsBot.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import com.bot4s.telegram.cats.Polling
1010

1111
import scala.concurrent.duration._
1212
import scala.util.Try
13+
import com.bot4s.telegram.cats.TelegramBot
14+
import sttp.client4.Backend
1315

1416
/**
1517
* Showcases different ways to declare commands (Commands + RegexCommands).
@@ -18,8 +20,8 @@ import scala.util.Try
1820
*
1921
* @param token Bot's token.
2022
*/
21-
class CommandsBot[F[_]: Async: Temporal](token: String)
22-
extends ExampleBot[F](token)
23+
class CommandsBot[F[_]: Async: Temporal](token: String, backend: Backend[F])
24+
extends TelegramBot[F](token, backend)
2325
with Polling[F]
2426
with Commands[F]
2527
with RegexCommands[F] {

0 commit comments

Comments
 (0)