Skip to content

Commit

Permalink
Move options that used tags to RequestOptions, replace tags with attr…
Browse files Browse the repository at this point in the history
…ibutes (#2364)
  • Loading branch information
adamw authored Dec 10, 2024
1 parent 3ce24c1 commit 492bba7
Show file tree
Hide file tree
Showing 13 changed files with 158 additions and 138 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ val zio2InteropRsVersion = "2.0.2"

val oxVersion = "0.5.1"
val sttpModelVersion = "1.7.11"
val sttpSharedVersion = "1.4.0"
val sttpSharedVersion = "1.4.2"

val logback = "ch.qos.logback" % "logback-classic" % "1.5.12"

Expand Down
10 changes: 8 additions & 2 deletions core/src/main/scala/sttp/client4/RequestOptions.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package sttp.client4

import scala.concurrent.duration.Duration
import sttp.model.HttpVersion
import sttp.client4.logging.LoggingOptions

/** Options for a [[Request]]. The defaults can be found on [[emptyRequest]]. */
case class RequestOptions(
followRedirects: Boolean,
readTimeout: Duration, // TODO: Use FiniteDuration while migrating to sttp-4
readTimeout: Duration,
maxRedirects: Int,
redirectToGet: Boolean
redirectToGet: Boolean,
disableAutoDecompression: Boolean,
httpVersion: Option[HttpVersion],
loggingOptions: LoggingOptions
)
5 changes: 3 additions & 2 deletions core/src/main/scala/sttp/client4/SpecifyAuthScheme.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package sttp.client4
import sttp.client4.internal.DigestAuthenticator
import sttp.client4.internal.Utf8
import java.util.Base64
import sttp.attributes.AttributeKey

class SpecifyAuthScheme[+R <: PartialRequestBuilder[R, _]](
hn: String,
req: R,
digestTag: String
digestAttributeKey: AttributeKey[DigestAuthenticator.DigestAuthData]
) {
def basic(user: String, password: String): R = {
val c = new String(Base64.getEncoder.encode(s"$user:$password".getBytes(Utf8)), Utf8)
Expand All @@ -21,5 +22,5 @@ class SpecifyAuthScheme[+R <: PartialRequestBuilder[R, _]](
req.header(hn, s"Bearer $token")

def digest(user: String, password: String): R =
req.tag(digestTag, DigestAuthenticator.DigestAuthData(user, password))
req.attribute(digestAttributeKey, DigestAuthenticator.DigestAuthData(user, password))
}
11 changes: 7 additions & 4 deletions core/src/main/scala/sttp/client4/SttpApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ package sttp.client4

import sttp.client4.internal._
import sttp.model._
import sttp.ws.WebSocket

import java.io.InputStream
import java.nio.ByteBuffer
import scala.collection.immutable.Seq
import scala.concurrent.duration._
import sttp.capabilities.Streams
import sttp.ws.WebSocketFrame
import sttp.capabilities.Effect
import sttp.client4.wrappers.FollowRedirectsBackend
import sttp.client4.logging.LoggingOptions
import sttp.attributes.AttributeMap

trait SttpApi extends SttpExtensions with UriInterpolator {
val DefaultReadTimeout: Duration = 1.minute
Expand All @@ -30,9 +30,12 @@ trait SttpApi extends SttpExtensions with UriInterpolator {
followRedirects = true,
DefaultReadTimeout,
FollowRedirectsBackend.MaxRedirects,
redirectToGet = false
redirectToGet = false,
disableAutoDecompression = false,
httpVersion = None,
loggingOptions = LoggingOptions()
),
Map()
AttributeMap.Empty
)

/** A starting request, with the following modification comparing to [[emptyRequest]]: `Accept-Encoding` is set to
Expand Down
35 changes: 13 additions & 22 deletions core/src/main/scala/sttp/client4/logging/Log.scala
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,11 @@ class DefaultLog[F[_]](
) extends Log[F] {

def beforeRequestSend(request: GenericRequest[_, _]): F[Unit] =
request.loggingOptions match {
case Some(options) =>
before(
request,
options.logRequestBody.getOrElse(logRequestBody),
options.logRequestHeaders.getOrElse(logRequestHeaders)
)
case None => before(request, logRequestBody, logRequestHeaders)
}
before(
request,
request.loggingOptions.logRequestBody.getOrElse(logRequestBody),
request.loggingOptions.logRequestHeaders.getOrElse(logRequestHeaders)
)

private def before(request: GenericRequest[_, _], _logRequestBody: Boolean, _logRequestHeaders: Boolean): F[Unit] =
logger(
Expand All @@ -82,19 +78,14 @@ class DefaultLog[F[_]](
response: Response[_],
responseBody: Option[String],
elapsed: Option[Duration]
): F[Unit] = request.loggingOptions match {
case Some(options) =>
handleResponse(
request.showBasic,
response,
responseBody,
options.logResponseBody.getOrElse(responseBody.isDefined),
options.logResponseHeaders.getOrElse(logResponseHeaders),
elapsed
)
case None =>
handleResponse(request.showBasic, response, responseBody, responseBody.isDefined, logResponseHeaders, elapsed)
}
): F[Unit] = handleResponse(
request.showBasic,
response,
responseBody,
request.loggingOptions.logResponseBody.getOrElse(responseBody.isDefined),
request.loggingOptions.logResponseHeaders.getOrElse(logResponseHeaders),
elapsed
)

private def handleResponse(
showBasic: String,
Expand Down
6 changes: 0 additions & 6 deletions core/src/main/scala/sttp/client4/package.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
package sttp

package object client4 extends SttpApi {

/** Provide an implicit value of this type to serialize arbitrary classes into a request body. Backends might also
* provide special logic for serializer instances which they define (e.g. to handle streaming).
*/
type BodySerializer[B] = B => BasicBodyPart

type RetryWhen = (GenericRequest[_, _], Either[Throwable, Response[_]]) => Boolean
}
57 changes: 33 additions & 24 deletions core/src/main/scala/sttp/client4/request.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import sttp.client4.internal.{ToCurlConverter, ToRfc2616Converter}
import sttp.shared.Identity

import scala.collection.immutable.Seq
import sttp.attributes.AttributeMap

/** A generic description of an HTTP request, along with a description of how the response body should be handled.
*
Expand Down Expand Up @@ -67,8 +68,8 @@ trait GenericRequest[+T, -R] extends RequestBuilder[GenericRequest[T, R]] with R
* @param response
* Description of how the response body should be handled. Needs to be specified upfront so that the response is
* always consumed and hence there are no requirements on client code to consume it.
* @param tags
* Request-specific tags which can be used by backends for logging, metrics, etc. Empty by default.
* @param attributes
* Request-specific attributes which can be used by backends for logging, metrics, etc. Empty by default.
* @tparam T
* The target type, to which the response body should be read.
*/
Expand All @@ -79,7 +80,7 @@ case class Request[T](
headers: Seq[Header],
response: ResponseAs[T],
options: RequestOptions,
tags: Map[String, Any]
attributes: AttributeMap
) extends GenericRequest[T, Any]
with RequestBuilder[Request[T]] {

Expand All @@ -88,11 +89,19 @@ case class Request[T](
override def method(method: Method, uri: Uri): Request[T] = copy(uri = uri, method = method)
override def withHeaders(headers: Seq[Header]): Request[T] = copy(headers = headers)
override def withOptions(options: RequestOptions): Request[T] = copy(options = options)
override def withTags(tags: Map[String, Any]): Request[T] = copy(tags = tags)
override def withAttributes(attributes: AttributeMap): Request[T] = copy(attributes = attributes)
override protected def copyWithBody(body: BasicBody): Request[T] = copy(body = body)

def multipartStreamBody[S](ps: Seq[Part[BodyPart[S]]]): StreamRequest[T, S] =
StreamRequest(method, uri, MultipartStreamBody(ps), headers, StreamResponseAs(response.delegate), options, tags)
StreamRequest(
method,
uri,
MultipartStreamBody(ps),
headers,
StreamResponseAs(response.delegate),
options,
attributes
)

def multipartStreamBody[S](p1: Part[BodyPart[S]], ps: Part[BodyPart[S]]*): StreamRequest[T, S] =
StreamRequest(
Expand All @@ -102,11 +111,11 @@ case class Request[T](
headers,
StreamResponseAs(response.delegate),
options,
tags
attributes
)

def streamBody[S](s: Streams[S])(b: s.BinaryStream): StreamRequest[T, S] =
StreamRequest(method, uri, StreamBody(s)(b), headers, StreamResponseAs(response.delegate), options, tags)
StreamRequest(method, uri, StreamBody(s)(b), headers, StreamResponseAs(response.delegate), options, attributes)

/** Specifies the target type to which the response body should be read. Note that this replaces any previous
* specifications, which also includes any previous `mapResponse` invocations.
Expand All @@ -117,19 +126,19 @@ case class Request[T](

/** Specifies that this is a WebSocket request. A [[WebSocketBackend]] will be required to send this request. */
def response[F[_], T2](ra: WebSocketResponseAs[F, T2]): WebSocketRequest[F, T2] =
WebSocketRequest(method, uri, body, headers, ra, options, tags)
WebSocketRequest(method, uri, body, headers, ra, options, attributes)

/** Specifies that the response body should be processed using a non-blocking, asynchronous stream, as witnessed by
* the `S` capability. A [[StreamBackend]] will be required to send this request.
*/
def response[T2, S](ra: StreamResponseAs[T2, S]): StreamRequest[T2, S] =
StreamRequest(method, uri, body, headers, ra, options, tags)
StreamRequest(method, uri, body, headers, ra, options, attributes)

/** Specifies that this is a WebSocket request, and the WebSocket will be processed using a non-blocking, asynchronous
* stream, as witnessed by the `S` capability. A [[WebSocketStreamBackend]] will be required to send this request.
*/
def response[T2, S](ra: WebSocketStreamResponseAs[T2, S]): WebSocketStreamRequest[T2, S] =
WebSocketStreamRequest(method, uri, body, headers, ra, options, tags)
WebSocketStreamRequest(method, uri, body, headers, ra, options, attributes)

/** Sends the request, using the given backend.
*
Expand Down Expand Up @@ -182,8 +191,8 @@ object Request {
* @param response
* Description of how the response body should be handled. Needs to be specified upfront so that the response is
* always consumed and hence there are no requirements on client code to consume it.
* @param tags
* Request-specific tags which can be used by backends for logging, metrics, etc. Empty by default.
* @param attributes
* Request-specific attributes which can be used by backends for logging, metrics, etc. Empty by default.
* @tparam T
* The target type, to which the response body should be read. If the response body is streamed, this might be the
* value obtained by processing the entire stream.
Expand All @@ -197,7 +206,7 @@ final case class StreamRequest[T, R](
headers: Seq[Header],
response: StreamResponseAs[T, R],
options: RequestOptions,
tags: Map[String, Any]
attributes: AttributeMap
) extends GenericRequest[T, R]
with RequestBuilder[StreamRequest[T, R]] {

Expand All @@ -206,7 +215,7 @@ final case class StreamRequest[T, R](
override def method(method: Method, uri: Uri): StreamRequest[T, R] = copy(method = method, uri = uri)
override def withHeaders(headers: Seq[Header]): StreamRequest[T, R] = copy(headers = headers)
override def withOptions(options: RequestOptions): StreamRequest[T, R] = copy(options = options)
override def withTags(tags: Map[String, Any]): StreamRequest[T, R] = copy(tags = tags)
override def withAttributes(attributes: AttributeMap): StreamRequest[T, R] = copy(attributes = attributes)
override protected def copyWithBody(body: BasicBody): StreamRequest[T, R] = copy(body = body)

/** Specifies the target type to which the response body should be read. Note that this replaces any previous
Expand All @@ -229,7 +238,7 @@ final case class StreamRequest[T, R](
headers,
WebSocketStreamResponseAs[T2, Effect[F] with R](ra.delegate),
options,
tags
attributes
)

def mapResponse[T2](f: T => T2): StreamRequest[T2, R] = copy(response = response.map(f))
Expand Down Expand Up @@ -263,8 +272,8 @@ final case class StreamRequest[T, R](
* @param response
* Description of how the WebSocket should be handled. Needs to be specified upfront so that the response is always
* consumed and hence there are no requirements on client code to consume it.
* @param tags
* Request-specific tags which can be used by backends for logging, metrics, etc. Empty by default.
* @param attributes
* Request-specific attributes which can be used by backends for logging, metrics, etc. Empty by default.
* @tparam F
* The effect type used to process the WebSocket. Might include asynchronous computations (e.g.
* [[scala.concurrent.Future]]), pure effect descriptions (`IO`), or synchronous computations ([[Identity]]).
Expand All @@ -279,7 +288,7 @@ final case class WebSocketRequest[F[_], T](
headers: Seq[Header],
response: WebSocketResponseAs[F, T],
options: RequestOptions,
tags: Map[String, Any]
attributes: AttributeMap
) extends GenericRequest[T, WebSockets with Effect[F]]
with RequestBuilder[WebSocketRequest[F, T]] {

Expand All @@ -288,7 +297,7 @@ final case class WebSocketRequest[F[_], T](
override def method(method: Method, uri: Uri): WebSocketRequest[F, T] = copy(method = method, uri = uri)
override def withHeaders(headers: Seq[Header]): WebSocketRequest[F, T] = copy(headers = headers)
override def withOptions(options: RequestOptions): WebSocketRequest[F, T] = copy(options = options)
override def withTags(tags: Map[String, Any]): WebSocketRequest[F, T] = copy(tags = tags)
override def withAttributes(attributes: AttributeMap): WebSocketRequest[F, T] = copy(attributes = attributes)
override protected def copyWithBody(body: BasicBody): WebSocketRequest[F, T] = copy(body = body)

def streamBody[S](s: Streams[S])(b: s.BinaryStream): WebSocketStreamRequest[T, Effect[F] with S] =
Expand All @@ -299,7 +308,7 @@ final case class WebSocketRequest[F[_], T](
headers,
WebSocketStreamResponseAs[T, Effect[F] with S](response.delegate),
options,
tags
attributes
)

def mapResponse[T2](f: T => T2): WebSocketRequest[F, T2] = copy(response = response.map(f))
Expand Down Expand Up @@ -343,8 +352,8 @@ final case class WebSocketRequest[F[_], T](
* @param response
* Description of how the WebSocket should be handled. Needs to be specified upfront so that the response is always
* consumed and hence there are no requirements on client code to consume it.
* @param tags
* Request-specific tags which can be used by backends for logging, metrics, etc. Empty by default.
* @param attributes
* Request-specific attributes which can be used by backends for logging, metrics, etc. Empty by default.
* @tparam T
* The target type, to which the response body should be read. If the WebSocket interactions are described entirely
* by the response description, this might be `Unit`. Otherwise, this can be an `S` stream of frames or mapped
Expand All @@ -359,7 +368,7 @@ final case class WebSocketStreamRequest[T, S](
headers: Seq[Header],
response: WebSocketStreamResponseAs[T, S],
options: RequestOptions,
tags: Map[String, Any]
attributes: AttributeMap
) extends GenericRequest[T, S with WebSockets]
with RequestBuilder[WebSocketStreamRequest[T, S]] {

Expand All @@ -368,7 +377,7 @@ final case class WebSocketStreamRequest[T, S](
override def method(method: Method, uri: Uri): WebSocketStreamRequest[T, S] = copy(method = method, uri = uri)
override def withHeaders(headers: Seq[Header]): WebSocketStreamRequest[T, S] = copy(headers = headers)
override def withOptions(options: RequestOptions): WebSocketStreamRequest[T, S] = copy(options = options)
override def withTags(tags: Map[String, Any]): WebSocketStreamRequest[T, S] = copy(tags = tags)
override def withAttributes(attributes: AttributeMap): WebSocketStreamRequest[T, S] = copy(attributes = attributes)
override protected def copyWithBody(body: BasicBody): WebSocketStreamRequest[T, S] = copy(body = body)

def mapResponse[T2](f: T => T2): WebSocketStreamRequest[T2, S] = copy(response = response.map(f))
Expand Down
Loading

0 comments on commit 492bba7

Please sign in to comment.