Skip to content

Commit

Permalink
Fixing dependencies for cross projects, removing unnecessary ones.
Browse files Browse the repository at this point in the history
Fixing DebuggingDirectives that would fail on large input/outputs.
  • Loading branch information
luksow committed Oct 9, 2024
1 parent 357e9e9 commit 8403336
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 74 deletions.
73 changes: 43 additions & 30 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ ThisBuild / scalaVersion := mainScalaVersion
ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("11"), JavaSpec.temurin("17"))
ThisBuild / githubWorkflowPublishTargetBranches := Seq(RefPredicate.StartsWith(Ref.Tag("v")),
RefPredicate.Equals(Ref.Branch("master")))
ThisBuild / tlBaseVersion := "0.3"
ThisBuild / tlBaseVersion := "0.4"
ThisBuild / tlCiHeaderCheck := false
ThisBuild / tlSonatypeUseLegacyHost := true

Expand Down Expand Up @@ -53,65 +53,78 @@ lazy val baseSettings = Seq(
crossScalaVersions := supportedScalaVersions,
scalafmtOnCompile := true)

val http4s = Seq(
"org.http4s" %% "http4s-dsl" % "0.23.28",
"org.http4s" %% "http4s-ember-server" % "0.23.28")
val http4sDsl = Def.setting("org.http4s" %%% "http4s-dsl" % "0.23.28")
val http4sEmber = Def.setting("org.http4s" %%% "http4s-ember-server" % "0.23.28")

val http4sClient = Seq(
"org.http4s" %% "http4s-ember-client" % "0.23.28")
val fs2Core = Def.setting("co.fs2" %%% "fs2-core" % "3.11.0")
val fs2Io = Def.setting("co.fs2" %%% "fs2-io" % "3.11.0")

val circe = Seq(
"io.circe" %% "circe-core" % "0.14.10",
"io.circe" %% "circe-generic" % "0.14.10",
"io.circe" %% "circe-parser" % "0.14.10",
"org.http4s" %% "http4s-circe" % "0.23.28")
val http4sClient = Def.setting(
"org.http4s" %%% "http4s-ember-client" % "0.23.28")

val logback = Seq(
"ch.qos.logback" % "logback-classic" % "1.5.8")
val circeCore = Def.setting("io.circe" %%% "circe-core" % "0.14.8")
val circeGeneric = Def.setting("io.circe" %%% "circe-generic" % "0.14.8")
val circeParser = Def.setting("io.circe" %%% "circe-parser" % "0.14.8")
val http4sCirce = Def.setting("org.http4s" %%% "http4s-circe" % "0.23.28")

lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform)
val scalatest = Def.setting("org.scalatest" %%% "scalatest" % "3.2.18")
val specs2 = Def.setting("org.specs2" %%% "specs2-core" % "4.20.6")

val scalaXml = Def.setting("org.scala-lang.modules" %%% "scala-xml" % "2.2.0")

lazy val core = crossProject(JVMPlatform, NativePlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("core"))
.settings(baseSettings *)
.settings(
name := "http4s-stir",
libraryDependencies ++= http4s,
libraryDependencies ++= Seq(http4sDsl.value, http4sEmber.value) ++ Seq(fs2Core.value,
fs2Io.value) ++ Seq(scalaXml.value),
Compile / doc / scalacOptions -= "-Xfatal-warnings")

lazy val coreTests = project
lazy val coreTests = crossProject(JVMPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("core-tests"))
.settings(baseSettings *)
.settings(noPublishSettings *)
.settings(
name := "http4s-stir-tests",
libraryDependencies ++= http4s ++ circe ++ Seq(
"org.scalatest" %% "scalatest" % "3.2.19" % Test,
"org.specs2" %% "specs2-core" % "4.20.8" % Test)).dependsOn(
testkit.jvm % "test",
core.jvm % "test->test")
libraryDependencies ++= Seq(http4sDsl.value, http4sEmber.value) ++
Seq(circeCore.value, circeGeneric.value, circeParser.value, http4sCirce.value) ++
Seq(
scalatest.value % Test,
specs2.value % Test))
.dependsOn(
testkit % "test",
core % "test->test")

