diff --git a/build.sbt b/build.sbt index 32b5f07add..6cf1a3e997 100644 --- a/build.sbt +++ b/build.sbt @@ -10,8 +10,14 @@ import com.softwaremill.SbtSoftwareMillBrowserTestJS._ val scala2_12 = "2.12.20" val scala2_13 = "2.13.15" +val scala3 = "3.3.4" + val scala2 = List(scala2_12, scala2_13) -val scala3 = List("3.3.4") +val scala2And3 = scala2 ++ List(scala3) + +val examplesScalaVersion = scala3 +val documentationScalaVersion = scala3 +val ideScalaVersion = scala3 lazy val testServerPort = settingKey[Int]("Port to run the http test server on") lazy val startTestServer = taskKey[Unit]("Start a http server used by tests") @@ -34,18 +40,17 @@ val commonSettings = commonSmlBuildSettings ++ ossPublishSettings ++ Seq( updateDocs := Def.taskDyn { val files1 = UpdateVersionInDocs(sLog.value, organization.value, version.value, List(file("README.md"))) Def.task { - (docs.jvm(scala2_13) / mdoc).toTask("").value + (docs.jvm(scala3) / mdoc).toTask("").value files1 ++ Seq(file("generated-docs/out")) } }.value, - ideSkipProject := (scalaVersion.value != scala2_13) + ideSkipProject := (scalaVersion.value != ideScalaVersion) || thisProjectRef.value.project.contains("JS") || thisProjectRef.value.project.contains("Native"), bspEnabled := !ideSkipProject.value, mimaPreviousArtifacts := Set.empty // we only use MiMa for `core` for now, using enableMimaSettings ) val commonJvmSettings = commonSettings ++ Seq( - scalacOptions += "-release:11", Test / testOptions += Tests.Argument("-oD") // add test timings; js build specify other options which conflict ) @@ -150,7 +155,7 @@ val zio2Version = "2.1.13" val zio1InteropRsVersion = "1.3.12" val zio2InteropRsVersion = "2.0.2" -val oxVersion = "0.5.1" +val oxVersion = "0.5.7" val sttpModelVersion = "1.7.11" val sttpSharedVersion = "1.4.2" @@ -169,7 +174,7 @@ val openTelemetryVersion = "1.45.0" val compileAndTest = "compile->compile;test->test" -lazy val loomProjects: Seq[String] = Seq(ox, examples3).flatMap(_.projectRefs).flatMap(projectId) +lazy val loomProjects: Seq[String] = Seq(ox, examples).flatMap(_.projectRefs).flatMap(projectId) def projectId(projectRef: ProjectReference): Option[String] = projectRef match { @@ -244,7 +249,6 @@ lazy val rawAllAggregates = slf4jBackend.projectRefs ++ examplesCe2.projectRefs ++ examples.projectRefs ++ - examples3.projectRefs ++ docs.projectRefs ++ testServer.projectRefs @@ -314,21 +318,21 @@ lazy val core = (projectMatrix in file("core")) ) .settings(testServerSettings) .jvmPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJvmSettings ++ versioningSchemeSettings ++ /*enableMimaSettings ++*/ List( Test / publishArtifact := true, // allow implementations outside of this repo scalacOptions ++= Seq("-J--add-modules", "-Jjava.net.http") ) ) .jsPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJsSettings ++ commonJsBackendSettings ++ browserChromeTestSettings ++ versioningSchemeSettings ++ List( Test / publishArtifact := true ) ) .nativePlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonNativeSettings ++ versioningSchemeSettings ++ List( Test / publishArtifact := true ) @@ -358,11 +362,11 @@ lazy val catsCe2 = (projectMatrix in file("effects/cats-ce2")) ) .dependsOn(core % compileAndTest) .jvmPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJvmSettings ) .jsPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJsSettings ++ commonJsBackendSettings ++ browserChromeTestSettings ++ testServerSettings ) @@ -379,11 +383,11 @@ lazy val cats = (projectMatrix in file("effects/cats")) .settings(testServerSettings) .dependsOn(core % compileAndTest) .jvmPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJvmSettings ) .jsPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJsSettings ++ commonJsBackendSettings ++ browserChromeTestSettings ++ testServerSettings ) @@ -399,7 +403,7 @@ lazy val fs2Ce2 = (projectMatrix in file("effects/fs2-ce2")) .settings(testServerSettings) .dependsOn(core % compileAndTest, catsCe2 % compileAndTest) .jvmPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJvmSettings ++ Seq( libraryDependencies ++= Seq( "co.fs2" %%% "fs2-reactive-streams" % fs2_2_version, @@ -407,7 +411,7 @@ lazy val fs2Ce2 = (projectMatrix in file("effects/fs2-ce2")) ) ) ) - .jsPlatform(scalaVersions = scala2 ++ scala3, settings = commonJsSettings) + .jsPlatform(scalaVersions = scala2And3, settings = commonJsSettings) lazy val fs2 = (projectMatrix in file("effects/fs2")) .settings( @@ -421,7 +425,7 @@ lazy val fs2 = (projectMatrix in file("effects/fs2")) .settings(testServerSettings) .dependsOn(core % compileAndTest, cats % compileAndTest) .jvmPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJvmSettings ++ Seq( libraryDependencies ++= Seq( "co.fs2" %%% "fs2-reactive-streams" % fs2_3_version, @@ -429,7 +433,7 @@ lazy val fs2 = (projectMatrix in file("effects/fs2")) ) ) ) - .jsPlatform(scalaVersions = scala2 ++ scala3, settings = commonJsSettings) + .jsPlatform(scalaVersions = scala2And3, settings = commonJsSettings) lazy val monix = (projectMatrix in file("effects/monix")) .settings( @@ -443,13 +447,13 @@ lazy val monix = (projectMatrix in file("effects/monix")) .settings(testServerSettings) .dependsOn(core % compileAndTest) .jvmPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJvmSettings ++ List( libraryDependencies ++= Seq("io.monix" %% "monix-nio" % "0.1.0") ) ) .jsPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJsSettings ++ commonJsBackendSettings ++ browserChromeTestSettings ++ testServerSettings ) @@ -462,7 +466,7 @@ lazy val ox = (projectMatrix in file("effects/ox")) ) ) .settings(testServerSettings) - .jvmPlatform(scalaVersions = scala3) + .jvmPlatform(scalaVersions = List(scala3)) .dependsOn(core % compileAndTest) lazy val zio1 = (projectMatrix in file("effects/zio1")) @@ -478,7 +482,7 @@ lazy val zio1 = (projectMatrix in file("effects/zio1")) .settings(testServerSettings) .dependsOn(core % compileAndTest) .jvmPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJvmSettings ++ Seq( libraryDependencies ++= Seq( "dev.zio" %% "zio-interop-reactivestreams" % zio1InteropRsVersion, @@ -487,7 +491,7 @@ lazy val zio1 = (projectMatrix in file("effects/zio1")) ) ) .jsPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJsSettings ++ commonJsBackendSettings ++ browserChromeTestSettings ++ testServerSettings ) @@ -504,7 +508,7 @@ lazy val zio = (projectMatrix in file("effects/zio")) .settings(testServerSettings) .dependsOn(core % compileAndTest) .jvmPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJvmSettings ++ Seq( libraryDependencies ++= Seq( "dev.zio" %% "zio-interop-reactivestreams" % zio2InteropRsVersion @@ -512,7 +516,7 @@ lazy val zio = (projectMatrix in file("effects/zio")) ) ) .jsPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJsSettings ++ commonJsBackendSettings ++ browserChromeTestSettings ++ testServerSettings ) @@ -564,7 +568,7 @@ lazy val pekkoHttpBackend = (projectMatrix in file("pekko-http-backend")) ) .dependsOn(core % compileAndTest) .jvmPlatform( - scalaVersions = scala2 ++ scala3 + scalaVersions = scala2And3 ) //-- okhttp @@ -577,19 +581,19 @@ lazy val okhttpBackend = (projectMatrix in file("okhttp-backend")) "com.squareup.okhttp3" % "okhttp" % "4.12.0" ) ) - .jvmPlatform(scalaVersions = scala2 ++ scala3) + .jvmPlatform(scalaVersions = scala2And3) .dependsOn(core % compileAndTest) -def okhttpBackendProject(proj: String, includeDotty: Boolean) = +def okhttpBackendProject(proj: String) = ProjectMatrix(s"okhttpBackend${proj.capitalize}", file(s"okhttp-backend/$proj")) .settings(commonJvmSettings) .settings(testServerSettings) .settings(name := s"okhttp-backend-$proj") - .jvmPlatform(scalaVersions = scala2 ++ (if (includeDotty) scala3 else Nil)) + .jvmPlatform(scalaVersions = scala2And3) .dependsOn(okhttpBackend % compileAndTest) lazy val okhttpMonixBackend = - okhttpBackendProject("monix", includeDotty = true) + okhttpBackendProject("monix") .dependsOn(monix % compileAndTest) //-- http4s @@ -618,7 +622,7 @@ lazy val http4sBackend = (projectMatrix in file("http4s-backend")) ), evictionErrorLevel := Level.Info ) - .jvmPlatform(scalaVersions = scala2 ++ scala3) + .jvmPlatform(scalaVersions = scala2And3) .dependsOn(cats % compileAndTest, core % compileAndTest, fs2 % compileAndTest) //-- finagle backend @@ -641,25 +645,25 @@ lazy val armeriaBackend = (projectMatrix in file("armeria-backend")) name := "armeria-backend", libraryDependencies += "com.linecorp.armeria" % "armeria" % "1.31.3" ) - .jvmPlatform(scalaVersions = scala2 ++ scala3) + .jvmPlatform(scalaVersions = scala2And3) .dependsOn(core % compileAndTest) -def armeriaBackendProject(proj: String, includeDotty: Boolean = false) = +def armeriaBackendProject(proj: String, includeScala3: Boolean = true) = ProjectMatrix(s"armeriaBackend${proj.capitalize}", file(s"armeria-backend/$proj")) .settings(commonJvmSettings) .settings(testServerSettings) .settings(name := s"armeria-backend-$proj") .dependsOn(armeriaBackend % compileAndTest) .jvmPlatform( - scalaVersions = scala2 ++ (if (includeDotty) scala3 else Nil) + scalaVersions = scala2 ++ (if (includeScala3) List(scala3) else Nil) ) lazy val armeriaMonixBackend = - armeriaBackendProject("monix", includeDotty = true) + armeriaBackendProject("monix") .dependsOn(monix % compileAndTest) lazy val armeriaFs2Ce2Backend = - armeriaBackendProject("fs2-ce2") + armeriaBackendProject("fs2-ce2", includeScala3 = false) .settings( libraryDependencies ++= Seq( "co.fs2" %% "fs2-reactive-streams" % fs2_2_version @@ -677,26 +681,26 @@ lazy val armeriaFs2Backend = .dependsOn(fs2 % compileAndTest) lazy val armeriaCatsCe2Backend = - armeriaBackendProject("cats-ce2") + armeriaBackendProject("cats-ce2", includeScala3 = false) .dependsOn(catsCe2 % compileAndTest) lazy val armeriaCatsBackend = - armeriaBackendProject("cats", includeDotty = true) + armeriaBackendProject("cats") .dependsOn(cats % compileAndTest) lazy val armeriaScalazBackend = - armeriaBackendProject("scalaz") + armeriaBackendProject("scalaz", includeScala3 = false) .dependsOn(scalaz % compileAndTest) lazy val armeriaZio1Backend = - armeriaBackendProject("zio1", includeDotty = true) + armeriaBackendProject("zio1") .settings( libraryDependencies ++= Seq("dev.zio" %% "zio-interop-reactivestreams" % zio1InteropRsVersion) ) .dependsOn(zio1 % compileAndTest) lazy val armeriaZioBackend = - armeriaBackendProject("zio", includeDotty = true) + armeriaBackendProject("zio") .settings( libraryDependencies ++= Seq("dev.zio" %% "zio-interop-reactivestreams" % zio2InteropRsVersion) ) @@ -709,11 +713,11 @@ lazy val jsonCommon = (projectMatrix in (file("json/common"))) scalaTest ) .jvmPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJvmSettings ) - .jsPlatform(scalaVersions = scala2 ++ scala3, settings = commonJsSettings) - .nativePlatform(scalaVersions = scala2 ++ scala3, settings = commonNativeSettings) + .jsPlatform(scalaVersions = scala2And3, settings = commonJsSettings) + .nativePlatform(scalaVersions = scala2And3, settings = commonNativeSettings) .dependsOn(core) lazy val circe = (projectMatrix in file("json/circe")) @@ -727,11 +731,11 @@ lazy val circe = (projectMatrix in file("json/circe")) scalaTest ) .jvmPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJvmSettings ) - .jsPlatform(scalaVersions = scala2 ++ scala3, settings = commonJsSettings) - .nativePlatform(scalaVersions = scala2 ++ scala3, settings = commonNativeSettings) + .jsPlatform(scalaVersions = scala2And3, settings = commonJsSettings) + .nativePlatform(scalaVersions = scala2And3, settings = commonNativeSettings) .dependsOn(core, jsonCommon % compileAndTest) lazy val jsoniter = (projectMatrix in file("json/jsoniter")) @@ -744,10 +748,10 @@ lazy val jsoniter = (projectMatrix in file("json/jsoniter")) scalaTest ) .jvmPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJvmSettings ) - .jsPlatform(scalaVersions = scala2 ++ scala3, settings = commonJsSettings) + .jsPlatform(scalaVersions = scala2And3, settings = commonJsSettings) .dependsOn(core, jsonCommon % compileAndTest) lazy val zioJson = (projectMatrix in file("json/zio-json")) @@ -760,10 +764,10 @@ lazy val zioJson = (projectMatrix in file("json/zio-json")) scalaTest ) .jvmPlatform( - scalaVersions = Seq(scala2_12, scala2_13) ++ scala3, + scalaVersions = scala2And3, settings = commonJvmSettings ) - .jsPlatform(scalaVersions = scala2 ++ scala3, settings = commonJsSettings) + .jsPlatform(scalaVersions = scala2And3, settings = commonJsSettings) .dependsOn(core, jsonCommon % compileAndTest) lazy val zio1Json = (projectMatrix in file("json/zio1-json")) @@ -776,10 +780,10 @@ lazy val zio1Json = (projectMatrix in file("json/zio1-json")) scalaTest ) .jvmPlatform( - scalaVersions = Seq(scala2_12, scala2_13) ++ scala3, + scalaVersions = scala2And3, settings = commonJvmSettings ) - .jsPlatform(scalaVersions = scala2 ++ scala3, settings = commonJsSettings) + .jsPlatform(scalaVersions = scala2And3, settings = commonJsSettings) .dependsOn(core, jsonCommon % compileAndTest) lazy val tethysJson = (projectMatrix in file("json/tethys-json")) @@ -793,7 +797,7 @@ lazy val tethysJson = (projectMatrix in file("json/tethys-json")) scalaTest ) .jvmPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJvmSettings ) .dependsOn(core, jsonCommon % compileAndTest) @@ -809,11 +813,11 @@ lazy val upickle = (projectMatrix in file("json/upickle")) Test / scalacOptions --= Seq("-Wconf:cat=other-match-analysis:error") ) .jvmPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJvmSettings ) - .jsPlatform(scalaVersions = scala2 ++ scala3, settings = commonJsSettings) - .nativePlatform(scalaVersions = scala2 ++ scala3, settings = commonNativeSettings) + .jsPlatform(scalaVersions = scala2And3, settings = commonJsSettings) + .nativePlatform(scalaVersions = scala2And3, settings = commonNativeSettings) .dependsOn(core, jsonCommon % compileAndTest) lazy val json4sVersion = "4.0.7" @@ -828,7 +832,7 @@ lazy val json4s = (projectMatrix in file("json/json4s")) ), scalaTest ) - .jvmPlatform(scalaVersions = scala2 ++ scala3) + .jvmPlatform(scalaVersions = scala2And3) .dependsOn(core, jsonCommon % compileAndTest) lazy val sprayJson = (projectMatrix in file("json/spray-json")) @@ -840,7 +844,7 @@ lazy val sprayJson = (projectMatrix in file("json/spray-json")) ), scalaTest ) - .jvmPlatform(scalaVersions = scala2 ++ scala3) + .jvmPlatform(scalaVersions = scala2And3) .dependsOn(core, jsonCommon % compileAndTest) lazy val play29Json = (projectMatrix in file("json/play29-json")) @@ -869,10 +873,10 @@ lazy val playJson = (projectMatrix in file("json/play-json")) scalaTest ) .jvmPlatform( - scalaVersions = scala2 ++ scala3, + scalaVersions = scala2And3, settings = commonJvmSettings ) - .jsPlatform(scalaVersions = scala2 ++ scala3, settings = commonJsSettings) + .jsPlatform(scalaVersions = scala2And3, settings = commonJsSettings) .dependsOn(core, jsonCommon % compileAndTest) lazy val prometheusBackend = (projectMatrix in file("observability/prometheus-backend")) @@ -884,7 +888,7 @@ lazy val prometheusBackend = (projectMatrix in file("observability/prometheus-ba ), scalaTest ) - .jvmPlatform(scalaVersions = scala2 ++ scala3) + .jvmPlatform(scalaVersions = scala2And3) .dependsOn(core) lazy val openTelemetryMetricsBackend = (projectMatrix in file("observability/opentelemetry-metrics-backend")) @@ -897,7 +901,7 @@ lazy val openTelemetryMetricsBackend = (projectMatrix in file("observability/ope ), scalaTest ) - .jvmPlatform(scalaVersions = scala2 ++ scala3) + .jvmPlatform(scalaVersions = scala2And3) .dependsOn(core) lazy val openTelemetryTracingZioBackend = (projectMatrix in file("observability/opentelemetry-tracing-zio-backend")) @@ -911,7 +915,7 @@ lazy val openTelemetryTracingZioBackend = (projectMatrix in file("observability/ "io.opentelemetry" % "opentelemetry-sdk-testing" % openTelemetryVersion % Test ) ) - .jvmPlatform(scalaVersions = scala2 ++ scala3) + .jvmPlatform(scalaVersions = scala2And3) .dependsOn(zio % compileAndTest) .dependsOn(core) @@ -924,9 +928,9 @@ lazy val scribeBackend = (projectMatrix in file("logging/scribe")) ), scalaTest ) - .jvmPlatform(scalaVersions = scala2 ++ scala3, settings = commonJvmSettings) - .jsPlatform(scalaVersions = scala2 ++ scala3, settings = commonJsSettings) - .nativePlatform(scalaVersions = scala2 ++ scala3, settings = commonNativeSettings) + .jvmPlatform(scalaVersions = scala2And3, settings = commonJvmSettings) + .jsPlatform(scalaVersions = scala2And3, settings = commonJsSettings) + .nativePlatform(scalaVersions = scala2And3, settings = commonNativeSettings) .dependsOn(core) lazy val slf4jBackend = (projectMatrix in file("logging/slf4j")) @@ -938,7 +942,7 @@ lazy val slf4jBackend = (projectMatrix in file("logging/slf4j")) ), scalaTest ) - .jvmPlatform(scalaVersions = scala2 ++ scala3) + .jvmPlatform(scalaVersions = scala2And3) .dependsOn(core) lazy val examplesCe2 = (projectMatrix in file("examples-ce2")) @@ -961,44 +965,28 @@ lazy val examples = (projectMatrix in file("examples")) libraryDependencies ++= Seq( "io.circe" %% "circe-generic" % circeVersion, "org.json4s" %% "json4s-native" % json4sVersion, - akkaStreams.exclude("org.scala-lang.modules", "scala-java8-compat_2.12"), pekkoStreams, logback ) ) - .jvmPlatform(scalaVersions = scala2) + .jvmPlatform(scalaVersions = List(examplesScalaVersion)) .dependsOn( core, fs2, zio, - akkaHttpBackend, pekkoHttpBackend, json4s, circe, upickle, scribeBackend, - slf4jBackend - ) - -lazy val examples3 = (projectMatrix in file("examples3")) - .settings(commonJvmSettings) - .settings( - name := "examples3", - publish / skip := true, - libraryDependencies ++= Seq( - logback - ) - ) - .jvmPlatform(scalaVersions = scala3) - .dependsOn( - core, + slf4jBackend, ox ) //TODO this should be invoked by compilation process, see #https://github.com/scalameta/mdoc/issues/355 val compileDocs: TaskKey[Unit] = taskKey[Unit]("Compiles docs module throwing away its output") compileDocs := { - (docs.jvm(scala2_13) / mdoc).toTask(" --out target/sttp-docs").value + (docs.jvm(scala3) / mdoc).toTask(" --out target/sttp-docs").value } lazy val docs: ProjectMatrix = (projectMatrix in file("generated-docs")) // important: it must not be docs/ @@ -1028,14 +1016,12 @@ lazy val docs: ProjectMatrix = (projectMatrix in file("generated-docs")) // impo "io.opentracing.brave" % "brave-opentracing" % braveOpentracingVersion, "io.zipkin.reporter2" % "zipkin-sender-okhttp3" % zipkinSenderOkHttpVersion, "io.opentelemetry" % "opentelemetry-semconv" % "1.2.0-alpha", - akkaStreams, pekkoStreams ), evictionErrorLevel := Level.Info ) .dependsOn( core % "compile->test", - akkaHttpBackend, pekkoHttpBackend, json4s, circe, @@ -1047,7 +1033,6 @@ lazy val docs: ProjectMatrix = (projectMatrix in file("generated-docs")) // impo // armeriaMonixBackend, armeriaFs2Backend, armeriaCatsBackend, - armeriaScalazBackend, okhttpBackend, // okhttpMonixBackend, http4sBackend, @@ -1056,4 +1041,4 @@ lazy val docs: ProjectMatrix = (projectMatrix in file("generated-docs")) // impo openTelemetryTracingZioBackend, slf4jBackend ) - .jvmPlatform(scalaVersions = List(scala2_13)) + .jvmPlatform(scalaVersions = List(documentationScalaVersion)) diff --git a/docs/backends/akka.md b/docs/backends/akka.md index 1bf982f6a9..a9b69f3f97 100644 --- a/docs/backends/akka.md +++ b/docs/backends/akka.md @@ -16,14 +16,14 @@ Note that you'll also need an explicit dependency on akka-streams, as akka-http Next you'll need to add create the backend instance: -```scala mdoc:compile-only +```scala import sttp.client4.akkahttp._ val backend = AkkaHttpBackend() ``` or, if you'd like to use an existing actor system: -```scala mdoc:compile-only +```scala import sttp.client4.akkahttp._ import akka.actor.ActorSystem @@ -35,7 +35,7 @@ This backend supports sending and receiving [akka-streams](http://doc.akka.io/do To set the request body as a stream: -```scala mdoc:compile-only +```scala import sttp.capabilities.akka.AkkaStreams import sttp.client4._ @@ -51,7 +51,7 @@ basicRequest To receive the response body as a stream: -```scala mdoc:compile-only +```scala import scala.concurrent.Future import sttp.capabilities.akka.AkkaStreams import sttp.client4._ @@ -79,7 +79,7 @@ That way, you can "mock" a server that the backend will talk to, without startin If your application provides a client library for its dependants to use, this is a great way to ensure that the client actually matches the routes exposed by your application: -```scala mdoc:compile-only +```scala import sttp.client4.akkahttp._ import akka.http.scaladsl.server.Route import akka.actor.ActorSystem @@ -101,7 +101,7 @@ Non-standard behavior: Received data streams can be parsed to a stream of server-sent events (SSE): -```scala mdoc:compile-only +```scala import scala.concurrent.Future import akka.stream.scaladsl.Source diff --git a/docs/backends/fs2.md b/docs/backends/fs2.md index cfd2e79e13..3758488094 100644 --- a/docs/backends/fs2.md +++ b/docs/backends/fs2.md @@ -102,7 +102,7 @@ or, if you'd like to instantiate the [WebClient](https://armeria.dev/docs/client import cats.effect.IO import cats.effect.std.Dispatcher import com.linecorp.armeria.client.WebClient -import com.linecorp.armeria.client.circuitbreaker._ +import com.linecorp.armeria.client.circuitbreaker.* import sttp.client4.armeria.fs2.ArmeriaFs2Backend val dispatcher: Dispatcher[IO] = ??? @@ -135,7 +135,7 @@ Requests can be sent with a streaming body like this: import cats.effect.IO import fs2.Stream import sttp.capabilities.fs2.Fs2Streams -import sttp.client4._ +import sttp.client4.* import sttp.client4.httpclient.fs2.HttpClientFs2Backend val effect = HttpClientFs2Backend.resource[IO]().use { backend => @@ -155,7 +155,7 @@ Responses can also be streamed: import cats.effect.IO import fs2.Stream import sttp.capabilities.fs2.Fs2Streams -import sttp.client4._ +import sttp.client4.* import sttp.client4.httpclient.fs2.HttpClientFs2Backend import scala.concurrent.duration.Duration @@ -181,9 +181,9 @@ The fs2 backends support both regular and streaming [websockets](../websockets.m Received data streams can be parsed to a stream of server-sent events (SSE): ```scala mdoc:compile-only -import cats.effect._ +import cats.effect.* import fs2.Stream -import sttp.client4._ +import sttp.client4.* import sttp.capabilities.fs2.Fs2Streams import sttp.client4.impl.fs2.Fs2ServerSentEvents import sttp.model.sse.ServerSentEvent diff --git a/docs/backends/future.md b/docs/backends/future.md index 732c296bf9..e065a90531 100644 --- a/docs/backends/future.md +++ b/docs/backends/future.md @@ -2,17 +2,18 @@ There are several backend implementations which are `scala.concurrent.Future`-based. These backends are **asynchronous**, sending a request is a non-blocking operation and results in a response wrapped in a `Future`. -Apart from the ones described below, also the [Akka](akka.md) backend is `Future`-based. +Apart from the ones described below, also the [Pekko](pekko.md) & [Akka](akka.md) backends are `Future`-based. ```eval_rst -===================================== ================================================ ========================== -Class Supported stream type Websocket support -===================================== ================================================ ========================== -``HttpClientFutureBackend`` n/a yes (regular) -``AkkaHttpBackend`` ``akka.stream.scaladsl.Source[ByteString, Any]`` yes (regular & streaming) -``OkHttpFutureBackend`` n/a yes (regular) -``ArmeriaFutureBackend`` n/a n/a -===================================== ================================================ ========================== +===================================== ================================================= ========================== +Class Supported stream type Websocket support +===================================== ================================================= ========================== +``HttpClientFutureBackend`` n/a yes (regular) +``PekkoHttpBackend`` ``pekko.stream.scaladsl.Source[ByteString, Any]`` yes (regular & streaming) +``AkkaHttpBackend`` ``akka.stream.scaladsl.Source[ByteString, Any]`` yes (regular & streaming) +``OkHttpFutureBackend`` n/a yes (regular) +``ArmeriaFutureBackend`` n/a n/a +===================================== ================================================= ========================== ``` ## Using HttpClient @@ -111,7 +112,7 @@ ArmeriaFutureBackend.usingDefaultClient() or, if you'd like to instantiate the [WebClient](https://armeria.dev/docs/client-http) yourself:: ```scala mdoc:compile-only -import com.linecorp.armeria.client.circuitbreaker._ +import com.linecorp.armeria.client.circuitbreaker.* import com.linecorp.armeria.client.WebClient // Fluently build Armeria WebClient with built-in decorators diff --git a/docs/backends/http4s.md b/docs/backends/http4s.md index 48be0edbbf..1054a61ec2 100644 --- a/docs/backends/http4s.md +++ b/docs/backends/http4s.md @@ -11,10 +11,10 @@ This backend is based on [http4s](https://http4s.org) (client) and is **asynchro The backend can be created in a couple of ways, e.g.: ```scala mdoc:compile-only -import cats.effect._ +import cats.effect.* import sttp.capabilities.fs2.Fs2Streams -import sttp.client4._ -import sttp.client4.http4s._ +import sttp.client4.* +import sttp.client4.http4s.* // the "org.http4s" %% "http4s-ember-client" % http4sVersion dependency needs to be explicitly added Http4sBackend.usingDefaultEmberClientBuilder[IO](): Resource[IO, StreamBackend[IO, Fs2Streams[IO]]] diff --git a/docs/backends/javascript/fetch.md b/docs/backends/javascript/fetch.md index c4d7d3a9a2..9417c1a5c9 100644 --- a/docs/backends/javascript/fetch.md +++ b/docs/backends/javascript/fetch.md @@ -135,8 +135,8 @@ To use, add the following dependency to your project: An example of streaming a response: ```scala -import sttp.client4._ -import sttp.client4.impl.monix._ +import sttp.client4.* +import sttp.client4.impl.monix.* import java.nio.ByteBuffer import monix.eval.Task @@ -170,7 +170,7 @@ import monix.eval.Task import sttp.capabilities.monix.MonixStreams import sttp.client4.impl.monix.MonixServerSentEvents import sttp.model.sse.ServerSentEvent -import sttp.client4._ +import sttp.client4.* def processEvents(source: Observable[ServerSentEvent]): Task[Unit] = ??? diff --git a/docs/backends/monix.md b/docs/backends/monix.md index 1462d20f05..bc28222b71 100644 --- a/docs/backends/monix.md +++ b/docs/backends/monix.md @@ -97,7 +97,7 @@ ArmeriaMonixBackend.usingDefaultClient() or, if you'd like to instantiate the [WebClient](https://armeria.dev/docs/client-http) yourself: ```scala -import com.linecorp.armeria.client.circuitbreaker._ +import com.linecorp.armeria.client.circuitbreaker.* import com.linecorp.armeria.client.WebClient // Fluently build Armeria WebClient with built-in decorators @@ -124,7 +124,7 @@ The Monix backends support streaming. The streams capability is represented as ` ```scala import sttp.capabilities.monix.MonixStreams -import sttp.client4._ +import sttp.client4.* import sttp.client4.httpclient.monix.HttpClientMonixBackend import monix.reactive.Observable @@ -143,7 +143,7 @@ And receive responses as an observable stream: ```scala import sttp.capabilities.monix.MonixStreams -import sttp.client4._ +import sttp.client4.* import sttp.client4.httpclient.monix.HttpClientMonixBackend import monix.eval.Task @@ -176,7 +176,7 @@ import monix.eval.Task import sttp.capabilities.monix.MonixStreams import sttp.client4.impl.monix.MonixServerSentEvents import sttp.model.sse.ServerSentEvent -import sttp.client4._ +import sttp.client4.* def processEvents(source: Observable[ServerSentEvent]): Task[Unit] = ??? diff --git a/docs/backends/pekko.md b/docs/backends/pekko.md index 7e0dec61d5..fc7aba5bc8 100644 --- a/docs/backends/pekko.md +++ b/docs/backends/pekko.md @@ -17,14 +17,14 @@ Note that you'll also need an explicit dependency on pekko-streams, as pekko-htt Next you'll need to add create the backend instance: ```scala mdoc:compile-only -import sttp.client4.pekkohttp._ +import sttp.client4.pekkohttp.* val backend = PekkoHttpBackend() ``` or, if you'd like to use an existing actor system: ```scala mdoc:compile-only -import sttp.client4.pekkohttp._ +import sttp.client4.pekkohttp.* import org.apache.pekko.actor.ActorSystem val actorSystem: ActorSystem = ??? @@ -37,7 +37,7 @@ To set the request body as a stream: ```scala mdoc:compile-only import sttp.capabilities.pekko.PekkoStreams -import sttp.client4._ +import sttp.client4.* import org.apache.pekko import pekko.stream.scaladsl.Source @@ -55,7 +55,7 @@ To receive the response body as a stream: ```scala mdoc:compile-only import scala.concurrent.Future import sttp.capabilities.pekko.PekkoStreams -import sttp.client4._ +import sttp.client4.* import sttp.client4.pekkohttp.PekkoHttpBackend import org.apache.pekko @@ -82,7 +82,7 @@ That way, you can "mock" a server that the backend will talk to, without startin If your application provides a client library for its dependants to use, this is a great way to ensure that the client actually matches the routes exposed by your application: ```scala mdoc:compile-only -import sttp.client4.pekkohttp._ +import sttp.client4.pekkohttp.* import org.apache.pekko import pekko.http.scaladsl.server.Route import pekko.actor.ActorSystem diff --git a/docs/backends/scalaz.md b/docs/backends/scalaz.md index e4ca9606fe..36dfaf7f66 100644 --- a/docs/backends/scalaz.md +++ b/docs/backends/scalaz.md @@ -13,13 +13,13 @@ To use, add the following dependency to your project: add imports: -```scala mdoc:silent +```scala import sttp.client4.armeria.scalaz.ArmeriaScalazBackend ``` create client: -```scala mdoc:compile-only +```scala val backend = ArmeriaScalazBackend() // You can use the default client which reuses the connection pool of ClientFactory.ofDefault() @@ -28,7 +28,7 @@ ArmeriaScalazBackend.usingDefaultClient() or, if you'd like to instantiate the [WebClient](https://armeria.dev/docs/client-http) yourself: -```scala mdoc:compile-only +```scala import com.linecorp.armeria.client.circuitbreaker._ import com.linecorp.armeria.client.WebClient diff --git a/docs/backends/synchronous.md b/docs/backends/synchronous.md index 16e94dad64..a82859670a 100644 --- a/docs/backends/synchronous.md +++ b/docs/backends/synchronous.md @@ -77,7 +77,7 @@ or, if you'd like to instantiate the OkHttpClient yourself: ```scala mdoc:compile-only import sttp.client4.okhttp.OkHttpSyncBackend -import okhttp3._ +import okhttp3.* val okHttpClient: OkHttpClient = ??? val backend = OkHttpSyncBackend.usingClient(okHttpClient) diff --git a/docs/backends/wrappers/custom.md b/docs/backends/wrappers/custom.md index 7c80f0fc0c..9e5dffeb97 100644 --- a/docs/backends/wrappers/custom.md +++ b/docs/backends/wrappers/custom.md @@ -44,21 +44,19 @@ metrics for completed requests and wraps any `Future`-based backend: ```scala mdoc:compile-only import sttp.attributes.AttributeKey import sttp.capabilities.Effect -import sttp.client4._ -import sttp.client4.akkahttp._ +import sttp.client4.* +import sttp.client4.pekkohttp.* import sttp.client4.wrappers.DelegateBackend import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global -import scala.util._ +import scala.util.* // the metrics infrastructure -trait MetricsServer { +trait MetricsServer: def reportDuration(name: String, duration: Long): Unit -} -class CloudMetricsServer extends MetricsServer { +class CloudMetricsServer extends MetricsServer: override def reportDuration(name: String, duration: Long): Unit = ??? -} case class MetricPrefix(prefix: String) val MetricPrefixAttributeKey = AttributeKey[MetricPrefix] @@ -66,35 +64,30 @@ val MetricPrefixAttributeKey = AttributeKey[MetricPrefix] // the backend wrapper abstract class MetricWrapper[P](delegate: GenericBackend[Future, P], metrics: MetricsServer) - extends DelegateBackend(delegate) { + extends DelegateBackend(delegate): - override def send[T](request: GenericRequest[T, P with Effect[Future]]): Future[Response[T]] = { + override def send[T](request: GenericRequest[T, P with Effect[Future]]): Future[Response[T]] = val start = System.currentTimeMillis() - def report(metricSuffix: String): Unit = { + def report(metricSuffix: String): Unit = val metricPrefix = request.attribute(MetricPrefixAttributeKey).getOrElse(MetricPrefix("?")) val end = System.currentTimeMillis() - metrics.reportDuration(metricPrefix + "-" + metricSuffix, end - start) - } + metrics.reportDuration(metricPrefix.prefix + "-" + metricSuffix, end - start) - delegate.send(request).andThen { + delegate.send(request).andThen: case Success(response) if response.is200 => report("ok") case Success(response) => report("notok") case Failure(t) => report("exception") - } - } -} -object MetricWrapper { +object MetricWrapper: def apply[S]( backend: WebSocketStreamBackend[Future, S], metrics: MetricsServer ): WebSocketStreamBackend[Future, S] = new MetricWrapper(backend, metrics) with WebSocketStreamBackend[Future, S] {} -} // example usage -val backend = MetricWrapper(AkkaHttpBackend(), new CloudMetricsServer()) +val backend = MetricWrapper(PekkoHttpBackend(), new CloudMetricsServer()) basicRequest .get(uri"http://company.com/api/service1") @@ -116,41 +109,33 @@ In some cases it's possible to implement a generic retry mechanism; such a mecha ```scala mdoc:compile-only import sttp.capabilities.Effect -import sttp.client4._ +import sttp.client4.* import sttp.client4.wrappers.DelegateBackend class RetryingBackend[F[_], P]( delegate: GenericBackend[F, P], shouldRetry: RetryWhen, maxRetries: Int) - extends DelegateBackend(delegate) { - - override def send[T](request: GenericRequest[T, P with Effect[F]]): F[Response[T]] = { + extends DelegateBackend(delegate): + override def send[T](request: GenericRequest[T, P with Effect[F]]): F[Response[T]] = sendWithRetryCounter(request, 0) - } private def sendWithRetryCounter[T]( - request: GenericRequest[T, P with Effect[F]], retries: Int): F[Response[T]] = { + request: GenericRequest[T, P with Effect[F]], retries: Int): F[Response[T]] = - val r = monad.handleError(delegate.send(request)) { + val r = monad.handleError(delegate.send(request)): case t if shouldRetry(request, Left(t)) && retries < maxRetries => sendWithRetryCounter(request, retries + 1) - } - monad.flatMap(r) { resp => - if (shouldRetry(request, Right(resp)) && retries < maxRetries) { + monad.flatMap(r): resp => + if shouldRetry(request, Right(resp)) && retries < maxRetries then sendWithRetryCounter(request, retries + 1) - } else { + else monad.unit(resp) - } - } - } -} -object RetryingBackend { +object RetryingBackend: def apply[F[_]](backend: WebSocketBackend[F], shouldRetry: RetryWhen, maxRetries: Int): WebSocketBackend[F] = new RetryingBackend(backend, shouldRetry, maxRetries) with WebSocketBackend[F] {} -} ``` ## Example backend with circuit breaker @@ -171,14 +156,12 @@ import java.util.concurrent.TimeUnit class CircuitSttpBackend[F[_], P]( circuitBreaker: CircuitBreaker, - delegate: GenericBackend[F, P]) extends DelegateBackend(delegate) { + delegate: GenericBackend[F, P]) extends DelegateBackend(delegate): - override def send[T](request: GenericRequest[T, P with Effect[F]]): F[Response[T]] = { + override def send[T](request: GenericRequest[T, P with Effect[F]]): F[Response[T]] = CircuitSttpBackend.decorateF(circuitBreaker, delegate.send(request)) - } -} -object CircuitSttpBackend { +object CircuitSttpBackend: def apply[F[_]](circuitBreaker: CircuitBreaker, backend: Backend[F]): Backend[F] = new CircuitSttpBackend(circuitBreaker, backend) with Backend[F] {} @@ -186,31 +169,26 @@ object CircuitSttpBackend { def decorateF[F[_], T]( circuitBreaker: CircuitBreaker, service: => F[T] - )(implicit monadError: MonadError[F]): F[T] = { - monadError.suspend { - if (!circuitBreaker.tryAcquirePermission()) { + )(implicit monadError: MonadError[F]): F[T] = + monadError.suspend: + if !circuitBreaker.tryAcquirePermission() then monadError.error(CallNotPermittedException .createCallNotPermittedException(circuitBreaker)) - } else { + else val start = System.nanoTime() - try { - monadError.handleError(monadError.map(service) { r => + try + monadError.handleError(monadError.map(service): r => circuitBreaker.onSuccess(System.nanoTime() - start, TimeUnit.NANOSECONDS) r - }) { + ) { case t => circuitBreaker.onError(System.nanoTime() - start, TimeUnit.NANOSECONDS, t) monadError.error(t) } - } catch { + catch case t: Throwable => circuitBreaker.onError(System.nanoTime() - start, TimeUnit.NANOSECONDS, t) monadError.error(t) - } - } - } - } -} ``` ## Example backend with rate limiter @@ -229,14 +207,12 @@ import sttp.client4.wrappers.DelegateBackend class RateLimitingSttpBackend[F[_], P]( rateLimiter: RateLimiter, delegate: GenericBackend[F, P] - )(implicit monadError: MonadError[F]) extends DelegateBackend(delegate) { + )(implicit monadError: MonadError[F]) extends DelegateBackend(delegate): - override def send[T](request: GenericRequest[T, P with Effect[F]]): F[Response[T]] = { + override def send[T](request: GenericRequest[T, P with Effect[F]]): F[Response[T]] = RateLimitingSttpBackend.decorateF(rateLimiter, delegate.send(request)) - } -} -object RateLimitingSttpBackend { +object RateLimitingSttpBackend: def apply[F[_], S]( rateLimiter: RateLimiter, backend: StreamBackend[F, S] @@ -246,18 +222,14 @@ object RateLimitingSttpBackend { def decorateF[F[_], T]( rateLimiter: RateLimiter, service: => F[T] - )(implicit monadError: MonadError[F]): F[T] = { - monadError.suspend { - try { + )(implicit monadError: MonadError[F]): F[T] = + monadError.suspend: + try RateLimiter.waitForPermission(rateLimiter) service - } catch { + catch case t: Throwable => monadError.error(t) - } - } - } -} ``` ## Example new backend @@ -271,15 +243,14 @@ Implementing a new backend is made easy as the tests are published in the `core` Implement your backend and extend the `HttpTest` class: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* import sttp.client4.testing.{ConvertToFuture, HttpTest} import scala.concurrent.Future -class MyCustomBackendHttpTest extends HttpTest[Future] { +class MyCustomBackendHttpTest extends HttpTest[Future]: override implicit val convertToFuture: ConvertToFuture[Future] = ConvertToFuture.future - override lazy val backend: Backend[Future] = ??? //new MyCustomBackend() + override val backend: Backend[Future] = ??? //new MyCustomBackend() override def timeoutToNone[T](t: Future[T], timeoutMillis: Int): Future[Option[T]] = ??? -} ``` ## Custom backend wrapper using cats diff --git a/docs/backends/wrappers/logging.md b/docs/backends/wrappers/logging.md index cf860d66e6..0b5d02ed06 100644 --- a/docs/backends/wrappers/logging.md +++ b/docs/backends/wrappers/logging.md @@ -36,7 +36,7 @@ There are three backend wrappers available, which log request & response informa Example usage: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* import sttp.client4.logging.slf4j.Slf4jLoggingBackend val backend = Slf4jLoggingBackend(DefaultSyncBackend()) diff --git a/docs/backends/wrappers/opentelemetry.md b/docs/backends/wrappers/opentelemetry.md index 08b87c0f7f..cfaf5e679c 100644 --- a/docs/backends/wrappers/opentelemetry.md +++ b/docs/backends/wrappers/opentelemetry.md @@ -19,8 +19,8 @@ Then an instance can be obtained as follows: ```scala mdoc:compile-only import scala.concurrent.Future -import sttp.client4._ -import sttp.client4.opentelemetry._ +import sttp.client4.* +import sttp.client4.opentelemetry.* import io.opentelemetry.api.OpenTelemetry // any effect and capabilities are supported @@ -34,8 +34,8 @@ All counters have provided default names, but the names can be customized by set ```scala mdoc:compile-only import scala.concurrent.Future -import sttp.client4._ -import sttp.client4.opentelemetry._ +import sttp.client4.* +import sttp.client4.opentelemetry.* import io.opentelemetry.api.OpenTelemetry val sttpBackend: Backend[Future] = ??? @@ -66,10 +66,10 @@ In order to do that, you need to provide the wrapper with a `Tracing` from zio-t Here's how you construct `ZioTelemetryOpenTelemetryBackend`. I would recommend wrapping this is in `ZLayer` ```scala mdoc:compile-only -import sttp.client4._ -import zio._ -import zio.telemetry.opentelemetry.tracing._ -import sttp.client4.opentelemetry.zio._ +import sttp.client4.* +import zio.* +import zio.telemetry.opentelemetry.tracing.* +import sttp.client4.opentelemetry.zio.* val zioBackend: Backend[Task] = ??? val tracing: Tracing = ??? diff --git a/docs/backends/wrappers/prometheus.md b/docs/backends/wrappers/prometheus.md index ad406df9b6..3c33a41aa1 100644 --- a/docs/backends/wrappers/prometheus.md +++ b/docs/backends/wrappers/prometheus.md @@ -9,7 +9,7 @@ To use, add the following dependency to your project: and some imports: ```scala mdoc -import sttp.client4.prometheus._ +import sttp.client4.prometheus.* ``` This backend depends on [Prometheus JVM Client](https://github.com/prometheus/client_java). Keep in mind this backend registers histograms and gathers request times, but you have to expose those metrics to [Prometheus](https://prometheus.io/). @@ -17,16 +17,16 @@ This backend depends on [Prometheus JVM Client](https://github.com/prometheus/cl The Prometheus backend wraps any other backend, for example: ```scala mdoc:compile-only -import sttp.client4.akkahttp._ -val backend = PrometheusBackend(AkkaHttpBackend()) +import sttp.client4.pekkohttp.* +val backend = PrometheusBackend(PekkoHttpBackend()) ``` It gathers request execution times in `Histogram`. It uses by default `http_client_request_duration_seconds` name, defined in `PrometheusBackend.DefaultHistogramName`. It is possible to define custom histograms name by passing function mapping request to histogram name: ```scala mdoc:compile-only -import sttp.client4.akkahttp._ +import sttp.client4.pekkohttp.* val backend = PrometheusBackend( - AkkaHttpBackend(), + PekkoHttpBackend(), PrometheusConfig( requestToHistogramNameMapper = request => Some(HistogramCollectorConfig(request.uri.host.getOrElse("example.com"))) ) @@ -36,16 +36,16 @@ val backend = PrometheusBackend( You can disable request histograms by passing `None` returning function: ```scala mdoc:compile-only -import sttp.client4.akkahttp._ -val backend = PrometheusBackend(AkkaHttpBackend(), PrometheusConfig(requestToHistogramNameMapper = _ => None)) +import sttp.client4.pekkohttp.* +val backend = PrometheusBackend(PekkoHttpBackend(), PrometheusConfig(requestToHistogramNameMapper = _ => None)) ``` This backend also offers `Gauge` with currently in-progress requests number. It uses by default `http_client_requests_active` name, defined in `PrometheusBackend.DefaultRequestsActiveCounterName`. It is possible to define custom gauge name by passing function mapping request to gauge name: ```scala mdoc:compile-only -import sttp.client4.akkahttp._ +import sttp.client4.pekkohttp.* val backend = PrometheusBackend( - AkkaHttpBackend(), + PekkoHttpBackend(), PrometheusConfig( requestToInProgressGaugeNameMapper = request => Some(CollectorConfig(request.uri.host.getOrElse("example.com"))) ) @@ -55,6 +55,6 @@ val backend = PrometheusBackend( You can disable request in-progress gauges by passing `None` returning function: ```scala mdoc:compile-only -import sttp.client4.akkahttp._ -val backend = PrometheusBackend(AkkaHttpBackend(), PrometheusConfig(requestToInProgressGaugeNameMapper = _ => None)) +import sttp.client4.pekkohttp.* +val backend = PrometheusBackend(PekkoHttpBackend(), PrometheusConfig(requestToInProgressGaugeNameMapper = _ => None)) ``` diff --git a/docs/backends/zio.md b/docs/backends/zio.md index b73638e661..e50bef5eda 100644 --- a/docs/backends/zio.md +++ b/docs/backends/zio.md @@ -74,7 +74,7 @@ ArmeriaZioBackend.usingDefaultClient().flatMap { backend => ??? } or, if you'd like to instantiate the [WebClient](https://armeria.dev/docs/client-http) yourself: ```scala mdoc:compile-only -import com.linecorp.armeria.client.circuitbreaker._ +import com.linecorp.armeria.client.circuitbreaker.* import com.linecorp.armeria.client.WebClient // Fluently build Armeria WebClient with built-in decorators @@ -107,9 +107,9 @@ When using constructors to express service dependencies, ZIO layers can be used The layers can be used to provide an implementation of the `SttpBackend` dependency when creating services. For example: ```scala mdoc:compile-only -import sttp.client4._ -import sttp.client4.httpclient.zio._ -import zio._ +import sttp.client4.* +import sttp.client4.httpclient.zio.* +import zio.* class MyService(sttpBackend: Backend[Task]) { def runLogic(): Task[Response[String]] = { @@ -143,9 +143,9 @@ The lifecycle of the `SttpClient` service is described by `ZLayer`s, which can b The `SttpClient` companion object contains effect descriptions which use the `SttpClient` service from the environment to send requests or open websockets. This is different from sttp usage with other effect libraries (which require invoking `.send(backend)` on the request), but is more in line with one of the styles of using ZIO. For example: ```scala mdoc:compile-only - import sttp.client4._ - import sttp.client4.httpclient.zio._ - import zio._ + import sttp.client4.* + import sttp.client4.httpclient.zio.* + import zio.* val request = basicRequest.get(uri"https://httpbin.org/get") val sent: ZIO[SttpClient, Throwable, Response[Either[String, String]]] = @@ -162,8 +162,8 @@ Requests can be sent with a streaming body: ```scala mdoc:compile-only import sttp.capabilities.zio.ZioStreams -import sttp.client4._ -import zio.stream._ +import sttp.client4.* +import zio.stream.* import zio.Task val sttpBackend: StreamBackend[Task, ZioStreams] = ??? @@ -180,10 +180,10 @@ And receive response bodies as a stream: ```scala mdoc:compile-only import sttp.capabilities.zio.ZioStreams -import sttp.client4._ +import sttp.client4.* -import zio._ -import zio.stream._ +import zio.* +import zio.stream.* import scala.concurrent.duration.Duration @@ -214,13 +214,13 @@ A layer with the stub `SttpBackend` can be then created by simply calling `ZLaye Received data streams can be parsed to a stream of server-sent events (SSE): ```scala mdoc:compile-only -import zio._ -import zio.stream._ +import zio.* +import zio.stream.* import sttp.capabilities.zio.ZioStreams import sttp.client4.impl.zio.ZioServerSentEvents import sttp.model.sse.ServerSentEvent -import sttp.client4._ +import sttp.client4.* def processEvents(source: Stream[Throwable, ServerSentEvent]): Task[Unit] = ??? diff --git a/docs/conf/proxy.md b/docs/conf/proxy.md index dc9e11acaf..cea210b8e1 100644 --- a/docs/conf/proxy.md +++ b/docs/conf/proxy.md @@ -13,7 +13,7 @@ Settings are loaded **in given order** and the **first existing value** is being Otherwise, proxy values can be specified manually when creating a backend: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* val backend = DefaultSyncBackend( options = BackendOptions.httpProxy("some.host", 8080)) @@ -26,7 +26,7 @@ basicRequest Or in case your proxy requires authentication (supported by the JVM backends): ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* BackendOptions.httpProxy("some.host", 8080, "username", "password") ``` diff --git a/docs/conf/redirects.md b/docs/conf/redirects.md index 4e6770f0df..5565c6f824 100644 --- a/docs/conf/redirects.md +++ b/docs/conf/redirects.md @@ -5,7 +5,7 @@ By default, sttp follows redirects. If you'd like to disable following redirects, use the `followRedirects` method: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* basicRequest.followRedirects(false) ``` @@ -19,7 +19,7 @@ If a `POST` or `PUT` request is redirected, by default it will be sent unchanged To enable this behavior, use the `redirectToGet` method: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* basicRequest.redirectToGet(true) ``` @@ -33,7 +33,7 @@ Most modern http clients will, by default, strip the `Authorization` header when You can disable the stripping of all sensitive headers using the following code: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* import sttp.client4.wrappers.{FollowRedirectsBackend, FollowRedirectsConfig} val myBackend: SyncBackend = DefaultSyncBackend() @@ -48,8 +48,8 @@ val backend: SyncBackend = FollowRedirectsBackend( If you just want to disable stripping of the `Authorization` header, you can do the following: ```scala mdoc:compile-only -import sttp.client4._ -import sttp.model._ +import sttp.client4.* +import sttp.model.* import sttp.client4.wrappers.{FollowRedirectsBackend, FollowRedirectsConfig} val myBackend: SyncBackend = DefaultSyncBackend() @@ -71,26 +71,23 @@ For example: ```scala mdoc:compile-only import sttp.capabilities.Effect -import sttp.client4._ +import sttp.client4.* import sttp.client4.wrappers.FollowRedirectsBackend import sttp.monad.MonadError abstract class MyWrapper[F[_], P] private (delegate: GenericBackend[F, P]) - extends GenericBackend[F, P] { + extends GenericBackend[F, P]: def send[T](request: GenericRequest[T, P with Effect[F]]): F[Response[T]] = ??? def close(): F[Unit] = ??? def monad: MonadError[F] = ??? -} -object MyWrapper { - def apply[F[_]](delegate: Backend[F]): Backend[F] = { +object MyWrapper: + def apply[F[_]](delegate: Backend[F]): Backend[F] = // disables any other FollowRedirectsBackend-s further down the delegate chain FollowRedirectsBackend(new MyWrapper(delegate) with Backend[F] {}) - } -} ``` ### Custom URI encoding @@ -100,7 +97,7 @@ Whenever a redirect request is about to be created, the `FollowRedirectsBackend` For example: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* import sttp.client4.wrappers.{FollowRedirectsBackend, FollowRedirectsConfig} import sttp.model.Uri.QuerySegmentEncoding diff --git a/docs/conf/ssl.md b/docs/conf/ssl.md index 8d122f5b0a..322c50a7bc 100644 --- a/docs/conf/ssl.md +++ b/docs/conf/ssl.md @@ -19,13 +19,12 @@ Sample code might look like this: import java.io.FileInputStream import java.security.{KeyStore, SecureRandom} import java.security.cert.X509Certificate -import javax.net.ssl._ +import javax.net.ssl.* -val TrustAllCerts: X509TrustManager = new X509TrustManager() { +val TrustAllCerts: X509TrustManager = new X509TrustManager(): def getAcceptedIssuers: Array[X509Certificate] = Array[X509Certificate]() override def checkServerTrusted(x509Certificates: Array[X509Certificate], s: String): Unit = () override def checkClientTrusted(x509Certificates: Array[X509Certificate], s: String): Unit = () -} val ks: KeyStore = KeyStore.getInstance(KeyStore.getDefaultType) ks.load(new FileInputStream("/path/to/your_cert.p12"), "password".toCharArray) @@ -48,7 +47,7 @@ Next, based on [one way SSL example](#one-way-ssl), add `TrustManagerFactory` to ```scala mdoc:invisible import java.security.{KeyStore, SecureRandom} -import javax.net.ssl._ +import javax.net.ssl.* import java.io.FileInputStream def ks: KeyStore = ??? def ssl: SSLContext = ??? @@ -65,43 +64,62 @@ val ssl: SSLContext = SSLContext.getInstance("TLS") ssl.init(kmf.getKeyManagers, tmf.getTrustManagers, new SecureRandom) ``` +## Using HttpClient + +Backends using `HttpClient` provides factory methods accepting `HttpClient`. +In this example we are using `IO` and `HttpClientFs2Backend`. + +Using `SSLContext` from [first section](#ssl-context): + +```scala mdoc:compile-only +import cats.effect.IO +import cats.effect.kernel.Resource +import cats.effect.std.Dispatcher +import java.net.http.HttpClient +import sttp.capabilities.fs2.Fs2Streams +import sttp.client4.WebSocketStreamBackend +import sttp.client4.httpclient.fs2.HttpClientFs2Backend + +val httpClient: HttpClient = HttpClient.newBuilder().sslContext(ssl).build() +val backend: Resource[IO, WebSocketStreamBackend[IO, Fs2Streams[IO]]] = HttpClientFs2Backend.resourceUsingClient[IO](httpClient) +``` + ## Using HttpUrlConnection Using `SSLContext` from [first section](#ssl-context) define a function to customize connection. ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* import sttp.client4.httpurlconnection.HttpURLConnectionBackend import java.net.HttpURLConnection import javax.net.ssl.HttpsURLConnection def useSSL(conn: HttpURLConnection): Unit = - conn match { + conn match case https: HttpsURLConnection => https.setSSLSocketFactory(ssl.getSocketFactory) case _ => () - } val backend = HttpURLConnectionBackend(customizeConnection = useSSL) ``` It is also possible to set default `SSLContext` using `SSLContext.setDefault(ssl)`. -## Using Akka-http +## Using Pekko-http Using `SSLContext` from [first section](#ssl-context) create a `HttpsConnectionContext`. ```scala mdoc:compile-only -import akka.actor.ActorSystem -import akka.http.scaladsl.{ConnectionContext, HttpsConnectionContext} -import sttp.client4.akkahttp._ +import org.apache.pekko.actor.ActorSystem +import org.apache.pekko.http.scaladsl.{ConnectionContext, HttpsConnectionContext} +import sttp.client4.pekkohttp.* val actorSystem: ActorSystem = ActorSystem() val https: HttpsConnectionContext = ConnectionContext.httpsClient(ssl) -val backend = AkkaHttpBackend.usingActorSystem(actorSystem, customHttpsContext = Some(https)) +val backend = PekkoHttpBackend.usingActorSystem(actorSystem, customHttpsContext = Some(https)) ``` -For more information refer to [akka docs](https://doc.akka.io/docs/akka-http/current/client-side/client-https-support.html). +For more information refer to [pekko docs](https://pekko.apache.org/docs/pekko-http/current/client-side/client-https-support.html). ## Using OkHttp @@ -124,24 +142,4 @@ val client: OkHttpClient = new OkHttpClient.Builder() val backend = OkHttpFutureBackend.usingClient(client) ``` -For more information refer to [okhttp docs](https://square.github.io/okhttp/https/). - -## Using HttpClient - -Backends using `HttpClient` provides factory methods accepting `HttpClient`. -In this example we are using `IO` and `HttpClientFs2Backend`. - -Using `SSLContext` from [first section](#ssl-context): - -```scala mdoc:compile-only -import cats.effect.IO -import cats.effect.kernel.Resource -import cats.effect.std.Dispatcher -import java.net.http.HttpClient -import sttp.capabilities.fs2.Fs2Streams -import sttp.client4.WebSocketStreamBackend -import sttp.client4.httpclient.fs2.HttpClientFs2Backend - -val httpClient: HttpClient = HttpClient.newBuilder().sslContext(ssl).build() -val backend: Resource[IO, WebSocketStreamBackend[IO, Fs2Streams[IO]]] = HttpClientFs2Backend.resourceUsingClient[IO](httpClient) -``` +For more information refer to [okhttp docs](https://square.github.io/okhttp/https/). \ No newline at end of file diff --git a/docs/conf/timeouts.md b/docs/conf/timeouts.md index d4442e327a..010d8056b6 100644 --- a/docs/conf/timeouts.md +++ b/docs/conf/timeouts.md @@ -8,8 +8,8 @@ sttp supports read and connection timeouts: How to use: ```scala mdoc:compile-only -import sttp.client4._ -import scala.concurrent.duration._ +import sttp.client4.* +import scala.concurrent.duration.* // all backends provide a constructor that allows to specify backend options val backend = DefaultSyncBackend( diff --git a/docs/how.md b/docs/how.md index f889bc2744..51647ad4fa 100644 --- a/docs/how.md +++ b/docs/how.md @@ -11,7 +11,7 @@ A `RequestT` value contains both information on what to include in the request, To start describing a request, import the sttp client package and customise `basicRequest`: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* val myRequest: Request[_] = ??? // basicRequest.(...) ``` @@ -30,7 +30,7 @@ Backends manage the connection pool, thread pools for handling responses, depend For example, the following sends a synchronous request, using the default JVM backend: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* val myRequest: Request[String] = ??? val backend = DefaultSyncBackend() val response = myRequest.send(backend) diff --git a/docs/index.md b/docs/index.md index 128a5fe527..c1c6c263aa 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,7 +12,7 @@ Backend implementations include the HTTP client that is shipped with Java, as we Here's a quick example of sttp client in action: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* val query = "http language:scala" val sort: Option[String] = None diff --git a/docs/json.md b/docs/json.md index fc439da2e4..d69a814f93 100644 --- a/docs/json.md +++ b/docs/json.md @@ -16,7 +16,7 @@ The following variants of `asJson` methods are available: The type signatures vary depending on the underlying library (required implicits and error representation differs), but they obey the following pattern: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* // request bodies def asJson[B](b: B): StringBody = ??? @@ -52,8 +52,8 @@ Automatic and semi-automatic derivation of encoders is possible by using the [ci Response can be parsed into json using `asJson[T]`, provided there's an implicit `io.circe.Decoder[T]` in scope. The decoding result will be represented as either a http/deserialization error, or the parsed value. For example: ```scala mdoc:compile-only -import sttp.client4._ -import sttp.client4.circe._ +import sttp.client4.* +import sttp.client4.circe.* val backend: SyncBackend = DefaultSyncBackend() @@ -86,15 +86,17 @@ Using this module it is possible to set request bodies and read response bodies Usage example: ```scala mdoc:compile-only -import sttp.client4._ -import sttp.client4.json4s._ +import org.json4s.Formats +import org.json4s.Serialization +import sttp.client4.* +import sttp.client4.json4s.* val backend: SyncBackend = DefaultSyncBackend() val requestPayload = RequestPayload("some data") -implicit val serialization = org.json4s.native.Serialization -implicit val formats = org.json4s.DefaultFormats +given Serialization = org.json4s.native.Serialization +given Formats = org.json4s.DefaultFormats val response: Response[Either[ResponseException[String, Exception], ResponsePayload]] = basicRequest @@ -117,9 +119,9 @@ Using this module it is possible to set request bodies and read response bodies Usage example: ```scala mdoc:compile-only -import sttp.client4._ -import sttp.client4.sprayJson._ -import spray.json._ +import sttp.client4.* +import sttp.client4.sprayJson.* +import spray.json.* val backend: SyncBackend = DefaultSyncBackend() @@ -174,9 +176,9 @@ Usage example: ```scala mdoc:compile-only -import sttp.client4._ -import sttp.client4.ziojson._ -import zio.json._ +import sttp.client4.* +import sttp.client4.ziojson.* +import zio.json.* val backend: SyncBackend = DefaultSyncBackend() @@ -213,10 +215,10 @@ However, an `implicit def` has been made for `Option` and is shipped in the `Stt Usage example: ```scala mdoc:compile-only -import sttp.client4._ -import sttp.client4.jsoniter._ -import com.github.plokhotnyuk.jsoniter_scala.core._ -import com.github.plokhotnyuk.jsoniter_scala.macros._ +import sttp.client4.* +import sttp.client4.jsoniter.* +import com.github.plokhotnyuk.jsoniter_scala.core.* +import com.github.plokhotnyuk.jsoniter_scala.macros.* val backend: SyncBackend = DefaultSyncBackend() @@ -251,9 +253,9 @@ To use, add an import: `import sttp.client4.upicklejson.default._` and define an Usage example: ```scala mdoc:compile-only -import sttp.client4._ -import sttp.client4.upicklejson.default._ -import upickle.default._ +import sttp.client4.* +import sttp.client4.upicklejson.default.* +import upickle.default.* val backend: SyncBackend = DefaultSyncBackend() @@ -277,12 +279,12 @@ That's needed as the `upickle.Api` contains the `read`/`write` methods to serial For example, if you want to use the `legacy` upickle configuration, the integration might look as follows: ```scala mdoc:compile-only -import upickle.legacy._ // get access to ReadWriter type, macroRW derivation, etc. +import upickle.legacy.* // get access to ReadWriter type, macroRW derivation, etc. object legacyUpickle extends sttp.client4.upicklejson.SttpUpickleApi { override val upickleApi: upickle.legacy.type = upickle.legacy } -import legacyUpickle._ +import legacyUpickle.* // use upickle as in the above examples ``` \ No newline at end of file diff --git a/docs/model/model.md b/docs/model/model.md index 24231f53a9..9306a30079 100644 --- a/docs/model/model.md +++ b/docs/model/model.md @@ -24,14 +24,13 @@ Example with objects: import sttp.client4._ import sttp.model._ -object Example { +object Example: val request = basicRequest.header(Header.contentType(MediaType.ApplicationJson)) .get(uri"https://httpbin.org") val backend = DefaultSyncBackend() val response = request.send(backend) - if (response.code == StatusCode.Ok) println("Ok!") -} + if response.code == StatusCode.Ok then println("Ok!") ``` Example with traits: @@ -40,14 +39,13 @@ Example with traits: import sttp.client4._ import sttp.model._ -object Example extends HeaderNames with MediaTypes with StatusCodes { +object Example extends HeaderNames with MediaTypes with StatusCodes: val request = basicRequest.header(ContentType, ApplicationJson.toString) .get(uri"https://httpbin.org") val backend = DefaultSyncBackend() val response = request.send(backend) - if (response.code == Ok) println("Ok!") -} + if response.code == Ok then println("Ok!") ``` For more information see diff --git a/docs/model/uri.md b/docs/model/uri.md index 84e5d20d7c..f427b127a6 100644 --- a/docs/model/uri.md +++ b/docs/model/uri.md @@ -30,7 +30,7 @@ Any values embedded in the URI will be URL-encoded, taking into account the cont All components of the URI can be embedded from values: scheme, username/password, host, port, path, query and fragment. The embedded values won't be further parsed, except the `:` in the host part, which is commonly used to pass in both the host and port: ```scala mdoc -import sttp.client4._ +import sttp.client4.* // the embedded / is escaped println(uri"http://example.org/${"a/b"}") @@ -108,7 +108,7 @@ When sending requests using relative URIs, the [`ResolveRelativeUrisBackend`](.. A fully-featured example: ```scala mdoc:silent -import sttp.client4._ +import sttp.client4.* val secure = true val scheme = if (secure) "https" else "http" val subdomains = List("sub1", "sub2") diff --git a/docs/quickstart.md b/docs/quickstart.md index 47e371d5dd..2c36c8de01 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -49,7 +49,7 @@ This brings into scope the starting point for defining requests and some helper And that's all you need to start using sttp client! To create and send your first request, import the above, type `basicRequest.` and see where your IDE's auto-complete gets you! Here's a simple request, using the synchronous backend: ```scala -import sttp.client4._ +import sttp.client4.* val backend = DefaultSyncBackend() val response = basicRequest @@ -78,9 +78,9 @@ dependency: Your code might then look as follows: ```scala mdoc:compile-only -import sttp.client4._ -import sttp.client4.upicklejson.default._ -import upickle.default._ +import sttp.client4.* +import sttp.client4.upicklejson.default.* +import upickle.default.* val backend = DefaultSyncBackend() @@ -115,7 +115,7 @@ use slf4j, you'll need the following dependency: Then, you'll need to configure your client: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* import sttp.client4.logging.slf4j.Slf4jLoggingBackend val backend = Slf4jLoggingBackend(DefaultSyncBackend()) @@ -130,7 +130,7 @@ This backend instance is global (created on first access), can't be customised a The `send()` extension method allows sending requests using that `backend` instance: ```scala mdoc:compile-only -import sttp.client4.quick._ +import sttp.client4.quick.* quickRequest.get(uri"http://httpbin.org/ip").send() ``` diff --git a/docs/requests/authentication.md b/docs/requests/authentication.md index 3ceffbf6ab..6566e74d45 100644 --- a/docs/requests/authentication.md +++ b/docs/requests/authentication.md @@ -5,7 +5,7 @@ sttp supports basic, bearer-token based authentication and digest authentication Basic authentication, using which the username and password are encoded using Base64, can be added as follows: ```scala mdoc:silent -import sttp.client4._ +import sttp.client4.* val username = "mary" val password = "p@assword" @@ -65,10 +65,11 @@ You can use sttp with OAuth2. Looking at the [OAuth2 protocol flow](https://tool 1. (A)/(B) - Your UI needs to enable the user to authenticate. Your application will then receive a callback from the authentication server, which will include an authentication code. 2. (C)/(D) - You need to send a request to the authentication server, passing in the authentication code from step 1. You'll receive an access token in response (and optionally a refresh token). For example, if you were using GitHub as your authentication server, you'd need to take the values of `clientId` and `clientSecret` from the GitHub settings, then take the `authCode` received in step 1 above, and send a request like this: + ```scala mdoc:compile-only -import sttp.client4.circe._ -import io.circe._ -import io.circe.generic.semiauto._ +import sttp.client4.circe.* +import io.circe.* +import io.circe.generic.semiauto.* val authCode = "SplxlOBeZQQYbYS6WxSbIA" val clientId = "myClient123" diff --git a/docs/requests/basics.md b/docs/requests/basics.md index 954720c1aa..2a5be56a88 100644 --- a/docs/requests/basics.md +++ b/docs/requests/basics.md @@ -3,7 +3,7 @@ As mentioned in the [quickstart](../quickstart.md), the following import will be needed: ```scala mdoc -import sttp.client4._ +import sttp.client4.* ``` This brings into scope `basicRequest`, the starting request. This request can be customised, each time yielding a new, immutable request definition (unless a mutable body is set on the request, such as a byte array). As the request definition is immutable, it can be freely stored in values, shared across threads, and customized multiple times in various ways. diff --git a/docs/requests/body.md b/docs/requests/body.md index 364be57495..96c9f422c1 100644 --- a/docs/requests/body.md +++ b/docs/requests/body.md @@ -11,14 +11,14 @@ In its simplest form, the request's body can be set as a `String`. By default, t A `String` body can be set on a request as follows: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* basicRequest.body("Hello, world!") ``` It is also possible to use a different character encoding: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* basicRequest.body("Hello, world!", "utf-8") ``` @@ -27,7 +27,7 @@ basicRequest.body("Hello, world!", "utf-8") To set a binary-data body, the following methods are available: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* val bytes: Array[Byte] = ??? basicRequest.body(bytes) @@ -54,7 +54,7 @@ If not specified before, these methods will set the content type to `application To upload a file, simply set the request body as a `File` or `Path`: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* import java.io.File basicRequest.body(new File("data.txt")) @@ -76,7 +76,7 @@ If you set the body as a `Map[String, String]` or `Seq[(String, String)]`, it wi By default, the `UTF-8` encoding is used, but can be also specified explicitly: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* basicRequest.body(Map("k1" -> "v1")) basicRequest.body(Map("k1" -> "v1"), "utf-8") basicRequest.body("k1" -> "v1", "k2" -> "v2") @@ -92,7 +92,7 @@ types: a `String`, byte array, an input stream, etc. For example, here's how to write a custom serializer for a case class, with serializer-specific default content type: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* import sttp.model.MediaType case class Person(name: String, surname: String, age: Int) diff --git a/docs/requests/cookies.md b/docs/requests/cookies.md index 8441e91f36..08f158a709 100644 --- a/docs/requests/cookies.md +++ b/docs/requests/cookies.md @@ -7,7 +7,7 @@ Cookies are currently only available on the JVM. Cookies can also be set using the following methods: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* import sttp.model.headers.CookieWithMeta basicRequest @@ -22,7 +22,7 @@ basicRequest It is often necessary to copy cookies from a response, e.g. after a login request is sent, and a successful response with the authentication cookie received. Having an object `response: Response[_]`, cookies on a request can be copied: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* val backend = DefaultSyncBackend() val loginRequest = basicRequest @@ -37,7 +37,7 @@ basicRequest.cookies(response) Or, it's also possible to store only the `sttp.model.CookieWithMeta` objects (a sequence of which can be obtained from a response), and set the on the request: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* val backend = DefaultSyncBackend() val loginRequest = basicRequest diff --git a/docs/requests/headers.md b/docs/requests/headers.md index dd4591aade..aabeb5c7e7 100644 --- a/docs/requests/headers.md +++ b/docs/requests/headers.md @@ -3,7 +3,7 @@ Arbitrary headers can be set on the request using the `.header` method: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* basicRequest.header("User-Agent", "myapp") ``` @@ -18,8 +18,8 @@ While most headers should be set only once on a request, HTTP allows setting a h There are also variants of this method accepting a number of headers: ```scala mdoc:compile-only -import sttp.client4._ -import sttp.model._ +import sttp.client4.* +import sttp.model.* basicRequest.header(Header("k1", "v1"), onDuplicate = DuplicateHeaderBehavior.Add) basicRequest.header("k2", "v2") @@ -33,7 +33,7 @@ basicRequest.headers(Header("k9", "v9"), Header("k10", "v10"), Header("k11", "v1 For some common headers, dedicated methods are provided: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* basicRequest.contentType("application/json") basicRequest.contentType("application/json", "iso-8859-1") diff --git a/docs/requests/multipart.md b/docs/requests/multipart.md index 72edd24187..bc7312c65b 100644 --- a/docs/requests/multipart.md +++ b/docs/requests/multipart.md @@ -27,8 +27,8 @@ basicRequest.multipartBody(multipart("p1", "v1"), multipart("p2", "v2")) For example: ```scala mdoc:compile-only -import sttp.client4._ -import java.io._ +import sttp.client4.* +import java.io.* val someFile = new File("/sample/path") @@ -44,8 +44,8 @@ basicRequest.multipartBody( For each part, an optional filename can be specified, as well as a custom content type and additional headers. For example: ```scala mdoc:compile-only -import sttp.client4._ -import java.io._ +import sttp.client4.* +import java.io.* val logoFile = new File("/sample/path/logo123.jpg") val docFile = new File("/sample/path/doc123.doc") diff --git a/docs/requests/streaming.md b/docs/requests/streaming.md index 072ded46e4..674d318bfe 100644 --- a/docs/requests/streaming.md +++ b/docs/requests/streaming.md @@ -13,18 +13,18 @@ An implementation of the `Streams[S]` capability must be passed to the `.streamB For example, using the [akka-http backend](../backends/akka.md), a request with a streaming body can be defined as follows: ```scala mdoc:compile-only -import sttp.client4._ -import sttp.capabilities.akka.AkkaStreams +import sttp.client4.* +import sttp.capabilities.pekko.PekkoStreams -import akka.stream.scaladsl.Source -import akka.util.ByteString +import org.apache.pekko.stream.scaladsl.Source +import org.apache.pekko.util.ByteString val chunks = "Streaming test".getBytes("utf-8").grouped(10).to(Iterable) val source: Source[ByteString, Any] = Source.apply(chunks.toList.map(ByteString(_))) basicRequest .post(uri"...") - .streamBody(AkkaStreams)(source) + .streamBody(PekkoStreams)(source) ``` ```eval_rst diff --git a/docs/resilience.md b/docs/resilience.md index 5fc5ad8690..a8af4cba8f 100644 --- a/docs/resilience.md +++ b/docs/resilience.md @@ -16,6 +16,7 @@ Still, the input for a particular resilience model might involve both the result Here's an incomplete list of libraries which can be used to manage retries in various Scala stacks: +* for synchornous/direct-style: [ox](https://github.com/softwaremill/ox) * for `Future`: [retry](https://github.com/softwaremill/retry) * for ZIO: [schedules](https://zio.dev/reference/schedule/), [rezilience](https://github.com/svroonland/rezilience) * for Monix/cats-effect: [cats-retry](https://github.com/cb372/cats-retry) diff --git a/docs/responses/basics.md b/docs/responses/basics.md index 7bed1da569..480d3156ad 100644 --- a/docs/responses/basics.md +++ b/docs/responses/basics.md @@ -19,8 +19,8 @@ Response headers are available through the `.headers` property, which gives all Individual headers can be obtained using the methods: ```scala mdoc:silent -import sttp.model._ -import sttp.client4._ +import sttp.model.* +import sttp.client4.* val backend = DefaultSyncBackend() val request = basicRequest diff --git a/docs/responses/body.md b/docs/responses/body.md index 5d860cfd53..7be3b8e05c 100644 --- a/docs/responses/body.md +++ b/docs/responses/body.md @@ -24,7 +24,7 @@ When the above request is completely described and sent, it will result in a `Re Other possible response descriptions include: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* import java.io.File import java.nio.file.Path @@ -56,7 +56,7 @@ def asBothOption[A, B](l: ResponseAs[A], r: ResponseAs[B]): ResponseAs[(A, Optio Hence, to discard the response body, the request description should include the following: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* basicRequest.response(ignore) ``` @@ -64,8 +64,8 @@ basicRequest.response(ignore) And to save the response to a file: ```scala mdoc:compile-only -import sttp.client4._ -import java.io._ +import sttp.client4.* +import java.io.* val someFile = new File("some/path") basicRequest.response(asFile(someFile)) @@ -82,7 +82,7 @@ basicRequest.response(asFile(someFile)) Sometimes it's convenient to get a failed effect (or an exception thrown) when the response status code is not successful. In such cases, the response description can be modified using the `.orFail` combinator: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* basicRequest.response(asString.orFail): PartialRequest[String] ``` @@ -108,7 +108,7 @@ It's possible to define custom body deserializers by taking any of the built-in As an example, to read the response body as an int, the following response description can be defined (warning: this ignores the possibility of exceptions!): ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* val asInt: ResponseAs[Either[String, Int]] = asString.mapRight((_: String).toInt) @@ -120,7 +120,7 @@ basicRequest To integrate with a third-party JSON library, and always parse the response as JSON (regardless of the status code): ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* type JsonError type JsonAST @@ -141,11 +141,11 @@ Using the `fromMetadata` combinator, it's possible to dynamically specify how th A more complex case, which uses Circe for deserializing JSON, choosing to which model to deserialize to depending on the status code, can look as following: ```scala mdoc:compile-only -import sttp.client4._ -import sttp.model._ -import sttp.client4.circe._ -import io.circe._ -import io.circe.generic.auto._ +import sttp.client4.* +import sttp.model.* +import sttp.client4.circe.* +import io.circe.* +import io.circe.generic.auto.* sealed trait MyModel case class SuccessModel(name: String, age: Int) extends MyModel @@ -163,11 +163,11 @@ val myRequest: Request[Either[ResponseException[String, io.circe.Error], MyModel The above example assumes that success and error models are part of one hierarchy (`MyModel`). Sometimes http errors are modelled independently of success. In this case, we can use `asJsonEither`, which uses `asEitherDeserialized` under the covers: ```scala mdoc:compile-only -import sttp.client4._ -import sttp.model._ -import sttp.client4.circe._ -import io.circe._ -import io.circe.generic.auto._ +import sttp.client4.* +import sttp.model.* +import sttp.client4.circe.* +import io.circe.* +import io.circe.generic.auto.* case class MyModel(p1: Int) @@ -195,7 +195,7 @@ If the backend used supports non-blocking, asynchronous streaming (see "Supporte ```scala mdoc:compile-only import sttp.capabilities.{Effect, Streams} -import sttp.client4._ +import sttp.client4.* import sttp.model.ResponseMetadata def asStream[F[_], T, S](s: Streams[S])(f: s.BinaryStream => F[T]): @@ -225,22 +225,22 @@ The first two "safe" variants pass the response stream to the user-provided func The "unsafe" variants return the stream directly to the user, and then it's up to the user of the code to consume and close the stream, releasing any resources held by the HTTP connection. -For example, when using the [Akka backend](../backends/akka.md): +For example, when using the [Pekko backend](../backends/pekko.md): ```scala mdoc:compile-only -import akka.stream.scaladsl.Source -import akka.util.ByteString +import org.apache.pekko.stream.scaladsl.Source +import org.apache.pekko.util.ByteString import scala.concurrent.Future -import sttp.capabilities.akka.AkkaStreams -import sttp.client4._ -import sttp.client4.akkahttp.AkkaHttpBackend +import sttp.capabilities.pekko.PekkoStreams +import sttp.client4.* +import sttp.client4.pekkohttp.PekkoHttpBackend -val backend: StreamBackend[Future, AkkaStreams] = AkkaHttpBackend() +val backend: StreamBackend[Future, PekkoStreams] = PekkoHttpBackend() val response: Future[Response[Either[String, Source[ByteString, Any]]]] = basicRequest .post(uri"...") - .response(asStreamUnsafe(AkkaStreams)) + .response(asStreamUnsafe(PekkoStreams)) .send(backend) ``` diff --git a/docs/testing.md b/docs/testing.md index 9bd0c2156f..d7f35f44fe 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -19,9 +19,9 @@ An empty backend stub can be created using the following ways: Some code which will be reused among following examples: ```scala mdoc -import sttp.client4._ -import sttp.model._ -import sttp.client4.testing._ +import sttp.client4.* +import sttp.model.* +import sttp.client4.testing.* import java.io.File import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global @@ -210,18 +210,17 @@ If you actually want a file to be written you can set up the stub like this: ```scala mdoc:compile-only import org.apache.commons.io.FileUtils -import cats.effect._ -import sttp.client4.impl.cats.implicits._ +import cats.effect.* +import sttp.client4.impl.cats.implicits.* import sttp.monad.MonadAsyncError val sourceFile = new File("path/to/file.ext") val destinationFile = new File("path/to/file.ext") BackendStub(implicitly[MonadAsyncError[IO]]) .whenRequestMatches(_ => true) - .thenRespondF { _ => + .thenRespondF: _ => FileUtils.copyFile(sourceFile, destinationFile) IO(ResponseStub(Right(destinationFile), StatusCode.Ok, "")) - } ``` ## Delegating to another backend @@ -274,10 +273,9 @@ val webSocketStub = WebSocketStub .initialReceive( List(WebSocketFrame.text("Hello from the server!")) ) - .thenRespondS(0) { + .thenRespondS(0): case (counter, tf: WebSocketFrame.Text) => (counter + 1, List(WebSocketFrame.text(s"echo: ${tf.payload}"))) case (counter, _) => (counter, List.empty) - } backend.whenAnyRequest.thenRespond(webSocketStub) ``` diff --git a/docs/websockets.md b/docs/websockets.md index 1189112498..5aa955f8c2 100644 --- a/docs/websockets.md +++ b/docs/websockets.md @@ -4,9 +4,9 @@ One of the optional capabilities (represented as `WebSockets`) that a backend ca A websocket request will be sent instead of a regular one if the response specification includes handling the response as a websocket. Depending on the backend you are using, there are three variants of websocket response specifications: synchronous, asynchronous and streaming. To use them, add one of the following imports: -* `import sttp.client4.ws.sync._` if you are using a synchronous backend (such as `DefaultSyncBackend`), without any effect wrappers -* `import sttp.client4.ws.async._` if you are using an asynchronous backend (e.g. based on `Future`s or `IO`s) -* `import sttp.client4.ws.stream._` if you want to handle web socket messages using a non-blocking stream (e.g. `fs2.Stream` or `akka.stream.scaladsl.Source`) +* `import sttp.client4.ws.sync.*` if you are using a synchronous backend (such as `DefaultSyncBackend`), without any effect wrappers +* `import sttp.client4.ws.async.*` if you are using an asynchronous backend (e.g. based on `Future`s or `IO`s) +* `import sttp.client4.ws.stream.*` if you want to handle web socket messages using a non-blocking stream (e.g. `fs2.Stream` or `akka.stream.scaladsl.Source`) The above imports will bring into scope a number of `asWebSocket(...)` methods, giving a couple of variants of working with websockets. @@ -22,12 +22,12 @@ The `SyncWebSocket` / `WebSocket` classes also contain other methods for receivi The following response specifications which use `SyncWebSocket` are available in the `sttp.client4.ws.sync` object (the second type parameter of `WebSocketResponseAs` specifies the type returned as the response body): ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* import sttp.client4.ws.SyncWebSocket import sttp.model.ResponseMetadata import sttp.shared.Identity -// when using import sttp.client4.ws.sync._ +// when using import sttp.client4.ws.sync.* def asWebSocket[T](f: SyncWebSocket => T): WebSocketResponseAs[Identity, Either[String, T]] = ??? @@ -67,7 +67,7 @@ Another possibility is to work with websockets by providing a streaming stage, w The following response specifications are available: ```scala mdoc:compile-only -import sttp.client4._ +import sttp.client4.* import sttp.capabilities.{Streams, WebSockets} import sttp.ws.WebSocketFrame diff --git a/docs/xml.md b/docs/xml.md index 96bea7e2e1..b89388c351 100644 --- a/docs/xml.md +++ b/docs/xml.md @@ -17,26 +17,22 @@ import sttp.model.MediaType import scala.xml.{NodeSeq, XML} -trait SttpScalaxbApi { +trait SttpScalaxbApi: case class XmlElementLabel(label: String) - def asXml[B](b: B)(implicit format: CanWriteXML[B], label: XmlElementLabel): StringBody = { + def asXml[B](b: B)(implicit format: CanWriteXML[B], label: XmlElementLabel): StringBody = val nodeSeq: NodeSeq = toXML[B](obj = b, elementLabel = label.label, scope = defaultScope) StringBody(nodeSeq.toString(), "utf-8", MediaType.ApplicationXml) - } - implicit def deserializeXml[B](implicit decoder: XMLFormat[B]): String => Either[Exception, B] = { (s: String) => - try { + implicit def deserializeXml[B](implicit decoder: XMLFormat[B]): String => Either[Exception, B] = (s: String) => + try Right(fromXML[B](XML.loadString(s))) - } catch { + catch case e: Exception => Left(e) - } - } def asXml[B: XMLFormat]: ResponseAs[Either[ResponseException[String, Exception], B], Any] = asString.mapWithMetadata(ResponseAs.deserializeRightWithError(deserializeXml[B])) .showAs("either(as string, as xml)") -} ``` This would add `asXml` methods needed for serialization and deserialization. Please notice, that `fromXML`, `toXML`, `CanWriteXML`, `XMLFormat` and `defaultScope` are members of code generated by scalaxb. diff --git a/effects/ox/src/main/scala/sttp/client4/impl/ox/sse/OxServerSentEvents.scala b/effects/ox/src/main/scala/sttp/client4/impl/ox/sse/OxServerSentEvents.scala index ca5d275457..25c49304c9 100644 --- a/effects/ox/src/main/scala/sttp/client4/impl/ox/sse/OxServerSentEvents.scala +++ b/effects/ox/src/main/scala/sttp/client4/impl/ox/sse/OxServerSentEvents.scala @@ -10,7 +10,7 @@ object OxServerSentEvents: Flow .fromInputStream(is) .linesUtf8 - .mapStatefulConcat(() => List.empty[String])( + .mapStatefulConcat(List.empty[String])( (lines, str) => if str.isEmpty then (Nil, List(lines)) else (lines :+ str, Nil), onComplete = { lines => if lines.nonEmpty then Some(lines) diff --git a/effects/ox/src/main/scala/sttp/client4/impl/ox/ws/OxWebSockets.scala b/effects/ox/src/main/scala/sttp/client4/impl/ox/ws/OxWebSockets.scala index 7d25ee686b..49adaaa870 100644 --- a/effects/ox/src/main/scala/sttp/client4/impl/ox/ws/OxWebSockets.scala +++ b/effects/ox/src/main/scala/sttp/client4/impl/ox/ws/OxWebSockets.scala @@ -44,7 +44,7 @@ def asSourceAndSink(ws: SyncWebSocket, concatenateFragmented: Boolean = true)(us case _: WebSocketFrame.Close => false case ping: WebSocketFrame.Ping => requestsChannel.sendOrClosed(WebSocketFrame.Pong(ping.payload)).discard - // Keep receiving even if pong couldn't be sent due to closed request channel. We want to process + // Keep receiving ev en if pong couldn't be sent due to closed request channel. We want to process // whatever responses there are still coming from the server until it signals the end with a Close frome. true case _: WebSocketFrame.Pong => @@ -91,7 +91,7 @@ private def optionallyConcatenateFrames(f: Flow[WebSocketFrame], doConcatenate: ): Flow[WebSocketFrame] = if doConcatenate then type Accumulator = Option[Either[Array[Byte], String]] - f.mapStateful(() => None: Accumulator) { + f.mapStateful(None: Accumulator) { case (None, f: WebSocketFrame.Ping) => (None, Some(f)) case (None, f: WebSocketFrame.Pong) => (None, Some(f)) case (None, f: WebSocketFrame.Close) => (None, Some(f)) diff --git a/examples/src/main/scala/sttp/client4/examples/GetAndParseJsonAkkaHttpJson4s.scala b/examples/src/main/scala/sttp/client4/examples/GetAndParseJsonAkkaHttpJson4s.scala deleted file mode 100644 index 6c9a2c4eb6..0000000000 --- a/examples/src/main/scala/sttp/client4/examples/GetAndParseJsonAkkaHttpJson4s.scala +++ /dev/null @@ -1,31 +0,0 @@ -package sttp.client4.examples - -object GetAndParseJsonAkkaHttpJson4s extends App { - import scala.concurrent.Future - - import sttp.client4._ - import sttp.client4.akkahttp._ - import sttp.client4.json4s._ - - import scala.concurrent.ExecutionContext.Implicits.global - - case class HttpBinResponse(origin: String, headers: Map[String, String]) - - implicit val serialization = org.json4s.native.Serialization - implicit val formats = org.json4s.DefaultFormats - val request = basicRequest - .get(uri"https://httpbin.org/get") - .response(asJson[HttpBinResponse]) - - val backend: Backend[Future] = AkkaHttpBackend() - val response: Future[Response[Either[ResponseException[String, Exception], HttpBinResponse]]] = - request.send(backend) - - for { - r <- response - } { - println(s"Got response code: ${r.code}") - println(r.body) - backend.close() - } -} diff --git a/examples/src/main/scala/sttp/client4/examples/GetAndParseJsonZioCirce.scala b/examples/src/main/scala/sttp/client4/examples/GetAndParseJsonZioCirce.scala index 00f8f864d1..021bb09b96 100644 --- a/examples/src/main/scala/sttp/client4/examples/GetAndParseJsonZioCirce.scala +++ b/examples/src/main/scala/sttp/client4/examples/GetAndParseJsonZioCirce.scala @@ -1,12 +1,13 @@ package sttp.client4.examples -import io.circe.generic.auto._ -import sttp.client4._ -import sttp.client4.circe._ -import sttp.client4.httpclient.zio.{send, HttpClientZioBackend} -import zio._ +import io.circe.generic.auto.* +import sttp.client4.* +import sttp.client4.circe.* +import sttp.client4.httpclient.zio.HttpClientZioBackend +import sttp.client4.httpclient.zio.send +import zio.* -object GetAndParseJsonZioCirce extends ZIOAppDefault { +object GetAndParseJsonZioCirce extends ZIOAppDefault: override def run = { case class HttpBinResponse(origin: String, headers: Map[String, String]) @@ -15,10 +16,9 @@ object GetAndParseJsonZioCirce extends ZIOAppDefault { .get(uri"https://httpbin.org/get") .response(asJson[HttpBinResponse]) - for { + for response <- send(request) _ <- Console.printLine(s"Got response code: ${response.code}") _ <- Console.printLine(response.body.toString) - } yield () + yield () }.provideLayer(HttpClientZioBackend.layer()) -} diff --git a/examples/src/main/scala/sttp/client4/examples/GetRawResponseBodySynchronous.scala b/examples/src/main/scala/sttp/client4/examples/GetRawResponseBodySynchronous.scala index d078fef740..3b6cc81a6b 100644 --- a/examples/src/main/scala/sttp/client4/examples/GetRawResponseBodySynchronous.scala +++ b/examples/src/main/scala/sttp/client4/examples/GetRawResponseBodySynchronous.scala @@ -1,12 +1,12 @@ package sttp.client4.examples import io.circe -import io.circe.generic.auto._ -import sttp.client4._ -import sttp.client4.circe._ +import io.circe.generic.auto.* +import sttp.client4.* +import sttp.client4.circe.* import sttp.client4.httpclient.HttpClientSyncBackend -object GetRawResponseBodySynchronous extends App { +@main def getRawResponseBodySynchronous(): Unit = case class HttpBinResponse(origin: String, headers: Map[String, String]) val request = basicRequest @@ -15,7 +15,7 @@ object GetRawResponseBodySynchronous extends App { val backend: SyncBackend = HttpClientSyncBackend() - try { + try val response: Response[(Either[ResponseException[String, circe.Error], HttpBinResponse], String)] = request.send(backend) @@ -23,6 +23,4 @@ object GetRawResponseBodySynchronous extends App { println("Got response - parsed: " + parsed) println("Got response - raw: " + raw) - - } finally backend.close() -} + finally backend.close() diff --git a/examples/src/main/scala/sttp/client4/examples/LogRequestsSlf4j.scala b/examples/src/main/scala/sttp/client4/examples/LogRequestsSlf4j.scala index 34d57d5412..a14b55d1f4 100644 --- a/examples/src/main/scala/sttp/client4/examples/LogRequestsSlf4j.scala +++ b/examples/src/main/scala/sttp/client4/examples/LogRequestsSlf4j.scala @@ -1,13 +1,13 @@ package sttp.client4.examples -import io.circe.generic.auto._ -import sttp.client4._ -import sttp.client4.circe._ +import io.circe.generic.auto.* +import sttp.client4.* +import sttp.client4.circe.* import sttp.client4.httpclient.HttpClientSyncBackend -import sttp.client4.logging.slf4j.Slf4jLoggingBackend import sttp.client4.logging.LogConfig +import sttp.client4.logging.slf4j.Slf4jLoggingBackend -object LogRequestsSlf4j extends App { +@main def logRequestsSlf4j(): Unit = case class HttpBinResponse(origin: String, headers: Map[String, String]) val request = basicRequest @@ -24,8 +24,7 @@ object LogRequestsSlf4j extends App { ) ) - try { + try val response: Response[HttpBinResponse] = request.send(backend) println("Done! " + response.code) - } finally backend.close() -} + finally backend.close() diff --git a/examples/src/main/scala/sttp/client4/examples/PostFormSynchronous.scala b/examples/src/main/scala/sttp/client4/examples/PostFormSynchronous.scala index c7d4de37fc..5580a21d0c 100644 --- a/examples/src/main/scala/sttp/client4/examples/PostFormSynchronous.scala +++ b/examples/src/main/scala/sttp/client4/examples/PostFormSynchronous.scala @@ -1,10 +1,9 @@ package sttp.client4.examples +import sttp.client4.* import sttp.client4.httpclient.HttpClientSyncBackend -object PostFormSynchronous extends App { - import sttp.client4._ - +@main def postFormSynchronous(): Unit = val signup = Some("yes") val request = basicRequest @@ -18,4 +17,3 @@ object PostFormSynchronous extends App { println(response.body) println(response.headers) -} diff --git a/examples/src/main/scala/sttp/client4/examples/RetryZio.scala b/examples/src/main/scala/sttp/client4/examples/RetryZio.scala index eaa495d18a..02701fc6c1 100644 --- a/examples/src/main/scala/sttp/client4/examples/RetryZio.scala +++ b/examples/src/main/scala/sttp/client4/examples/RetryZio.scala @@ -1,13 +1,17 @@ package sttp.client4.examples -import sttp.client4._ +import sttp.client4.* import sttp.client4.httpclient.zio.HttpClientZioBackend -import zio.{durationInt, Schedule, Task, ZIO, ZIOAppDefault} +import zio.Schedule +import zio.Task +import zio.ZIO +import zio.ZIOAppDefault +import zio.durationInt -object RetryZio extends ZIOAppDefault { +object RetryZio extends ZIOAppDefault: override def run: ZIO[Any, Throwable, Response[String]] = HttpClientZioBackend() - .flatMap { backend => + .flatMap: backend => val localhostRequest = basicRequest .get(uri"http://localhost/test") .response(asStringAlways) @@ -23,5 +27,3 @@ object RetryZio extends ZIOAppDefault { .absolve sendWithRetries.ensuring(backend.close().ignore) - } -} diff --git a/examples/src/main/scala/sttp/client4/examples/StreamFs2.scala b/examples/src/main/scala/sttp/client4/examples/StreamFs2.scala index 2f536c72f9..4f8faf69e6 100644 --- a/examples/src/main/scala/sttp/client4/examples/StreamFs2.scala +++ b/examples/src/main/scala/sttp/client4/examples/StreamFs2.scala @@ -1,14 +1,16 @@ package sttp.client4.examples -import sttp.client4._ -import sttp.client4.httpclient.fs2.HttpClientFs2Backend +import cats.effect.ExitCode import cats.effect.IO -import cats.instances.string._ -import fs2.{text, Stream} +import cats.effect.IOApp +import cats.instances.string.* +import fs2.Stream +import fs2.text import sttp.capabilities.fs2.Fs2Streams +import sttp.client4.* +import sttp.client4.httpclient.fs2.HttpClientFs2Backend -object StreamFs2 extends App { - +object StreamFs2 extends IOApp: def streamRequestBody(backend: StreamBackend[IO, Fs2Streams[IO]]): IO[Unit] = { val stream: Stream[IO, Byte] = Stream.emits("Hello, world".getBytes) @@ -27,9 +29,9 @@ object StreamFs2 extends App { .send(backend) .map(response => println(s"RECEIVED:\n${response.body}")) - val effect = HttpClientFs2Backend.resource[IO]().use { backend => - streamRequestBody(backend).flatMap(_ => streamResponseBody(backend)) - } + val effect = HttpClientFs2Backend + .resource[IO]() + .use: backend => + streamRequestBody(backend).flatMap(_ => streamResponseBody(backend)) - effect.unsafeRunSync()(cats.effect.unsafe.implicits.global) -} + override def run(args: List[String]): IO[ExitCode] = effect.map(_ => ExitCode.Success) diff --git a/examples/src/main/scala/sttp/client4/examples/StreamZio.scala b/examples/src/main/scala/sttp/client4/examples/StreamZio.scala index 4645c88af8..65ba02da0a 100644 --- a/examples/src/main/scala/sttp/client4/examples/StreamZio.scala +++ b/examples/src/main/scala/sttp/client4/examples/StreamZio.scala @@ -1,21 +1,22 @@ package sttp.client4.examples import sttp.capabilities.zio.ZioStreams -import sttp.client4._ -import zio.Console._ -import zio._ -import zio.stream._ -import sttp.client4.httpclient.zio.{send, HttpClientZioBackend, SttpClient} +import sttp.client4.* +import sttp.client4.httpclient.zio.HttpClientZioBackend +import sttp.client4.httpclient.zio.SttpClient +import sttp.client4.httpclient.zio.send +import zio.* +import zio.Console.* +import zio.stream.* -object StreamZio extends ZIOAppDefault { - def streamRequestBody: RIO[SttpClient, Unit] = { +object StreamZio extends ZIOAppDefault: + def streamRequestBody: RIO[SttpClient, Unit] = val stream: Stream[Throwable, Byte] = ZStream("Hello, world".getBytes.toIndexedSeq: _*) send( basicRequest .post(uri"https://httpbin.org/post") .streamBody(ZioStreams)(stream) ).flatMap(response => printLine(s"RECEIVED:\n${response.body}")) - } def streamResponseBody: RIO[SttpClient, Unit] = send( @@ -27,4 +28,3 @@ object StreamZio extends ZIOAppDefault { override def run = (streamRequestBody *> streamResponseBody).provide(HttpClientZioBackend.layer()) -} diff --git a/examples/src/main/scala/sttp/client4/examples/TestEndpointMultipleQueryParameters.scala b/examples/src/main/scala/sttp/client4/examples/TestEndpointMultipleQueryParameters.scala index 24b1f2a85c..5ee1abfd68 100644 --- a/examples/src/main/scala/sttp/client4/examples/TestEndpointMultipleQueryParameters.scala +++ b/examples/src/main/scala/sttp/client4/examples/TestEndpointMultipleQueryParameters.scala @@ -1,9 +1,9 @@ package sttp.client4.examples -object TestEndpointMultipleQueryParameters extends App { - import sttp.client4._ - import sttp.client4.testing._ +import sttp.client4.* +import sttp.client4.testing.* +@main def testEndpointMultipleQueryParameters(): Unit = val backend = SyncBackendStub .whenRequestMatches(_.uri.paramsMap.contains("filter")) .thenRespond("Filtered") @@ -25,4 +25,3 @@ object TestEndpointMultipleQueryParameters extends App { .send(backend) .body ) -} diff --git a/examples/src/main/scala/sttp/client4/examples/WebSocketAkka.scala b/examples/src/main/scala/sttp/client4/examples/WebSocketAkka.scala deleted file mode 100644 index d23468aa9a..0000000000 --- a/examples/src/main/scala/sttp/client4/examples/WebSocketAkka.scala +++ /dev/null @@ -1,30 +0,0 @@ -package sttp.client4.examples - -import sttp.client4._ -import sttp.client4.ws.async._ -import sttp.client4.akkahttp.AkkaHttpBackend -import sttp.ws.WebSocket - -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.Future - -object WebSocketAkka extends App { - def useWebSocket(ws: WebSocket[Future]): Future[Unit] = { - def send(i: Int) = ws.sendText(s"Hello $i!") - def receive() = ws.receiveText().map(t => println(s"RECEIVED: $t")) - for { - _ <- send(1) - _ <- send(2) - _ <- receive() - _ <- receive() - } yield () - } - - val backend = AkkaHttpBackend() - - basicRequest - .get(uri"wss://ws.postman-echo.com/raw") - .response(asWebSocket(useWebSocket)) - .send(backend) - .onComplete(_ => backend.close()) -} diff --git a/examples/src/main/scala/sttp/client4/examples/WebSocketPekko.scala b/examples/src/main/scala/sttp/client4/examples/WebSocketPekko.scala index c16ff96e02..d71891334a 100644 --- a/examples/src/main/scala/sttp/client4/examples/WebSocketPekko.scala +++ b/examples/src/main/scala/sttp/client4/examples/WebSocketPekko.scala @@ -8,17 +8,16 @@ import sttp.ws.WebSocket import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future -object WebSocketPekko extends App { - def useWebSocket(ws: WebSocket[Future]): Future[Unit] = { +@main def webSocketPekko(): Unit = + def useWebSocket(ws: WebSocket[Future]): Future[Unit] = def send(i: Int) = ws.sendText(s"Hello $i!") def receive() = ws.receiveText().map(t => println(s"RECEIVED: $t")) - for { + for _ <- send(1) _ <- send(2) _ <- receive() _ <- receive() - } yield () - } + yield () val backend = PekkoHttpBackend() @@ -27,4 +26,3 @@ object WebSocketPekko extends App { .response(asWebSocket(useWebSocket)) .send(backend) .onComplete(_ => backend.close()) -} diff --git a/examples/src/main/scala/sttp/client4/examples/WebSocketStreamFs2.scala b/examples/src/main/scala/sttp/client4/examples/WebSocketStreamFs2.scala index 9e9d37007d..c2c8b19d4b 100644 --- a/examples/src/main/scala/sttp/client4/examples/WebSocketStreamFs2.scala +++ b/examples/src/main/scala/sttp/client4/examples/WebSocketStreamFs2.scala @@ -1,17 +1,16 @@ package sttp.client4.examples +import cats.effect.ExitCode import cats.effect.IO -import cats.effect.unsafe.IORuntime -import fs2._ +import cats.effect.IOApp +import fs2.* import sttp.capabilities.fs2.Fs2Streams -import sttp.client4._ -import sttp.client4.ws.stream._ +import sttp.client4.* import sttp.client4.httpclient.fs2.HttpClientFs2Backend +import sttp.client4.ws.stream.* import sttp.ws.WebSocketFrame -object WebSocketStreamFs2 extends App { - implicit val runtime: IORuntime = cats.effect.unsafe.implicits.global - +object WebSocketStreamFs2 extends IOApp: def webSocketFramePipe: Pipe[IO, WebSocketFrame.Data[_], WebSocketFrame] = { input => Stream.emit(WebSocketFrame.text("1")) ++ input.flatMap { case WebSocketFrame.Text("10", _, _) => @@ -24,7 +23,7 @@ object WebSocketStreamFs2 extends App { } } - HttpClientFs2Backend + override def run(args: List[String]): IO[ExitCode] = HttpClientFs2Backend .resource[IO]() .use { backend => basicRequest @@ -33,5 +32,4 @@ object WebSocketStreamFs2 extends App { .send(backend) .void } - .unsafeRunSync() -} + .map(_ => ExitCode.Success) diff --git a/examples/src/main/scala/sttp/client4/examples/WebSocketSynchronous.scala b/examples/src/main/scala/sttp/client4/examples/WebSocketSynchronous.scala index d3693c13e0..96e073a184 100644 --- a/examples/src/main/scala/sttp/client4/examples/WebSocketSynchronous.scala +++ b/examples/src/main/scala/sttp/client4/examples/WebSocketSynchronous.scala @@ -1,11 +1,11 @@ package sttp.client4.examples -import sttp.client4._ +import sttp.client4.* import sttp.client4.ws.SyncWebSocket -import sttp.client4.ws.sync._ +import sttp.client4.ws.sync.* -object WebSocketSynchronous extends App { - def useWebSocket(ws: SyncWebSocket): Unit = { +@main def webSocketSynchronous(): Unit = + def useWebSocket(ws: SyncWebSocket): Unit = def send(i: Int): Unit = ws.sendText(s"Hello $i!") def receive(): Unit = { val t = ws.receiveText() @@ -15,14 +15,14 @@ object WebSocketSynchronous extends App { send(2) receive() receive() - } val backend = DefaultSyncBackend() try - basicRequest - .get(uri"wss://ws.postman-echo.com/raw") - .response(asWebSocket(useWebSocket)) - .send(backend) + println( + basicRequest + .get(uri"wss://ws.postman-echo.com/raw") + .response(asWebSocket(useWebSocket)) + .send(backend) + ) finally backend.close() -} diff --git a/examples/src/main/scala/sttp/client4/examples/WebSocketZio.scala b/examples/src/main/scala/sttp/client4/examples/WebSocketZio.scala index e039d275b9..7f73036cd2 100644 --- a/examples/src/main/scala/sttp/client4/examples/WebSocketZio.scala +++ b/examples/src/main/scala/sttp/client4/examples/WebSocketZio.scala @@ -1,17 +1,17 @@ package sttp.client4.examples -import sttp.client4._ -import sttp.client4.ws.async._ +import sttp.client4.* import sttp.client4.httpclient.zio.HttpClientZioBackend +import sttp.client4.ws.async.* import sttp.ws.WebSocket -import zio.{Console, _} +import zio.* +import zio.Console -object WebSocketZio extends ZIOAppDefault { - def useWebSocket(ws: WebSocket[Task]): Task[Unit] = { +object WebSocketZio extends ZIOAppDefault: + def useWebSocket(ws: WebSocket[Task]): Task[Unit] = def send(i: Int) = ws.sendText(s"Hello $i!") val receive = ws.receiveText().flatMap(t => Console.printLine(s"RECEIVED: $t")) send(1) *> send(2) *> receive *> receive - } // create a description of a program, which requires SttpClient dependency in the environment def sendAndPrint(backend: WebSocketBackend[Task]): Task[Response[Unit]] = @@ -20,4 +20,3 @@ object WebSocketZio extends ZIOAppDefault { override def run = // provide an implementation for the SttpClient dependency HttpClientZioBackend.scoped().flatMap(sendAndPrint) -} diff --git a/examples/src/main/scala/sttp/client4/examples/getAndParseJsonPekkoHttpJson4s.scala b/examples/src/main/scala/sttp/client4/examples/getAndParseJsonPekkoHttpJson4s.scala new file mode 100644 index 0000000000..3e6e116746 --- /dev/null +++ b/examples/src/main/scala/sttp/client4/examples/getAndParseJsonPekkoHttpJson4s.scala @@ -0,0 +1,29 @@ +package sttp.client4.examples + +import org.json4s.Formats +import org.json4s.Serialization +import sttp.client4.* +import sttp.client4.json4s.* +import sttp.client4.pekkohttp.* + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.Future + +@main def getAndParseJsonPekkoHttpJson4s(): Unit = + case class HttpBinResponse(origin: String, headers: Map[String, String]) + + given Serialization = org.json4s.native.Serialization + given Formats = org.json4s.DefaultFormats + + val request = basicRequest + .get(uri"https://httpbin.org/get ") + .response(asJson[HttpBinResponse]) + + val backend: Backend[Future] = PekkoHttpBackend() + val response: Future[Response[Either[ResponseException[String, Exception], HttpBinResponse]]] = + request.send(backend) + + for r <- response do + println(s"Got response code: ${r.code}") + println(r.body) + backend.close() diff --git a/examples3/src/main/scala/sttp/client4/examples/WebSocketOx.scala b/examples/src/main/scala/sttp/client4/examples/wsOxExample.scala similarity index 97% rename from examples3/src/main/scala/sttp/client4/examples/WebSocketOx.scala rename to examples/src/main/scala/sttp/client4/examples/wsOxExample.scala index 452bfd6fea..120c690b38 100644 --- a/examples3/src/main/scala/sttp/client4/examples/WebSocketOx.scala +++ b/examples/src/main/scala/sttp/client4/examples/wsOxExample.scala @@ -13,7 +13,7 @@ import sttp.ws.WebSocketFrame supervised: val inputs = Source.fromValues(1, 2, 3).map(i => WebSocketFrame.text(s"Frame no $i")) val (wsSource, wsSink) = asSourceAndSink(ws) - fork: + forkDiscard: inputs.pipeTo(wsSink, propagateDone = true) wsSource.foreach: frame => println(s"RECEIVED: $frame")