Skip to content

Commit

Permalink
Release 4.0.0-M1
Browse files Browse the repository at this point in the history
  • Loading branch information
adamw committed Mar 14, 2023
1 parent 83d6fe9 commit aee041e
Show file tree
Hide file tree
Showing 44 changed files with 637 additions and 494 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ sttp (v2) documentation is available at [sttp.softwaremill.com/en/v2](https://st

sttp (v1) documentation is available at [sttp.softwaremill.com/en/v1](https://sttp.softwaremill.com/en/v1).

scaladoc is available at [https://www.javadoc.io](https://www.javadoc.io/doc/com.softwaremill.sttp.client4/core_2.12/3.8.13)
scaladoc is available at [https://www.javadoc.io](https://www.javadoc.io/doc/com.softwaremill.sttp.client4/core_2.12/4.0.0-M1)

## Quickstart with scala-cli

Add the following directive to the top of your scala file to add the core sttp dependency:
If you are using [scala-cli](https://scala-cli.virtuslab.org), you can quickly start experimenting with sttp by copy-pasting the following:

```
//> using lib "com.softwaremill.sttp.client4:core:3.8.13"
//> using lib "com.softwaremill.sttp.client4:core:4.0.0-M1"
import sttp.client4.quick._
quickRequest.get(uri"http://httpbin.org/ip").send()
```
Expand All @@ -69,7 +69,7 @@ The `quick` package import brings in the sttp API and a pre-configured, global s
Similarly, using [Ammonite](http://ammonite.io):

```scala
import $ivy.`com.softwaremill.sttp.client4::core:3.8.13`
import $ivy.`com.softwaremill.sttp.client4::core:4.0.0-M1`
import sttp.client4.quick._
quickRequest.get(uri"http://httpbin.org/ip").send()
```
Expand All @@ -79,7 +79,7 @@ quickRequest.get(uri"http://httpbin.org/ip").send()
Add the following dependency:

```scala
"com.softwaremill.sttp.client4" %% "core" % "3.8.13"
"com.softwaremill.sttp.client4" %% "core" % "4.0.0-M1"
```

Then, import:
Expand Down Expand Up @@ -134,7 +134,7 @@ The documentation is typechecked using [mdoc](https://scalameta.org/mdoc/). The

When generating documentation, it's best to set the version to the current one, so that the generated doc files don't include modifications with the current snapshot version.

That is, in sbt run: `set version := "3.8.13"`, before running `mdoc` in `docs`.
That is, in sbt run: `set version := "4.0.0-M1"`, before running `mdoc` in `docs`.

### Testing the Scala.JS backend

Expand Down
86 changes: 86 additions & 0 deletions generated-docs/out/adr/0003-separate-backend-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# 3. Separate backend types

Date: 2023-02-22

## Context

Sttp base types are [being refactored](https://github.com/softwaremill/sttp/pull/1703) to decrease the learning curve,
and generally improve the developer experience.

## Decision

The `SttpBackend` type is split into a type hierarchy, with new subtypes representing backends with different
effects and capabilities.

The root of the hierarchy is `GenericBackend`, which corresponds to the old backend: it is parametrised with the
type constructor used to represent effects, and a set of capabilities that the backend supports. Additionally, there's
a number of subtypes: `Backend`, `SyncBackend`, `StreamBackend`, `WebSocketBackend` and `WebSocketStreamBackend` which
fix the capabilities, and in case of `SyncBackend` also the effect type.

### What do we gain

Thanks to the different kinds of backends being represented by top-level types, the `send` methods on request
descriptions can now have more developer-friendly and precise signatures. For example:

```scala
class Request[T] {
def send[F[_]](backend: Backend[F]): F[Response[T]] = backend.send(this)
def send(backend: SyncBackend): Response[T] = backend.send(this)
}
```

Specifically, for a request sent using a synchronous request, the result type is a `Response` directly, without
the `Identity` wrapper. This improves ergonomics (e.g. when using autocomplete) and type inference.

Moreover:
* users are exposed to simpler types, such as `SyncBackend` instead of `SttpBackend[Identity, Any]`
* error messages are more precise, pointing to the specific backend type that is required to send a request

### What do we loose

Backend wrappers, which are designed to work with any delegate backend, now need to have 5 alternate constructors,
for each specific backend subtype. For example:

```scala
object FollowRedirectsBackend {
def apply(delegate: SyncBackend): SyncBackend = new FollowRedirectsBackend(delegate) with SyncBackend {}
def apply[F[_]](delegate: Backend[F]): Backend[F] = new FollowRedirectsBackend(delegate) with Backend[F] {}
def apply[F[_]](delegate: WebSocketBackend[F]): WebSocketBackend[F] = new FollowRedirectsBackend(delegate) with WebSocketBackend[F] {}
def apply[F[_], S](delegate: StreamBackend[F, S]): StreamBackend[F, S] = new FollowRedirectsBackend(delegate) with StreamBackend[F, S] {}
def apply[F[_], S](delegate: WebSocketStreamBackend[F, S]): WebSocketStreamBackend[F, S] = new FollowRedirectsBackend(delegate) with WebSocketStreamBackend[F, S] {}
}
```

Additionally, adding more capabilities will require enriching the type hierarchy with more subtypes, as well as adding
new variants to any backend wrappers. However, in the course of sttp's history, no new capabilities have been added, and
we do not foresee having to add more in the future.

### Alternate designs

We [have explored](https://github.com/adpi2/sttp/pull/5) an alternate design, where the `GenericBackend` was a
stand-alone type, to which types representing to specific capabilities (`Backend`, `StreamBackend` etc.) delegate.
This partially replaced the inheritance with composition.

The main goal of the change was to allow implementing generic backend wrappers in a more straightforward way. The
delegating types included a `SelfType` type parameter, which was leveraged by generic backend wrappers, e.g.:

```scala
trait SyncBackend extends Backend[Identity] { self =>
override type Capabilities <: Any
override type SelfType <: SyncBackend
}

object FollowRedirectsBackend {
def apply[F[_]](delegate: Backend[F]): delegate.SelfType =
delegate.wrap(new FollowRedirectsBackend(_, FollowRedirectsConfig.Default))
}
```

However, this had two major drawbacks:

* the type inference on the wrapped backends was worse, as it returned e.g. `SyncBackend.SelfType` instead of
`SyncBackend`
* alternatively, we could fix the self type to become type aliases instead of type bounds, but then the subtyping
relation between the backend types has been lost
* additionally, using two wrappers in a generic way resulted in type such as `delegate.SelfType#SelfType`, which
are not only not readable, but also rejected by the Scala 3 compiler.
47 changes: 47 additions & 0 deletions generated-docs/out/adr/0004-separate-request-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# 4. Separate request types

Date: 2023-02-23

## Context

Sttp base types are [being refactored](https://github.com/softwaremill/sttp/pull/1703) to decrease the learning curve,
and generally improve the developer experience.

## Decision

The `RequestT` type is being split into a type hierarchy, with new subtypes representing requests requiring different
capabilities.

The requests are split into request builders: top-level `PartialRequest` and traits with common methods:
`PartialRequestBuilder` and `RequestBuilder`.

Additionally, we've got a base trait for a generic request (that is a request description, which includes the uri
and method), `GenericRequest`. This is then implemented by a number of top-level classes: `Request`, `StreamRequest`,
`WebSocketRequest` and `WebSocketStreamRequest`. The capabilities supported by these requests are fixed. Setting
a response description or an input stream body promotes a `Request` or `PartialRequest` to the appropriate type.

### What do we gain

The main gain is that the types are simpler. Before, the `RequestT[U, T, R]` type had three type parameters:
a marker type constructor, specifying if the uri/method are specified; the target type, to which the response is
deserialized; and the set of required capabilities.

With the new design, the base `Request[T]` type has one type parameter only (response target type). This is expanded
to two type parameters in the other request types, to express the additional requirements.

Moreover, the specific request types clearly specify what type of `Backend` is required to send the request.

### What do we loose

It is harder to write generic methods which manipulate *any* request description (including partial request). This
is still possible, by properly parametrizing the method with the subtype of `PartialRequestBuilder` used, but might
not be as straightforward as before.

Moreover, integration with Tapir might be more challenging, as we cannot simply accept an endpoint with a set of
capabilities, and produce a request with a mirror capability set. Special-casing on streaming/websocket capabilities
will be required.

Finally, some flexibility is lost, as there are no partial streaming/websocket requests. That is, the method & uri
need to be specified on the request *before* specifying that the body should be streamed or that the response
should be a web socket one. This could be amended by introducing additional `PartialRequest` subtypes, however it is
not clear right now that it is necessary.
11 changes: 6 additions & 5 deletions generated-docs/out/backends/akka.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
This backend is based on [akka-http](http://doc.akka.io/docs/akka-http/current/scala/http/). To use, add the following dependency to your project:

```
"com.softwaremill.sttp.client4" %% "akka-http-backend" % "3.8.13"
"com.softwaremill.sttp.client4" %% "akka-http-backend" % "4.0.0-M1"
```

A fully **asynchronous** backend. Uses the `Future` effect to return responses. There are also [other `Future`-based backends](future.md), which don't depend on Akka.
Expand All @@ -18,7 +18,6 @@ Next you'll need to add create the backend instance:

```scala
import sttp.client4.akkahttp._

val backend = AkkaHttpBackend()
```

Expand Down Expand Up @@ -46,8 +45,8 @@ import akka.util.ByteString
val source: Source[ByteString, Any] = ???

basicRequest
.streamBody(AkkaStreams)(source)
.post(uri"...")
.streamBody(AkkaStreams)(source)
```

To receive the response body as a stream:
Expand Down Expand Up @@ -114,6 +113,8 @@ import sttp.client4._

def processEvents(source: Source[ServerSentEvent, Any]): Future[Unit] = ???

basicRequest.response(asStream(AkkaStreams)(stream =>
processEvents(stream.via(AkkaHttpServerSentEvents.parse))))
basicRequest
.get(uri"...")
.response(asStream(AkkaStreams)(stream =>
processEvents(stream.via(AkkaHttpServerSentEvents.parse))))
```
12 changes: 6 additions & 6 deletions generated-docs/out/backends/catseffect.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ Creation of the backend can be done in two basic ways:
Firstly, add the following dependency to your project:

```scala
"com.softwaremill.sttp.client4" %% "armeria-backend-cats" % "3.8.13" // for cats-effect 3.x
"com.softwaremill.sttp.client4" %% "armeria-backend-cats" % "4.0.0-M1" // for cats-effect 3.x
// or
"com.softwaremill.sttp.client4" %% "armeria-backend-cats-ce2" % "3.8.13" // for cats-effect 2.x
"com.softwaremill.sttp.client4" %% "armeria-backend-cats-ce2" % "4.0.0-M1" // for cats-effect 2.x
```

create client:
Expand Down Expand Up @@ -112,10 +112,10 @@ import sttp.client4.armeria.cats.ArmeriaCatsBackend

// Fluently build Armeria WebClient with built-in decorators
val client = WebClient.builder("https://my-service.com")
// Open circuit on 5xx server error status
.decorator(CircuitBreakerClient.newDecorator(CircuitBreaker.ofDefaultName(),
CircuitBreakerRule.onServerErrorStatus()))
.build()
// Open circuit on 5xx server error status
.decorator(CircuitBreakerClient.newDecorator(CircuitBreaker.ofDefaultName(),
CircuitBreakerRule.onServerErrorStatus()))
.build()

val backend = ArmeriaCatsBackend.usingClient[IO](client)
```
Expand Down
3 changes: 1 addition & 2 deletions generated-docs/out/backends/finagle.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
To use, add the following dependency to your project:

```
"com.softwaremill.sttp.client4" %% "finagle-backend" % "3.8.13"
"com.softwaremill.sttp.client4" %% "finagle-backend" % "4.0.0-M1"
```

Next you'll need to add an implicit value:

```scala
import sttp.client4.finagle.FinagleBackend

val backend = FinagleBackend()
```

Expand Down
26 changes: 14 additions & 12 deletions generated-docs/out/backends/fs2.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ Creation of the backend can be done in two basic ways:
Firstly, add the following dependency to your project:

```scala
"com.softwaremill.sttp.client4" %% "fs2" % "3.8.13" // for cats-effect 3.x & fs2 3.x
"com.softwaremill.sttp.client4" %% "fs2" % "4.0.0-M1" // for cats-effect 3.x & fs2 3.x
// or
"com.softwaremill.sttp.client4" %% "fs2-ce2" % "3.8.13" // for cats-effect 2.x & fs2 2.x
"com.softwaremill.sttp.client4" %% "fs2-ce2" % "4.0.0-M1" // for cats-effect 2.x & fs2 2.x
```

Obtain a cats-effect `Resource` which creates the backend, and closes the thread pool after the resource is no longer used:
Expand Down Expand Up @@ -78,9 +78,9 @@ Host header override is supported in environments running Java 12 onwards, but i
To use, add the following dependency to your project:

```scala
"com.softwaremill.sttp.client4" %% "armeria-backend-fs2" % "3.8.13" // for cats-effect 3.x & fs2 3.x
"com.softwaremill.sttp.client4" %% "armeria-backend-fs2" % "4.0.0-M1" // for cats-effect 3.x & fs2 3.x
// or
"com.softwaremill.sttp.client4" %% "armeria-backend-fs2" % "3.8.13" // for cats-effect 2.x & fs2 2.x
"com.softwaremill.sttp.client4" %% "armeria-backend-fs2" % "4.0.0-M1" // for cats-effect 2.x & fs2 2.x
```

create client:
Expand Down Expand Up @@ -110,11 +110,11 @@ val dispatcher: Dispatcher[IO] = ???

// Fluently build Armeria WebClient with built-in decorators
val client = WebClient.builder("https://my-service.com")
// Open circuit on 5xx server error status
.decorator(CircuitBreakerClient.newDecorator(CircuitBreaker.ofDefaultName(),
CircuitBreakerRule.onServerErrorStatus()))
.build()

// Open circuit on 5xx server error status
.decorator(CircuitBreakerClient.newDecorator(CircuitBreaker.ofDefaultName(),
CircuitBreakerRule.onServerErrorStatus()))
.build()
val backend = ArmeriaFs2Backend.usingClient[IO](client, dispatcher)
```

Expand Down Expand Up @@ -143,8 +143,8 @@ val effect = HttpClientFs2Backend.resource[IO]().use { backend =>
val stream: Stream[IO, Byte] = ???

basicRequest
.streamBody(Fs2Streams[IO])(stream)
.post(uri"...")
.streamBody(Fs2Streams[IO])(stream)
.send(backend)
}
```
Expand Down Expand Up @@ -189,6 +189,8 @@ import sttp.model.sse.ServerSentEvent

def processEvents(source: Stream[IO, ServerSentEvent]): IO[Unit] = ???

basicRequest.response(asStream(Fs2Streams[IO])(stream =>
processEvents(stream.through(Fs2ServerSentEvents.parse))))
basicRequest
.get(uri"")
.response(asStream(Fs2Streams[IO])(stream =>
processEvents(stream.through(Fs2ServerSentEvents.parse))))
```
6 changes: 3 additions & 3 deletions generated-docs/out/backends/future.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Class Supported stream type
To use, you don't need any extra dependencies, `core` is enough:

```
"com.softwaremill.sttp.client4" %% "core" % "3.8.13"
"com.softwaremill.sttp.client4" %% "core" % "4.0.0-M1"
```

You'll need the following imports:
Expand Down Expand Up @@ -59,7 +59,7 @@ Host header override is supported in environments running Java 12 onwards, but i
To use, add the following dependency to your project:

```scala
"com.softwaremill.sttp.client4" %% "okhttp-backend" % "3.8.13"
"com.softwaremill.sttp.client4" %% "okhttp-backend" % "4.0.0-M1"
```

and some imports:
Expand Down Expand Up @@ -91,7 +91,7 @@ This backend depends on [OkHttp](http://square.github.io/okhttp/) and fully supp
To use, add the following dependency to your project:

```
"com.softwaremill.sttp.client4" %% "armeria-backend" % "3.8.13"
"com.softwaremill.sttp.client4" %% "armeria-backend" % "4.0.0-M1"
```

add imports:
Expand Down
8 changes: 4 additions & 4 deletions generated-docs/out/backends/http4s.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
This backend is based on [http4s](https://http4s.org) (client) and is **asynchronous**. To use, add the following dependency to your project:

```scala
"com.softwaremill.sttp.client4" %% "http4s-backend" % "3.8.13" // for cats-effect 3.x & http4s 1.0.0-Mx
"com.softwaremill.sttp.client4" %% "http4s-backend" % "4.0.0-M1" // for cats-effect 3.x & http4s 1.0.0-Mx
// or
"com.softwaremill.sttp.client4" %% "http4s-ce2-backend" % "3.8.13" // for cats-effect 2.x & http4s 0.21.x
"com.softwaremill.sttp.client4" %% "http4s-ce2-backend" % "4.0.0-M1" // for cats-effect 2.x & http4s 0.21.x
```

The backend can be created in a couple of ways, e.g.:
Expand All @@ -17,10 +17,10 @@ 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, SttpBackend[IO, Fs2Streams[IO]]]
Http4sBackend.usingDefaultEmberClientBuilder[IO](): Resource[IO, StreamBackend[IO, Fs2Streams[IO]]]

// the "org.http4s" %% "http4s-blaze-client" % http4sVersion dependency needs to be explicitly added
Http4sBackend.usingDefaultBlazeClientBuilder[IO](): Resource[IO, SttpBackend[IO, Fs2Streams[IO]]]
Http4sBackend.usingDefaultBlazeClientBuilder[IO](): Resource[IO, StreamBackend[IO, Fs2Streams[IO]]]
```

Sending a request is a non-blocking, lazily-evaluated operation and results in a wrapped response. There's a transitive dependency on `http4s`.
Expand Down
Loading

0 comments on commit aee041e

Please sign in to comment.