lazy val testkit = crossProject(JSPlatform, JVMPlatform, NativePlatform)
lazy val testkit = crossProject(JVMPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("testkit"))
.settings(baseSettings *)
.settings(
name := "http4s-stir-testkit",
libraryDependencies ++= http4s ++ http4sClient ++ Seq(
"org.scalatest" %% "scalatest" % "3.2.19" % "provided",
"org.specs2" %% "specs2-core" % "4.20.8" % "provided")).dependsOn(core)
libraryDependencies ++= Seq(http4sClient.value) ++ Seq(
scalatest.value % "provided",
specs2.value % "provided"))
.dependsOn(core)

lazy val examples = project
lazy val examples = crossProject(JVMPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("examples"))
.settings(baseSettings *)
.settings(noPublishSettings *)
.settings(
name := "http4s-stir-examples",
libraryDependencies ++= http4s ++ circe ++ logback ++ Seq(
"org.specs2" %% "specs2-core" % "4.20.8" % Test,
"org.scalatest" %% "scalatest" % "3.2.19" % Test))
.dependsOn(core.jvm, testkit.jvm % Test)
libraryDependencies ++= Seq(http4sDsl.value, http4sEmber.value) ++ Seq(circeCore.value, circeGeneric.value,
circeParser.value, http4sCirce.value) ++ Seq(
specs2.value % Test,
scalatest.value % Test))
.dependsOn(core, testkit % Test)

lazy val root = tlCrossRootProject.aggregate(core, testkit, examples, coreTests)
.settings(baseSettings *)
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package pl.iterators.stir.server

import cats.effect.IO
import cats.effect.std.Console
import org.http4s.Status.InternalServerError
import org.typelevel.log4cats

import scala.util.control.NonFatal

