From 8724027c0a0ecdf14e77ae1cc0f2f7533612318c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Sowa?= Date: Mon, 14 Oct 2024 20:41:33 +0200 Subject: [PATCH] 0.4.0 release (#6) * Bumping deps, mainly for SN 0.5 * Updating ci.yml * Fixing dependencies for cross projects, removing unnecessary ones. Fixing DebuggingDirectives that would fail on large input/outputs. * Better body handling when logging requests. Handling for unconsumed request body. Updated README. * Deps update, ci update. * Fixing tests * semver in build.sbt --- .github/workflows/ci.yml | 15 +-- README.md | 87 ++++++++------ build.sbt | 76 +++++++----- .../directives/DebuggingDirectivesSpec.scala | 10 +- .../stir/server/ExceptionHandler.scala | 4 +- .../directives/DebuggingDirectives.scala | 111 +++++++++++++----- .../FileAndResourceDirectives.scala | 2 +- .../directives/FileUploadDirectives.scala | 6 +- examples/src/main/scala/Service.scala | 16 ++- project/plugins.sbt | 6 +- .../testkit/RouteTestResultComponent.scala | 4 +- 11 files changed, 207 insertions(+), 130 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fee08e6..bcdf6da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,8 +40,7 @@ jobs: timeout-minutes: 60 steps: - name: Install sbt - if: contains(runner.os, 'macos') - run: brew install sbt + uses: sbt/setup-sbt@v1 - name: Checkout current branch (full) uses: actions/checkout@v4 @@ -98,11 +97,11 @@ jobs: - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/master') - run: mkdir -p testkit/.native/target testkit/.js/target core/.native/target core/.js/target core/.jvm/target testkit/.jvm/target project/target + run: mkdir -p core/.native/target core/.js/target core/.jvm/target testkit/.jvm/target project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/master') - run: tar cf targets.tar testkit/.native/target testkit/.js/target core/.native/target core/.js/target core/.jvm/target testkit/.jvm/target project/target + run: tar cf targets.tar core/.native/target core/.js/target core/.jvm/target testkit/.jvm/target project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/master') @@ -122,8 +121,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Install sbt - if: contains(runner.os, 'macos') - run: brew install sbt + uses: sbt/setup-sbt@v1 - name: Checkout current branch (full) uses: actions/checkout@v4 @@ -242,7 +240,7 @@ jobs: dependency-submission: name: Submit Dependencies - if: github.event_name != 'pull_request' + if: github.event.repository.fork == false && github.event_name != 'pull_request' strategy: matrix: os: [ubuntu-latest] @@ -250,8 +248,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Install sbt - if: contains(runner.os, 'macos') - run: brew install sbt + uses: sbt/setup-sbt@v1 - name: Checkout current branch (full) uses: actions/checkout@v4 diff --git a/README.md b/README.md index 4a51e0e..00d7543 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ http4s-stir also furnishes a test kit akin to Pekko's (Akka's). In SBT: ```scala -libraryDependencies += "pl.iterators" %% "http4s-stir" % "0.2" -libraryDependencies += "pl.iterators" %% "http4s-stir-testkit" % "0.2" % Test // if you need this +libraryDependencies += "pl.iterators" %% "http4s-stir" % "0.4.0" +libraryDependencies += "pl.iterators" %% "http4s-stir-testkit" % "0.4.0" % Test // if you need this ``` For `scala-cli` see [this example](#example). @@ -25,15 +25,15 @@ For `scala-cli` see [this example](#example). Here's an example in Scala 3 that you can run using scala-cli: -```scala +```scala 3 // Main.scala -//> using dep org.typelevel::cats-effect:3.5.1 -//> using dep org.http4s::http4s-dsl:0.23.23 -//> using dep org.http4s::http4s-ember-server:0.23.23 -//> using dep org.http4s::http4s-circe:0.23.23 -//> using dep io.circe::circe-core:0.14.5 -//> using dep io.circe::circe-generic:0.14.5 -//> using dep pl.iterators::http4s-stir:0.2 +//> using dep org.typelevel::cats-effect::3.5.4 +//> using dep org.http4s::http4s-dsl::0.23.28 +//> using dep org.http4s::http4s-ember-server::0.23.28 +//> using dep org.http4s::http4s-circe::0.23.28 +//> using dep io.circe::circe-core::0.14.10 +//> using dep io.circe::circe-generic::0.14.10 +//> using dep pl.iterators::http4s-stir::0.4.0 import org.http4s.Status import org.http4s.ember.server.EmberServerBuilder @@ -94,17 +94,23 @@ val route: Route = object Main extends IOApp.Simple { val run = EmberServerBuilder - .default[IO] - .withHttpApp(route.toHttpRoutes.orNotFound) - .build - .use(_ => IO.never) + .default[IO] + .withHttpApp(route.toHttpRoutes.orNotFound) + .build + .use(_ => IO.never) } + ``` +To run this service you can use `scala-cli run .`. + +Or maybe if you want, you can compile it to JS file: `scala-cli --power package --js --js-module-kind commonjs Main.scala`. + ```scala 3 // Main.test.scala -//> using test.dep org.specs2::specs2-core:4.19.2 -//> using test.dep pl.iterators::http4s-stir-testkit:0.2 +//> using test.dep org.specs2::specs2-core:5.5.8 +//> using test.dep pl.iterators::http4s-stir-testkit:0.4.0 +//> using test.dep org.http4s::http4s-circe:0.23.28 import org.http4s.Status import org.http4s.circe.CirceEntityEncoder.* @@ -115,33 +121,36 @@ import org.specs2.mutable.Specification import pl.iterators.stir.testkit.Specs2RouteTest class MainRoutesSpec extends Specification with Specs2RouteTest { - override implicit val runtime: IORuntime = IORuntime.global - - sequential - "The routes" should { - "create order" in { - Post("/create-order", Order(List(Item("foo", 42)))) ~> route ~> check { - responseAs[String] must contain("order created") - orders.head must beEqualTo(Item("foo", 42)) - } - } - "retrieve an item if present" in { - orders = List(Item("foo", 42)) - Get("/item/42") ~> route ~> check { - responseAs[Item] must beEqualTo(Item("foo", 42)) - } - } - "return 404 if item is not present" in { - orders = List.empty - Get("/item/42") ~> route ~> check { - status must beEqualTo(Status.NotFound) - } - } + override implicit val runtime: IORuntime = IORuntime.global + + sequential + "The routes" should { + "create order" in { + Post("/create-order", Order(List(Item("foo", 42)))) ~> route ~> check { + responseAs[String] must contain("order created") + orders.head must beEqualTo(Item("foo", 42)) + } + } + "retrieve an item if present" in { + orders = List(Item("foo", 42)) + Get("/item/42") ~> route ~> check { + responseAs[Item] must beEqualTo(Item("foo", 42)) + } } + "return 404 if item is not present" in { + orders = List.empty + Get("/item/42") ~> route ~> check { + status must beEqualTo(Status.NotFound) + } + } + } } + ``` -For a more comprehensive example showcasing additional directives see [examples](https://github.com/theiterators/http4s-stir/blob/master/examples/src/main/scala/Service.scala). +To run the tests you can use `scala-cli test .`. + +For a more comprehensive example showcasing additional directives see [examples](https://github.com/theiterators/http4s-stir/blob/master/examples/src/main/scala/Service.scala). You can run it with `~examples/reStart`. ## Why this library? diff --git a/build.sbt b/build.sbt index 1f8f279..6471bd0 100644 --- a/build.sbt +++ b/build.sbt @@ -6,12 +6,13 @@ val supportedScalaVersions = Seq(scala_2_13, scala_3) ThisBuild / crossScalaVersions := supportedScalaVersions ThisBuild / scalaVersion := mainScalaVersion +ThisBuild / versionScheme := Some("early-semver") 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 +ThisBuild / sonatypeCredentialHost := xerial.sbt.Sonatype.sonatypeLegacy lazy val noPublishSettings = Seq( @@ -53,65 +54,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.10") +val circeParser = Def.setting("io.circe" %%% "circe-parser" % "0.14.10") +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.19") +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 *) diff --git a/core-tests/src/test/scala/pl/iterators/stir/server/directives/DebuggingDirectivesSpec.scala b/core-tests/src/test/scala/pl/iterators/stir/server/directives/DebuggingDirectivesSpec.scala index 918f13f..5fcbedf 100644 --- a/core-tests/src/test/scala/pl/iterators/stir/server/directives/DebuggingDirectivesSpec.scala +++ b/core-tests/src/test/scala/pl/iterators/stir/server/directives/DebuggingDirectivesSpec.scala @@ -40,7 +40,7 @@ class DebuggingDirectivesSpec extends RoutingSpec { resetDebugMsg() Get("/hello") ~> route ~> check { response.status shouldEqual Status.Ok - normalizedDebugMsg() shouldEqual "HTTP/1.1 GET /hello Headers() body=\"\"\n" + normalizedDebugMsg() shouldEqual "HTTP/1.1 GET /hello Headers()\n" } } } @@ -54,7 +54,7 @@ class DebuggingDirectivesSpec extends RoutingSpec { resetDebugMsg() Get("/hello") ~> route ~> check { response.status shouldEqual Status.Ok - normalizedDebugMsg() shouldEqual "HTTP/1.1 200 OK Headers() body=\"\"\n" + normalizedDebugMsg() shouldEqual "HTTP/1.1 200 OK Headers()\n" } } } @@ -69,9 +69,9 @@ class DebuggingDirectivesSpec extends RoutingSpec { Get("/hello") ~> route ~> check { response.status shouldEqual Status.Ok normalizedDebugMsg() shouldEqual - """|HTTP/1.1 GET /hello Headers() body="" - |HTTP/1.1 200 OK Headers() body="" - |""".stripMarginWithNewline("\n") + """|HTTP/1.1 GET /hello Headers() + |HTTP/1.1 200 OK Headers() + |""".stripMarginWithNewline("\n") } } // "be able to log only rejections" in { diff --git a/core/src/main/scala/pl/iterators/stir/server/ExceptionHandler.scala b/core/src/main/scala/pl/iterators/stir/server/ExceptionHandler.scala index cbea651..8b7b906 100644 --- a/core/src/main/scala/pl/iterators/stir/server/ExceptionHandler.scala +++ b/core/src/main/scala/pl/iterators/stir/server/ExceptionHandler.scala @@ -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 @@ -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 => { diff --git a/core/src/main/scala/pl/iterators/stir/server/directives/DebuggingDirectives.scala b/core/src/main/scala/pl/iterators/stir/server/directives/DebuggingDirectives.scala index 7989a56..395a4c3 100644 --- a/core/src/main/scala/pl/iterators/stir/server/directives/DebuggingDirectives.scala +++ b/core/src/main/scala/pl/iterators/stir/server/directives/DebuggingDirectives.scala @@ -1,11 +1,11 @@ package pl.iterators.stir.server.directives import cats.effect.IO -import fs2.{ Chunk, Stream } +import cats.effect.std.Console +import fs2.{ Pull, Stream } import org.http4s.server.middleware.Logger import org.http4s.{ Headers, Request, Response } import org.typelevel.ci.CIString -import org.typelevel.log4cats import pl.iterators.stir.server.{ Directive, Directive0, RouteResult } trait DebuggingDirectives { @@ -17,27 +17,46 @@ 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 => - 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 log = logAction.getOrElse { (s: String) => + DebuggingDirectives.logger(s) + } + val logWithTrimmingIndicator = indicateTrimming(maxBodyBytes, ctx.request.contentLength).andThen(log) + val logWithBodyNotConsumedIndicator = indicateBodyNotConsumed(ctx.request.contentLength).andThen(log) - val newCtx = ctx.copy(request = ctx.request.withBodyStream(newBody)) - Logger.logMessage[IO, Request[IO]](newRequest)(logHeaders, logBody = true, redactHeadersWhen)(log).flatMap( - _ => - inner(())(newCtx)) + if (logBody && !ctx.request.isChunked && ctx.request.contentLength.exists(_ > 0)) { + IO.ref(false).flatMap { bodyConsumedRef => + val newBody = ctx.request.body.pull.unconsN(maxBodyBytes, allowFewer = true).flatMap { + case Some((head, tail)) => + Pull.output(head) >> + Pull.eval { + bodyConsumedRef.update(_ => true) *> Logger.logMessage[IO, Request[IO]]( + ctx.request.withBodyStream(Stream.chunk(head)))(logHeaders, + logBody = true, redactHeadersWhen)(logWithTrimmingIndicator) + } >> + tail.pull.echo + case None => + Pull.eval { + bodyConsumedRef.update(_ => true) *> Logger.logMessage[IO, Request[IO]](ctx.request)(logHeaders, + logBody = false, redactHeadersWhen)(log) + } + }.stream + val newRequest = ctx.request.withBodyStream(newBody) + inner(())(ctx.copy(request = newRequest)).flatTap { + _ => + bodyConsumedRef.get.flatMap { + bodyConsumed => + if (!bodyConsumed) { + Logger.logMessage[IO, Request[IO]](newRequest)(logHeaders, logBody = false, redactHeadersWhen)( + logWithBodyNotConsumedIndicator) + } else { + IO.unit + } + } } + } } else { Logger.logMessage[IO, Request[IO]](ctx.request)(logHeaders, logBody = false, redactHeadersWhen)(log).flatMap( _ => @@ -53,18 +72,29 @@ 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 newBody = response.body.pull.unconsN(maxBodyBytes, allowFewer = true).flatMap { + case Some((head, tail)) => + Pull.output(head) >> + Pull.eval { + Logger.logMessage[IO, Response[IO]](response.withBodyStream(Stream.chunk(head)))(logHeaders, + logBody = true, redactHeadersWhen)(logWithTrimmingIndicator) + } >> + tail.pull.echo + case None => Pull.eval { + Logger.logMessage[IO, Response[IO]](response)(logHeaders, logBody = false, redactHeadersWhen)(log) + } + }.stream + IO.pure(RouteResult.Complete(response.copy(body = newBody))) } else { Logger.logMessage[IO, Response[IO]](response)(logHeaders, logBody = false, redactHeadersWhen)(log).as( RouteResult.Complete(response)) @@ -83,19 +113,36 @@ 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 + } + } + + private def indicateBodyNotConsumed(contentLength: Option[Long]): String => String = { log => + contentLength match { + case Some(length) => + s"$log body= ($length bytes total)" + case None => + s"$log body= (??? bytes total)" + } } } 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 } diff --git a/core/src/main/scala/pl/iterators/stir/server/directives/FileAndResourceDirectives.scala b/core/src/main/scala/pl/iterators/stir/server/directives/FileAndResourceDirectives.scala index 8ea8224..3aec886 100644 --- a/core/src/main/scala/pl/iterators/stir/server/directives/FileAndResourceDirectives.scala +++ b/core/src/main/scala/pl/iterators/stir/server/directives/FileAndResourceDirectives.scala @@ -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)) } } diff --git a/core/src/main/scala/pl/iterators/stir/server/directives/FileUploadDirectives.scala b/core/src/main/scala/pl/iterators/stir/server/directives/FileUploadDirectives.scala index d4d992c..f0b3736 100644 --- a/core/src/main/scala/pl/iterators/stir/server/directives/FileUploadDirectives.scala +++ b/core/src/main/scala/pl/iterators/stir/server/directives/FileUploadDirectives.scala @@ -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(())) @@ -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 @@ -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) } diff --git a/examples/src/main/scala/Service.scala b/examples/src/main/scala/Service.scala index c2ef2b2..753f11e 100644 --- a/examples/src/main/scala/Service.scala +++ b/examples/src/main/scala/Service.scala @@ -101,15 +101,27 @@ object Main extends IOApp.Simple { } } } + } ~ (path("pipe") & extractRequest) { request => + complete { + Status.Ok -> request.body + } + } ~ (path("empty") & extractRequest) { _ => + complete { + Status.Ok + } + } ~ (post & path("empty") & extractRequest) { _ => + complete { + Status.Ok + } } ~ (path("file-upload") & storeUploadedFiles("file", fi => new File("/tmp/" + fi.fileName))) { files => complete { Status.Ok -> s"File $files uploaded" } } ~ authenticateBasic("d-and-d-realm", authenticator) { _ => path("file") { - getFromFile("project/plugins.sbt") + getFromFile("../src/main/scala/Service.scala") } ~ pathPrefix("dir") { - getFromDirectory("src/main") + getFromDirectory("../") } } } ~ path("ws") { diff --git a/project/plugins.sbt b/project/plugins.sbt index bf2808a..9bfccc1 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,11 +2,9 @@ addSbtPlugin("io.spray" % "sbt-revolver" % "0.10.0") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") -//addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.12") - 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.typelevel" % "sbt-typelevel-ci-release" % "0.7.2") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.17") +addSbtPlugin("org.typelevel" % "sbt-typelevel-ci-release" % "0.7.4") addSbtPlugin("org.jmotor.sbt" % "sbt-dependency-updates" % "1.2.9") diff --git a/testkit/src/main/scala/pl/iterators/stir/testkit/RouteTestResultComponent.scala b/testkit/src/main/scala/pl/iterators/stir/testkit/RouteTestResultComponent.scala index 2fcde5d..c060781 100644 --- a/testkit/src/main/scala/pl/iterators/stir/testkit/RouteTestResultComponent.scala +++ b/testkit/src/main/scala/pl/iterators/stir/testkit/RouteTestResultComponent.scala @@ -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 */ )