From c9c66b3c3cfe30161732931282dc2c6748cd1b76 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Sat, 10 Dec 2022 00:14:15 +0000 Subject: [PATCH 01/12] Update jsoniter-scala-core, ... to 2.19.1 --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index d95d30ddcd..d915b69971 100644 --- a/build.sbt +++ b/build.sbt @@ -774,8 +774,8 @@ lazy val jsoniterScala: ProjectMatrix = (projectMatrix in file("json/jsoniter")) .settings( name := "tapir-jsoniter-scala", libraryDependencies ++= Seq( - "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % "2.18.1", - "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % "2.18.1" % Test, + "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % "2.19.1", + "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % "2.19.1" % Test, scalaTest.value % Test ) ) From edce475aea83a948c3b23d1485f8c0f54dc9e12f Mon Sep 17 00:00:00 2001 From: adamw Date: Mon, 12 Dec 2022 11:53:12 +0100 Subject: [PATCH 02/12] Discourse --- README.md | 6 +++--- doc/contributing.md | 2 +- generated-doc/out/contributing.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 62a4d95101..a81ff3c535 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ ![tapir](https://github.com/softwaremill/tapir/raw/master/banner.png) -# Happy 1.0 birthday, tapir! +# Welcome! -[![Join the chat at https://gitter.im/softwaremill/tapir](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/softwaremill/tapir?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Ideas, suggestions, problems, questions](https://img.shields.io/badge/Discourse-ask%20question-blue)](https://softwaremill.community/c/tapir) [![CI](https://github.com/softwaremill/tapir/workflows/CI/badge.svg)](https://github.com/softwaremill/tapir/actions?query=workflow%3A%22CI%22) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.sttp.tapir/tapir-core_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.sttp.tapir/tapir-core_2.13) @@ -174,7 +174,7 @@ All suggestions welcome :) See the list of [issues](https://github.com/softwaremill/tapir/issues) and pick one! Or report your own. If you are having doubts on the *why* or *how* something works, don't hesitate to ask a question on -[gitter](https://gitter.im/softwaremill/tapir) or via github. This probably means that the documentation, scaladocs or +[discourse](https://softwaremill.community/c/tapir) or via github. This probably means that the documentation, scaladocs or code is unclear and be improved for the benefit of all. The `core` module needs to remain binary-compatible with earlier versions. To check if your changes meet this requirement, diff --git a/doc/contributing.md b/doc/contributing.md index a3c61b94eb..e4e5421705 100644 --- a/doc/contributing.md +++ b/doc/contributing.md @@ -6,7 +6,7 @@ If you'd like to contribute, see the list of [issues](https://github.com/softwar Or report your own. If you have an idea you'd like to discuss, that's always a good option. If you are having doubts on the *why* or *how* something works, don't hesitate to ask a question on -[gitter](https://gitter.im/softwaremill/tapir) or via github. This probably means that the documentation, scaladocs or +[discourse](https://softwaremill.community/c/tapir) or via github. This probably means that the documentation, scaladocs or code is unclear and can be improved for the benefit of all. ## Acknowledgments diff --git a/generated-doc/out/contributing.md b/generated-doc/out/contributing.md index a3c61b94eb..e4e5421705 100644 --- a/generated-doc/out/contributing.md +++ b/generated-doc/out/contributing.md @@ -6,7 +6,7 @@ If you'd like to contribute, see the list of [issues](https://github.com/softwar Or report your own. If you have an idea you'd like to discuss, that's always a good option. If you are having doubts on the *why* or *how* something works, don't hesitate to ask a question on -[gitter](https://gitter.im/softwaremill/tapir) or via github. This probably means that the documentation, scaladocs or +[discourse](https://softwaremill.community/c/tapir) or via github. This probably means that the documentation, scaladocs or code is unclear and can be improved for the benefit of all. ## Acknowledgments From d09e35899b7a920e09002eed3f07dd8d9a29abe4 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Tue, 13 Dec 2022 00:16:55 +0000 Subject: [PATCH 03/12] Update netty-all to 4.1.86.Final --- project/Versions.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Versions.scala b/project/Versions.scala index fcf160557c..76bbbfa5a6 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -53,5 +53,5 @@ object Versions { val openTelemetry = "1.20.1" val mockServer = "5.14.0" val dogstatsdClient = "4.1.0" - val nettyAll = "4.1.82.Final" + val nettyAll = "4.1.86.Final" } From 9eb04c49964c862cec18aff381ed40963a1b745d Mon Sep 17 00:00:00 2001 From: Krzysztof Pado Date: Tue, 13 Dec 2022 00:06:00 -0800 Subject: [PATCH 04/12] Build zio modules for ScalaJS --- build.sbt | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/build.sbt b/build.sbt index d915b69971..738aacd5f3 100644 --- a/build.sbt +++ b/build.sbt @@ -567,14 +567,18 @@ lazy val zio1: ProjectMatrix = (projectMatrix in file("integrations/zio1")) name := "tapir-zio1", testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"), libraryDependencies ++= Seq( - "dev.zio" %% "zio" % Versions.zio1, - "dev.zio" %% "zio-streams" % Versions.zio1, - "dev.zio" %% "zio-test" % Versions.zio1 % Test, - "dev.zio" %% "zio-test-sbt" % Versions.zio1 % Test, - "com.softwaremill.sttp.shared" %% "zio1" % Versions.sttpShared + "dev.zio" %%% "zio" % Versions.zio1, + "dev.zio" %%% "zio-streams" % Versions.zio1, + "dev.zio" %%% "zio-test" % Versions.zio1 % Test, + "dev.zio" %%% "zio-test-sbt" % Versions.zio1 % Test, + "com.softwaremill.sttp.shared" %%% "zio1" % Versions.sttpShared ) ) .jvmPlatform(scalaVersions = scala2And3Versions) + .jsPlatform( + scalaVersions = scala2And3Versions, + settings = commonJsSettings + ) .dependsOn(core, serverCore % Test) lazy val zio: ProjectMatrix = (projectMatrix in file("integrations/zio")) @@ -583,14 +587,18 @@ lazy val zio: ProjectMatrix = (projectMatrix in file("integrations/zio")) name := "tapir-zio", testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"), libraryDependencies ++= Seq( - "dev.zio" %% "zio" % Versions.zio, - "dev.zio" %% "zio-streams" % Versions.zio, - "dev.zio" %% "zio-test" % Versions.zio % Test, - "dev.zio" %% "zio-test-sbt" % Versions.zio % Test, - "com.softwaremill.sttp.shared" %% "zio" % Versions.sttpShared + "dev.zio" %%% "zio" % Versions.zio, + "dev.zio" %%% "zio-streams" % Versions.zio, + "dev.zio" %%% "zio-test" % Versions.zio % Test, + "dev.zio" %%% "zio-test-sbt" % Versions.zio % Test, + "com.softwaremill.sttp.shared" %%% "zio" % Versions.sttpShared ) ) .jvmPlatform(scalaVersions = scala2And3Versions) + .jsPlatform( + scalaVersions = scala2And3Versions, + settings = commonJsSettings + ) .dependsOn(core, serverCore % Test) lazy val derevo: ProjectMatrix = (projectMatrix in file("integrations/derevo")) From 690440b9619ad5ac3859fd1a33b8f54a51a6d760 Mon Sep 17 00:00:00 2001 From: adamw Date: Wed, 14 Dec 2022 14:47:09 +0100 Subject: [PATCH 05/12] Add an attribute to customise default decode failure handling for individual inputs/outputs --- doc/server/errors.md | 15 ++++- .../decodefailure/DecodeFailureHandler.scala | 56 ++++++++++++++----- .../tapir/server/tests/ServerBasicTests.scala | 39 ++++++++++++- 3 files changed, 92 insertions(+), 18 deletions(-) diff --git a/doc/server/errors.md b/doc/server/errors.md index 181b751ab8..7da66c7d2c 100644 --- a/doc/server/errors.md +++ b/doc/server/errors.md @@ -114,7 +114,20 @@ swapped, e.g. to return responses in a different format (other than plain text), The default decode failure handler also has the option to return a `400 Bad Request`, instead of a no-match (ultimately leading to a `404 Not Found`), when the "shape" of the path matches (that is, the number of segments in the request and endpoint's paths are the same), but when decoding some part of the path ends in an error. See the -`badRequestOnPathErrorIfPathShapeMatches` in `ServerDefaults`. +scaladoc for `DefaultDecodeFailureHandler.default` and the `badRequestOnPathErrorIfPathShapeMatches` and +`badRequestOnPathInvalidIfPathShapeMatches` parameters of `DefaultDecodeFailureHandler.response`. + +When using the `DefaultDecodeFailureHandler`, decode failure handling can be overriden on a per-input/output basis, +by setting an attribute. For example: + +```scala mdoc:compile-only +import sttp.tapir._ +// bringing into scope the onDecodeFailureBadRequest extension method +import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler.OnDecodeFailure._ + +// be default, when the customer_id is not an int, the next endpoint would be tried; here, we always return a bad request +endpoint.in("customer" / path[Int]("customer_id").onDecodeFailureBadRequest) +``` ## Customising how error messages are rendered diff --git a/server/core/src/main/scala/sttp/tapir/server/interceptor/decodefailure/DecodeFailureHandler.scala b/server/core/src/main/scala/sttp/tapir/server/interceptor/decodefailure/DecodeFailureHandler.scala index c20c201a61..0abe688ce7 100644 --- a/server/core/src/main/scala/sttp/tapir/server/interceptor/decodefailure/DecodeFailureHandler.scala +++ b/server/core/src/main/scala/sttp/tapir/server/interceptor/decodefailure/DecodeFailureHandler.scala @@ -61,7 +61,21 @@ object DefaultDecodeFailureHandler { * The error messages contain information about the source of the decode error, and optionally the validation error detail that caused * the failure. * + * The default decode failure handler can be customised by providing alternate functions for deciding whether a response should be sent, + * creating the error message and creating the response. + * + * Furthermore, how decode failures are handled can be adjusted globally by changing the flags passed to [[respond]]. By default, if the + * shape of the path for an endpoint matches the request, but decoding a path capture causes an error (e.g. a `path[Int]("amount")` + * cannot be parsed), the next endpoint is tried. However, if there's a validation error (e.g. a `path[Kind]("kind")`, where `Kind` is an + * enum, and a value outside the enumeration values is provided), a 400 response is sent. + * + * Finally, behavior can be adjusted per-endpoint-input, by setting an attribute. Import the [[OnDecodeFailure]] object and use the + * [[OnDecodeFailure.RichEndpointTransput.onDecodeFailureBadRequest]] and + * [[OnDecodeFailure.RichEndpointTransput.onDecodeFailureNextEndpoint]] extension methods. + * * This is only used for failures that occur when decoding inputs, not for exceptions that happen when the server logic is invoked. + * Exceptions can be either handled by the server logic, and converted to an error output value. Uncaught exceptions can be handled using + * the [[sttp.tapir.server.interceptor.exception.ExceptionInterceptor]]. */ val default: DefaultDecodeFailureHandler = DefaultDecodeFailureHandler( respond(_, badRequestOnPathErrorIfPathShapeMatches = false, badRequestOnPathInvalidIfPathShapeMatches = true), @@ -95,35 +109,38 @@ object DefaultDecodeFailureHandler { badRequestOnPathInvalidIfPathShapeMatches: Boolean ): Option[(StatusCode, List[Header])] = { failingInput(ctx) match { - case _: EndpointInput.Query[_] => Some(onlyStatus(StatusCode.BadRequest)) - case _: EndpointInput.QueryParams[_] => Some(onlyStatus(StatusCode.BadRequest)) - case _: EndpointInput.Cookie[_] => Some(onlyStatus(StatusCode.BadRequest)) + case i: EndpointTransput.Atom[_] if i.attribute(OnDecodeFailure.key).contains(OnDecodeFailure(true)) => respondBadRequest + case i: EndpointTransput.Atom[_] if i.attribute(OnDecodeFailure.key).contains(OnDecodeFailure(false)) => None + case _: EndpointInput.Query[_] => respondBadRequest + case _: EndpointInput.QueryParams[_] => respondBadRequest + case _: EndpointInput.Cookie[_] => respondBadRequest case h: EndpointIO.Header[_] if ctx.failure.isInstanceOf[DecodeResult.Mismatch] && h.name == HeaderNames.ContentType => - Some(onlyStatus(StatusCode.UnsupportedMediaType)) - case _: EndpointIO.Header[_] => Some(onlyStatus(StatusCode.BadRequest)) + respondUnsupportedMediaType + case _: EndpointIO.Header[_] => respondBadRequest case fh: EndpointIO.FixedHeader[_] if ctx.failure.isInstanceOf[DecodeResult.Mismatch] && fh.h.name == HeaderNames.ContentType => - Some(onlyStatus(StatusCode.UnsupportedMediaType)) - case _: EndpointIO.FixedHeader[_] => Some(onlyStatus(StatusCode.BadRequest)) - case _: EndpointIO.Headers[_] => Some(onlyStatus(StatusCode.BadRequest)) - case _: EndpointIO.Body[_, _] => Some(onlyStatus(StatusCode.BadRequest)) - case _: EndpointIO.OneOfBody[_, _] if ctx.failure.isInstanceOf[DecodeResult.Mismatch] => - Some(onlyStatus(StatusCode.UnsupportedMediaType)) - case _: EndpointIO.StreamBodyWrapper[_, _] => Some(onlyStatus(StatusCode.BadRequest)) + respondUnsupportedMediaType + case _: EndpointIO.FixedHeader[_] => respondBadRequest + case _: EndpointIO.Headers[_] => respondBadRequest + case _: EndpointIO.Body[_, _] => respondBadRequest + case _: EndpointIO.OneOfBody[_, _] if ctx.failure.isInstanceOf[DecodeResult.Mismatch] => respondUnsupportedMediaType + case _: EndpointIO.StreamBodyWrapper[_, _] => respondBadRequest // we assume that the only decode failure that might happen during path segment decoding is an error // a non-standard path decoder might return Missing/Multiple/Mismatch, but that would be indistinguishable from // a path shape mismatch case _: EndpointInput.PathCapture[_] if (badRequestOnPathErrorIfPathShapeMatches && ctx.failure.isInstanceOf[DecodeResult.Error]) || (badRequestOnPathInvalidIfPathShapeMatches && ctx.failure.isInstanceOf[DecodeResult.InvalidValue]) => - Some(onlyStatus(StatusCode.BadRequest)) + respondBadRequest // if the failing input contains an authentication input (potentially nested), sending its challenge case FirstAuth(a) => Some((StatusCode.Unauthorized, Header.wwwAuthenticate(a.challenge))) // other basic endpoints - the request doesn't match, but not returning a response (trying other endpoints) case _: EndpointInput.Basic[_] => None // all other inputs (tuples, mapped) - responding with bad request - case _ => Some(onlyStatus(StatusCode.BadRequest)) + case _ => respondBadRequest } } + private val respondBadRequest = Some(onlyStatus(StatusCode.BadRequest)) + private val respondUnsupportedMediaType = Some(onlyStatus(StatusCode.UnsupportedMediaType)) def respondNotFoundIfHasAuth( ctx: DecodeFailureContext, @@ -285,4 +302,15 @@ object DefaultDecodeFailureHandler { case _ => v } } + + private[decodefailure] case class OnDecodeFailure(value: Boolean) extends AnyVal + + object OnDecodeFailure { + private[decodefailure] val key: AttributeKey[OnDecodeFailure] = AttributeKey[OnDecodeFailure] + + implicit class RichEndpointTransput[ET <: EndpointTransput.Atom[_]](val et: ET) extends AnyVal { + def onDecodeFailureBadRequest: ET = et.attribute(key, OnDecodeFailure(true)).asInstanceOf[ET] + def onDecodeFailureNextEndpoint: ET = et.attribute(key, OnDecodeFailure(false)).asInstanceOf[ET] + } + } } diff --git a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala index be802755f9..e2e1401609 100644 --- a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala +++ b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala @@ -40,7 +40,7 @@ class ServerBasicTests[F[_], OPTIONS, ROUTE]( methodMatchingTests() ++ pathMatchingTests() ++ pathMatchingMultipleEndpoints() ++ - pathShapeMatchingTests() ++ + customiseDecodeFailureHandlerTests() ++ serverSecurityLogicTests() ++ (if (inputStreamSupport) inputStreamTests() else Nil) ++ exceptionTests() @@ -593,10 +593,10 @@ class ServerBasicTests[F[_], OPTIONS, ROUTE]( } ) - def pathShapeMatchingTests(): List[Test] = List( + def customiseDecodeFailureHandlerTests(): List[Test] = List( testServer( in_path_fixed_capture_fixed_capture, - "Returns 400 if path 'shape' matches, but failed to parse a path parameter", + "Returns 400 if path 'shape' matches, but failed to parse a path parameter, using a custom decode failure handler", _.decodeFailureHandler(decodeFailureHandlerBadRequestOnPathFailure) )(_ => pureResult(Either.right[Unit, Unit](()))) { (backend, baseUri) => basicRequest.get(uri"$baseUri/customer/asd/orders/2").send(backend).map { response => @@ -615,6 +615,39 @@ class ServerBasicTests[F[_], OPTIONS, ROUTE]( .get(uri"$baseUri/customer/asd/orders/2/xyz") .send(backend) .map(response => response.code shouldBe StatusCode.NotFound) + }, { + import DefaultDecodeFailureHandler.OnDecodeFailure._ + testServer( + endpoint.get.in("customer" / path[Int]("customer_id").onDecodeFailureBadRequest), + "Returns 400 if path 'shape' matches, but failed to parse a path parameter, using .badRequestOnDecodeFailure" + )(_ => pureResult(Either.right[Unit, Unit](()))) { (backend, baseUri) => + basicRequest.get(uri"$baseUri/customer/asd").send(backend).map { response => + response.body shouldBe Left("Invalid value for: path parameter customer_id") + response.code shouldBe StatusCode.BadRequest + } + } + }, { + import DefaultDecodeFailureHandler.OnDecodeFailure._ + testServer( + "Tries next endpoint if path 'shape' matches, but validation fails, using .badRequestOnDecodeFailure", + NonEmptyList.of( + route( + endpoint.get + .in("customer" / path[Int]("customer_id").validate(Validator.min(10)).onDecodeFailureNextEndpoint) + .out(stringBody) + .serverLogic((_: Int) => pureResult("e1".asRight[Unit])) + ), + route( + endpoint.get + .in("customer" / path[String]("customer_id")) + .out(stringBody) + .serverLogic((_: String) => pureResult("e2".asRight[Unit])) + ) + ) + ) { (backend, baseUri) => + basicStringRequest.get(uri"$baseUri/customer/20").send(backend).map(_.body shouldBe "e1") >> + basicStringRequest.get(uri"$baseUri/customer/2").send(backend).map(_.body shouldBe "e2") + } } ) From 5908388270fc8c93de63be018eae77e3da7f8cdf Mon Sep 17 00:00:00 2001 From: adamw Date: Wed, 14 Dec 2022 14:52:35 +0100 Subject: [PATCH 06/12] More docs --- doc/server/errors.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/doc/server/errors.md b/doc/server/errors.md index 7da66c7d2c..7a0525623a 100644 --- a/doc/server/errors.md +++ b/doc/server/errors.md @@ -115,7 +115,27 @@ The default decode failure handler also has the option to return a `400 Bad Requ leading to a `404 Not Found`), when the "shape" of the path matches (that is, the number of segments in the request and endpoint's paths are the same), but when decoding some part of the path ends in an error. See the scaladoc for `DefaultDecodeFailureHandler.default` and the `badRequestOnPathErrorIfPathShapeMatches` and -`badRequestOnPathInvalidIfPathShapeMatches` parameters of `DefaultDecodeFailureHandler.response`. +`badRequestOnPathInvalidIfPathShapeMatches` parameters of `DefaultDecodeFailureHandler.response`. For example: + +```scala mdoc:compile-only +import sttp.tapir._ +import sttp.tapir.server.akkahttp.AkkaHttpServerOptions +import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler +import scala.concurrent.ExecutionContext.Implicits.global + +val myDecodeFailureHandler = DefaultDecodeFailureHandler.default.copy( + respond = DefaultDecodeFailureHandler.respond( + _, + badRequestOnPathErrorIfPathShapeMatches = true, + badRequestOnPathInvalidIfPathShapeMatches = true + ) +) + +val myServerOptions: AkkaHttpServerOptions = AkkaHttpServerOptions + .customiseInterceptors + .decodeFailureHandler(myDecodeFailureHandler) + .options +``` When using the `DefaultDecodeFailureHandler`, decode failure handling can be overriden on a per-input/output basis, by setting an attribute. For example: From 5df149d22bfa332c5b42f53d721b4989d4a9c8c7 Mon Sep 17 00:00:00 2001 From: adamw Date: Wed, 14 Dec 2022 14:53:45 +0100 Subject: [PATCH 07/12] More docs --- doc/server/errors.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/doc/server/errors.md b/doc/server/errors.md index 7a0525623a..6a7c57b3aa 100644 --- a/doc/server/errors.md +++ b/doc/server/errors.md @@ -112,10 +112,9 @@ an error or return a "no match", create error messages and create the response. swapped, e.g. to return responses in a different format (other than plain text), or customise the error messages. The default decode failure handler also has the option to return a `400 Bad Request`, instead of a no-match (ultimately -leading to a `404 Not Found`), when the "shape" of the path matches (that is, the number of segments in the request -and endpoint's paths are the same), but when decoding some part of the path ends in an error. See the -scaladoc for `DefaultDecodeFailureHandler.default` and the `badRequestOnPathErrorIfPathShapeMatches` and -`badRequestOnPathInvalidIfPathShapeMatches` parameters of `DefaultDecodeFailureHandler.response`. For example: +leading to a `404 Not Found`), when the "shape" of the path matches (that is, the constant parts and number of segments +in the request and endpoint's paths are the same), but when decoding some part of the path ends in an error. See the +scaladoc for `DefaultDecodeFailureHandler.default` and parameters of `DefaultDecodeFailureHandler.response`. For example: ```scala mdoc:compile-only import sttp.tapir._ @@ -137,8 +136,8 @@ val myServerOptions: AkkaHttpServerOptions = AkkaHttpServerOptions .options ``` -When using the `DefaultDecodeFailureHandler`, decode failure handling can be overriden on a per-input/output basis, -by setting an attribute. For example: +Moreover, when using the `DefaultDecodeFailureHandler`, decode failure handling can be overriden on a per-input/output +basis, by setting an attribute. For example: ```scala mdoc:compile-only import sttp.tapir._ From f1eaebd36319bfd2d4e7510adaf393bf7b3c722e Mon Sep 17 00:00:00 2001 From: adamw Date: Wed, 14 Dec 2022 15:14:46 +0100 Subject: [PATCH 08/12] Fix for 2.12 --- .../decodefailure/DecodeFailureHandler.scala | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/server/core/src/main/scala/sttp/tapir/server/interceptor/decodefailure/DecodeFailureHandler.scala b/server/core/src/main/scala/sttp/tapir/server/interceptor/decodefailure/DecodeFailureHandler.scala index 0abe688ce7..c118e5c3ba 100644 --- a/server/core/src/main/scala/sttp/tapir/server/interceptor/decodefailure/DecodeFailureHandler.scala +++ b/server/core/src/main/scala/sttp/tapir/server/interceptor/decodefailure/DecodeFailureHandler.scala @@ -109,11 +109,11 @@ object DefaultDecodeFailureHandler { badRequestOnPathInvalidIfPathShapeMatches: Boolean ): Option[(StatusCode, List[Header])] = { failingInput(ctx) match { - case i: EndpointTransput.Atom[_] if i.attribute(OnDecodeFailure.key).contains(OnDecodeFailure(true)) => respondBadRequest - case i: EndpointTransput.Atom[_] if i.attribute(OnDecodeFailure.key).contains(OnDecodeFailure(false)) => None - case _: EndpointInput.Query[_] => respondBadRequest - case _: EndpointInput.QueryParams[_] => respondBadRequest - case _: EndpointInput.Cookie[_] => respondBadRequest + case i: EndpointTransput.Atom[_] if i.attribute(OnDecodeFailure.key).contains(OnDecodeFailureAttribute(true)) => respondBadRequest + case i: EndpointTransput.Atom[_] if i.attribute(OnDecodeFailure.key).contains(OnDecodeFailureAttribute(false)) => None + case _: EndpointInput.Query[_] => respondBadRequest + case _: EndpointInput.QueryParams[_] => respondBadRequest + case _: EndpointInput.Cookie[_] => respondBadRequest case h: EndpointIO.Header[_] if ctx.failure.isInstanceOf[DecodeResult.Mismatch] && h.name == HeaderNames.ContentType => respondUnsupportedMediaType case _: EndpointIO.Header[_] => respondBadRequest @@ -303,14 +303,14 @@ object DefaultDecodeFailureHandler { } } - private[decodefailure] case class OnDecodeFailure(value: Boolean) extends AnyVal + private[decodefailure] case class OnDecodeFailureAttribute(value: Boolean) extends AnyVal object OnDecodeFailure { - private[decodefailure] val key: AttributeKey[OnDecodeFailure] = AttributeKey[OnDecodeFailure] + private[decodefailure] val key: AttributeKey[OnDecodeFailureAttribute] = AttributeKey[OnDecodeFailureAttribute] implicit class RichEndpointTransput[ET <: EndpointTransput.Atom[_]](val et: ET) extends AnyVal { - def onDecodeFailureBadRequest: ET = et.attribute(key, OnDecodeFailure(true)).asInstanceOf[ET] - def onDecodeFailureNextEndpoint: ET = et.attribute(key, OnDecodeFailure(false)).asInstanceOf[ET] + def onDecodeFailureBadRequest: ET = et.attribute(key, OnDecodeFailureAttribute(true)).asInstanceOf[ET] + def onDecodeFailureNextEndpoint: ET = et.attribute(key, OnDecodeFailureAttribute(false)).asInstanceOf[ET] } } } From 8608a45010b723aa4ce32510e6143521a194c2f5 Mon Sep 17 00:00:00 2001 From: adamw Date: Wed, 14 Dec 2022 16:08:50 +0100 Subject: [PATCH 09/12] Fix test for Armeria --- .../tapir/server/tests/ServerBasicTests.scala | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala index e2e1401609..426b7293ef 100644 --- a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala +++ b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala @@ -629,19 +629,19 @@ class ServerBasicTests[F[_], OPTIONS, ROUTE]( }, { import DefaultDecodeFailureHandler.OnDecodeFailure._ testServer( - "Tries next endpoint if path 'shape' matches, but validation fails, using .badRequestOnDecodeFailure", + "Tries next endpoint if path 'shape' matches, but validation fails, using .onDecodeFailureNextEndpoint", NonEmptyList.of( route( - endpoint.get - .in("customer" / path[Int]("customer_id").validate(Validator.min(10)).onDecodeFailureNextEndpoint) - .out(stringBody) - .serverLogic((_: Int) => pureResult("e1".asRight[Unit])) - ), - route( - endpoint.get - .in("customer" / path[String]("customer_id")) - .out(stringBody) - .serverLogic((_: String) => pureResult("e2".asRight[Unit])) + List( + endpoint.get + .in("customer" / path[Int]("customer_id").validate(Validator.min(10)).onDecodeFailureNextEndpoint) + .out(stringBody) + .serverLogic((_: Int) => pureResult("e1".asRight[Unit])), + endpoint.get + .in("customer" / path[String]("customer_id")) + .out(stringBody) + .serverLogic((_: String) => pureResult("e2".asRight[Unit])) + ) ) ) ) { (backend, baseUri) => From 229426648c269953400d7b6fd5e364af8a141820 Mon Sep 17 00:00:00 2001 From: adamw Date: Wed, 14 Dec 2022 21:05:49 +0100 Subject: [PATCH 10/12] Typo --- doc/server/errors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/server/errors.md b/doc/server/errors.md index 6a7c57b3aa..8d947ffb4a 100644 --- a/doc/server/errors.md +++ b/doc/server/errors.md @@ -144,7 +144,7 @@ import sttp.tapir._ // bringing into scope the onDecodeFailureBadRequest extension method import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler.OnDecodeFailure._ -// be default, when the customer_id is not an int, the next endpoint would be tried; here, we always return a bad request +// by default, when the customer_id is not an int, the next endpoint would be tried; here, we always return a bad request endpoint.in("customer" / path[Int]("customer_id").onDecodeFailureBadRequest) ``` From 38cacebbf43691c6f8fe2aef10d3b65083cbf373 Mon Sep 17 00:00:00 2001 From: scala-steward Date: Thu, 15 Dec 2022 00:05:58 +0000 Subject: [PATCH 11/12] Update sbt-softwaremill-browser-test-js, ... to 2.0.12 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 3015f40725..ce04cbd32a 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ -val sbtSoftwareMillVersion = "2.0.9" +val sbtSoftwareMillVersion = "2.0.12" addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-common" % sbtSoftwareMillVersion) addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" % sbtSoftwareMillVersion) addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-browser-test-js" % sbtSoftwareMillVersion) From cbbb0a3390d2dfef8a06ac0f02749550933e8488 Mon Sep 17 00:00:00 2001 From: adamw Date: Thu, 15 Dec 2022 08:32:20 +0100 Subject: [PATCH 12/12] Fix for scala 3 --- .../main/scala/sttp/tapir/server/tests/ServerBasicTests.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala index 426b7293ef..90dbe77a4c 100644 --- a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala +++ b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerBasicTests.scala @@ -636,11 +636,11 @@ class ServerBasicTests[F[_], OPTIONS, ROUTE]( endpoint.get .in("customer" / path[Int]("customer_id").validate(Validator.min(10)).onDecodeFailureNextEndpoint) .out(stringBody) - .serverLogic((_: Int) => pureResult("e1".asRight[Unit])), + .serverLogic[F]((_: Int) => pureResult("e1".asRight[Unit])), endpoint.get .in("customer" / path[String]("customer_id")) .out(stringBody) - .serverLogic((_: String) => pureResult("e2".asRight[Unit])) + .serverLogic[F]((_: String) => pureResult("e2".asRight[Unit])) ) ) )