Expand Down Expand Up @@ -43,7 +43,7 @@ object ExceptionHandler {
*/
def default(logAction: Option[(Throwable, String) => IO[Unit]] = None): ExceptionHandler = {
val log = logAction.getOrElse { (t: Throwable, s: String) =>
log4cats.slf4j.Slf4jFactory.create[IO].getLogger.error(t)(s)
Console[IO].errorln(s) *> Console[IO].printStackTrace(t)
}
apply(knownToBeSealed = true) {
case NonFatal(e) => ctx => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package pl.iterators.stir.server.directives

import cats.effect.IO
import fs2.{ Chunk, Stream }
import cats.effect.std.Console
import cats.implicits.toFlatMapOps
import fs2.{ Chunk, Pull, Stream }
import org.http4s.server.middleware.Logger
import org.http4s.{ Headers, Request, Response }
import org.http4s.{ EntityBody, Headers, Request, Response }
import org.typelevel.ci.CIString
import org.typelevel.log4cats
import pl.iterators.stir.server.{ Directive, Directive0, RouteResult }

trait DebuggingDirectives {
Expand All @@ -17,27 +18,33 @@ trait DebuggingDirectives {
*/
def logRequest(logHeaders: Boolean = true, logBody: Boolean = true,
redactHeadersWhen: CIString => Boolean = Headers.SensitiveHeaders.contains,
maxLogLength: Int = Int.MaxValue,
maxBodyBytes: Int = DebuggingDirectives.DefaultLogLength,
logAction: Option[String => IO[Unit]] = None): Directive0 = {
val log = trimLog(maxLogLength).andThen(logAction.getOrElse { (s: String) =>
DebuggingDirectives.logger.info(s)
})

Directive { inner => ctx =>
val log = logAction.getOrElse { (s: String) =>
DebuggingDirectives.logger(s)
}
val logWithTrimmingIndicator = indicateTrimming(maxBodyBytes, ctx.request.contentLength).andThen(log)
if (logBody && !ctx.request.isChunked) {
IO.ref(Vector.empty[Chunk[Byte]])
.flatMap { vec =>
val newBody = Stream.eval(vec.get)
.flatMap(chunks => Stream.emits(chunks).covary[IO])
.flatMap(chunks => Stream.chunk(chunks).covary[IO])
val newRequest = ctx.request.withBodyStream(
ctx.request.body.observe(_.chunks.flatMap(chunk => Stream.eval(vec.update(_ :+ chunk)).drain)))

val newCtx = ctx.copy(request = ctx.request.withBodyStream(newBody))
Logger.logMessage[IO, Request[IO]](newRequest)(logHeaders, logBody = true, redactHeadersWhen)(log).flatMap(
_ =>
inner(())(newCtx))
}
ctx.request.body.pull.unconsN(maxBodyBytes).flatMap {
case Some((head, tail)) =>
Pull.eval {
Logger.logMessage[IO, Request[IO]](ctx.request.withBodyStream(Stream.chunk(head)))(logHeaders,
logBody = true,
redactHeadersWhen)(logWithTrimmingIndicator).flatMap { _ =>
val newBody = Stream.chunk(head) ++ tail
val newRequest = ctx.request.withBodyStream(newBody)
val newCtx = ctx.copy(request = newRequest)
inner(())(newCtx)
}
}.flatMap(r => Pull.output1(r))
case None =>
Pull.eval {
Logger.logMessage[IO, Request[IO]](ctx.request)(logHeaders, logBody = false, redactHeadersWhen)(
log).flatMap(_ =>
inner(())(ctx))
}.flatMap(r => Pull.output1(r))
}.stream.compile.onlyOrError
} else {
Logger.logMessage[IO, Request[IO]](ctx.request)(logHeaders, logBody = false, redactHeadersWhen)(log).flatMap(
_ =>
Expand All @@ -53,18 +60,21 @@ trait DebuggingDirectives {
*/
def logResult(logHeaders: Boolean = true, logBody: Boolean = true,
redactHeadersWhen: CIString => Boolean = Headers.SensitiveHeaders.contains,
maxLogLength: Int = Int.MaxValue,
maxBodyBytes: Int = DebuggingDirectives.DefaultLogLength,
logAction: Option[String => IO[Unit]] = None): Directive0 = {
val log = trimLog(maxLogLength).andThen(logAction.getOrElse { (s: String) =>
DebuggingDirectives.logger.info(s)
})

Directive { inner => ctx =>
val log = logAction.getOrElse { (s: String) =>
DebuggingDirectives.logger(s)
}
inner(())(ctx).flatMap {
case RouteResult.Complete(response) =>
val logWithTrimmingIndicator = indicateTrimming(maxBodyBytes, response.contentLength).andThen(log)
if (logBody && !response.isChunked) {
Logger.logMessage[IO, Response[IO]](response)(logHeaders, logBody = true, redactHeadersWhen)(log).as(
RouteResult.Complete(response))
val bodyToLog = response.body.take(maxBodyBytes.toLong).chunks.flatMap(Stream.chunk)
Logger.logMessage[IO, Response[IO]](response.withBodyStream(bodyToLog))(
logHeaders,
logBody = true,
redactHeadersWhen)(logWithTrimmingIndicator).as(RouteResult.Complete(response))
} else {
Logger.logMessage[IO, Response[IO]](response)(logHeaders, logBody = false, redactHeadersWhen)(log).as(
RouteResult.Complete(response))
Expand All @@ -83,19 +93,27 @@ trait DebuggingDirectives {
*/
def logRequestResult(logHeaders: Boolean = true, logBody: Boolean = true,
redactHeadersWhen: CIString => Boolean = Headers.SensitiveHeaders.contains,
maxLogLength: Int = Int.MaxValue,
maxBodyBytes: Int = DebuggingDirectives.DefaultLogLength,
logAction: Option[String => IO[Unit]] = None): Directive0 = {
logResult(logHeaders, logBody, redactHeadersWhen, maxLogLength, logAction) & logRequest(logHeaders, logBody,
logResult(logHeaders, logBody, redactHeadersWhen, maxBodyBytes, logAction) & logRequest(logHeaders, logBody,
redactHeadersWhen,
maxLogLength,
maxBodyBytes,
logAction)
}

private def trimLog(maxLogLength: Int): String => String = { log =>
if (log.length > maxLogLength) log.take(maxLogLength) + "..." else log
private def indicateTrimming(maxBodyBytes: Int, contentLength: Option[Long]): String => String = { log =>
contentLength match {
case Some(length) if length > maxBodyBytes =>
s"$log ... ($length bytes total)"
case None =>
s"$log ... (??? bytes total)"
case _ =>
log
}
}
}

object DebuggingDirectives extends DebuggingDirectives {
private val logger = log4cats.slf4j.Slf4jFactory.create[IO].getLogger
private def logger[A](a: A) = Console[IO].println(a)
private val DefaultLogLength: Int = 4096
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ trait FileAndResourceDirectives {
if (file.isFile && file.canRead) {
extractRequest { request =>
complete {
StaticFile.fromPath(Path.fromNioPath(file.toPath), Some(request)).getOrElse(
StaticFile.fromPath(Path(file.getAbsolutePath), Some(request)).getOrElse(
Response[IO](InternalServerError))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ trait FileUploadDirectives {
fileUpload(fieldName).flatMap {
case (fileInfo, bytes) =>
val dest = destFn(fileInfo)
val path = Path.fromNioPath(dest.toPath)
val path = Path(dest.getAbsolutePath)
val uploadedF: IO[(FileInfo, File)] =
bytes.through(Files[IO].writeAll(path)).compile.drain.map(_ => (fileInfo, dest)).onError(_ =>
IO.delay(dest.delete()).as(()))
Expand All @@ -54,7 +54,7 @@ trait FileUploadDirectives {
val fileInfo = FileInfo(part.name.getOrElse(""), part.filename.get,
part.contentType.getOrElse(throw new IllegalStateException(s"Missing content type for part $fieldName")))
val dest = destFn(fileInfo)
val path = Path.fromNioPath(dest.toPath)
val path = Path(dest.getAbsolutePath)
part.body.through(Files[IO].writeAll(path)).compile.drain.map(_ => (fileInfo, dest)).onError(_ =>
IO.delay(dest.delete()).as(()))
}.parSequence
Expand Down Expand Up @@ -102,7 +102,7 @@ trait FileUploadDirectives {
storeUploadedFiles(fieldName, tempDest).map { files =>
files.map {
case (fileInfo, src) =>
val path = Path.fromNioPath(src.toPath)
val path = Path(src.getAbsolutePath)
val byteSource: Stream[IO, Byte] = Files[IO].readAll(path).onFinalize(IO.delay(src.delete()).as(()))
(fileInfo, byteSource)
}
Expand Down
2 changes: 1 addition & 1 deletion examples/src/main/scala/Service.scala
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ object Main extends IOApp.Simple {
path("file") {
getFromFile("project/plugins.sbt")
} ~ pathPrefix("dir") {
getFromDirectory("src/main")
getFromDirectory("core/src/main")
}
}
} ~ path("ws") {
Expand Down
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.17.0")
addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2")
addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.2")
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.5")
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.17")
addSbtPlugin("org.typelevel" % "sbt-typelevel-ci-release" % "0.7.2")
addSbtPlugin("org.jmotor.sbt" % "sbt-dependency-updates" % "1.2.9")
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ trait RouteTestResultComponent {
// this
// }

private[this] lazy val entityRecreator: IORuntime => EntityBody[IO] = runtime =>
private[this] lazy val entityRecreator: IORuntime => EntityBody[IO] = implicit runtime =>
rawResponse.body.compile.toVector.map { bytes =>
Stream.emits(bytes): Stream[IO, Byte]
}.unsafeRunSync()(runtime)
}.unsafeRunSync()

private def failNeitherCompletedNorRejected(): Nothing =
failTest("Request was neither completed nor rejected" /*within " + timeout */ )
Expand Down

0 comments on commit 8403336

Please sign in to comment.