Skip to content

Commit

Permalink
added curl logger client aspect.
Browse files Browse the repository at this point in the history
  • Loading branch information
landlockedsurfer committed Jan 28, 2025
1 parent d8f9c69 commit d95c630
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 0 deletions.
23 changes: 23 additions & 0 deletions zio-http/jvm/src/test/scala/zio/http/ZClientAspectSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,29 @@ object ZClientAspectSpec extends ZIOHttpSpec {
extractStatus(response) == Status.Ok,
),
),
test("curl request logger") {

for {
port <- Server.install(routes)
baseClient <- ZIO.service[Client]
client = baseClient
.url(
URL(Path.empty, Location.Absolute(Scheme.HTTP, "localhost", Some(port))),
)
.batched @@ ZClientAspect.curlLogger(logEffect = m => Console.printLine(m).orDie)
response <- client.request(Request.get(URL.empty / "hello"))
output <- TestConsole.output
} yield assertTrue(
output.mkString("") ==
s"""curl \\
| --verbose \\
| --request GET \\
| --header 'user-agent:${Client.defaultUAHeader.renderedValue}' \\
| 'http://localhost:${port}/hello'
|""".stripMargin,
extractStatus(response) == Status.Ok,
)
},
).provide(
ZLayer.succeed(Server.Config.default.onAnyOpenPort),
Server.customized,
Expand Down
105 changes: 105 additions & 0 deletions zio-http/shared/src/main/scala/zio/http/ZClientAspect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -492,4 +492,109 @@ object ZClientAspect {
},
)
}

/**
* Client aspect that logs details of web request as curl command
*/
final def curlLogger(
verbose: Boolean = true,
logEffect: String => ZIO[Any, Nothing, Unit] = (m: String) => ZIO.log(m),
)(implicit trace: Trace): ZClientAspect[Nothing, Any, Nothing, Body, Nothing, Any, Nothing, Response] =
new ZClientAspect[Nothing, Any, Nothing, Body, Nothing, Any, Nothing, Response] {

def formatCurlCommand(
version: Version,
method: Method,
url: URL,
headers: Headers,
body: Body,
proxy: Option[Proxy],
): String = {
val versionOpt = version match {
case Version.Default => Chunk.empty
case Version.Http_1_0 => Chunk("--http1.0")
case Version.Http_1_1 => Chunk("--http1.1")
}
val verboseOpt = if (verbose) Chunk("--verbose") else Chunk.empty
val requestOpt = method match {
case Method.GET => Chunk("--request GET")
case Method.POST => Chunk("--request POST")
case Method.PUT => Chunk("--request PUT")
case Method.DELETE => Chunk("--request DELETE")
case Method.PATCH => Chunk("--request PATCH")
case Method.HEAD => Chunk("--request HEAD")
case Method.OPTIONS => Chunk("--request OPTIONS")
case Method.CONNECT => Chunk("--request CONNECT")
case Method.TRACE => Chunk("--request TRACE")
case Method.CUSTOM(name) => Chunk(s"--request $name")
case Method.ANY => Chunk("--request GET")
}
val headerOpt = Chunk.fromIterable(headers.map(h => s"--header '${h.headerName}:${h.renderedValue}'"))
val bodyOpt = body match {
case Body.empty => Chunk.empty
case body => {
Chunk(s"--data '${body.asString.map(_.replace("'", "'\\''"))}'")
}
}
val proxyOpt = proxy match {
case Some(proxy) =>
Chunk(
s"--proxy '${proxy.url}'" +
proxy.credentials.map(c => s" --proxy-user '${c.uname}:${c.upassword}'") +
proxy.headers.map(h => s" --proxy-header '${h.headerName}:${h.renderedValue}'").mkString(" "),
)
case None => Chunk.empty
}
(
Chunk("curl") ++
verboseOpt ++
requestOpt ++
headerOpt ++
versionOpt ++
proxyOpt ++
bodyOpt ++
Chunk.single("'" + url.encode + "'")
).mkString(" \\\n ")
}

override def apply[
ReqEnv,
Env >: Nothing <: Any,
In >: Nothing <: Body,
Err >: Nothing <: Any,
Out >: Nothing <: Response,
](
client: ZClient[Env, ReqEnv, In, Err, Out],
): ZClient[Env, ReqEnv, In, Err, Out] = {
val oldDriver = client.driver

val newDriver = new ZClient.Driver[Env, ReqEnv, Err] {
override def request(
version: Version,
method: Method,
url: URL,
headers: Headers,
body: Body,
sslConfig: Option[ClientSSLConfig],
proxy: Option[Proxy],
)(implicit trace: Trace): ZIO[Env & ReqEnv, Err, Response] = {

val r: ZIO[Env & ReqEnv, Err, Response] =
logEffect(formatCurlCommand(version, method, url, headers, body, proxy)) *>
oldDriver
.request(version, method, url, headers, body, sslConfig, proxy)
r
}

override def socket[Env1 <: Env](version: Version, url: URL, headers: Headers, app: WebSocketApp[Env1])(
implicit
trace: Trace,
ev: ReqEnv =:= Scope,
): ZIO[Env1 & ReqEnv, Err, Response] =
client.driver.socket(version, url, headers, app)
}

client.transform(client.bodyEncoder, client.bodyDecoder, newDriver)
}
}
}

0 comments on commit d95c630

Please sign in to comment.