Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move options that used tags to RequestOptions, replace tags with attributes #2364

Merged
merged 5 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading