diff --git a/client/sttp-client/src/main/scala/sttp/tapir/client/sttp/EndpointToSttpClient.scala b/client/sttp-client/src/main/scala/sttp/tapir/client/sttp/EndpointToSttpClient.scala index ab1191b122..b906d56dff 100644 --- a/client/sttp-client/src/main/scala/sttp/tapir/client/sttp/EndpointToSttpClient.scala +++ b/client/sttp-client/src/main/scala/sttp/tapir/client/sttp/EndpointToSttpClient.scala @@ -3,6 +3,7 @@ package sttp.tapir.client.sttp import sttp.capabilities.Streams import sttp.client3._ import sttp.model._ +import sttp.shared.Identity import sttp.tapir.Codec.PlainCodec import sttp.tapir._ import sttp.tapir.client.ClientOutputParams diff --git a/core/src/main/scala/sttp/tapir/Endpoint.scala b/core/src/main/scala/sttp/tapir/Endpoint.scala index 70c9641ce9..5c5872064e 100644 --- a/core/src/main/scala/sttp/tapir/Endpoint.scala +++ b/core/src/main/scala/sttp/tapir/Endpoint.scala @@ -2,12 +2,14 @@ package sttp.tapir import sttp.capabilities.WebSockets import sttp.model.Method +import sttp.monad.IdentityMonad import sttp.monad.syntax._ +import sttp.shared.Identity import sttp.tapir.EndpointInput.{FixedMethod, PathCapture, Query} import sttp.tapir.EndpointOutput.OneOfVariant import sttp.tapir.internal._ import sttp.tapir.macros.{EndpointErrorOutputsMacros, EndpointInputsMacros, EndpointOutputsMacros, EndpointSecurityInputsMacros} -import sttp.tapir.server.{PartialServerEndpoint, PartialServerEndpointWithSecurityOutput, ServerEndpoint} +import sttp.tapir.server.{PartialServerEndpoint, PartialServerEndpointSync, PartialServerEndpointWithSecurityOutput, PartialServerEndpointWithSecurityOutputSync, ServerEndpoint} import sttp.tapir.typelevel.{ErasureSameAsType, ParamConcat} import scala.reflect.ClassTag @@ -23,7 +25,8 @@ import scala.reflect.ClassTag * When interpreting an endpoint as a server, the inputs are decoded and the security logic is run first, before decoding the body in the * regular inputs. This allows short-circuiting further processing in case security checks fail. Server logic can be provided using * [[EndpointServerLogicOps.serverSecurityLogic]] variants for secure endpoints, and [[EndpointServerLogicOps.serverLogic]] variants for - * public endpoints. + * public endpoints; when using a synchronous server, you can also use the more concise [[EndpointServerLogicOps.handle]] methods, which + * work the save as above, but have the "effect" type fixed to [[Identity]]. * * A concise description of an endpoint can be generated using the [[EndpointMetaOps.show]] method. * @@ -582,6 +585,108 @@ trait EndpointServerLogicOps[A, I, E, O, -R] { outer: Endpoint[A, I, E, O, R] => } ) } + + // Direct-style + + /** Direct-style variant of [[serverLogic]], using the [[Identity]] "effect". */ + def handle(f: I => Either[E, O])(implicit aIsUnit: A =:= Unit): ServerEndpoint.Full[Unit, Unit, I, E, O, R, Identity] = + serverLogic[Identity](f) + + /** Direct-style variant of [[serverLogicSuccess]], using the [[Identity]] "effect". */ + def handleSuccess(f: I => O)(implicit aIsUnit: A =:= Unit): ServerEndpoint.Full[Unit, Unit, I, E, O, R, Identity] = + serverLogicSuccess[Identity](f) + + /** Direct-style variant of [[serverLogicError]], using the [[Identity]] "effect". */ + def handleError(f: I => E)(implicit aIsUnit: A =:= Unit): ServerEndpoint.Full[Unit, Unit, I, E, O, R, Identity] = + serverLogicError[Identity](f) + + /** Direct-style variant of [[serverLogicRecoverErrors]], using the [[Identity]] "effect". */ + def handleRecoverErrors( + f: I => O + )(implicit eIsThrowable: E <:< Throwable, eClassTag: ClassTag[E], aIsUnit: A =:= Unit): ServerEndpoint.Full[Unit, Unit, I, E, O, R, Identity] = + serverLogicRecoverErrors[Identity](f) + + /** Direct-style variant of [[serverLogicOption]], using the [[Identity]] "effect". */ + def handleOption( + f: I => Option[O] + )(implicit aIsUnit: A =:= Unit, eIsUnit: E =:= Unit): ServerEndpoint.Full[Unit, Unit, I, Unit, O, R, Identity] = + serverLogicOption[Identity](f) + + // + + /** Direct-style variant of [[serverSecurityLogic]], using the [[Identity]] "effect". */ + def handleSecurity[PRINCIPAL](f: A => Either[E, PRINCIPAL]): PartialServerEndpointSync[A, PRINCIPAL, I, E, O, R] = + PartialServerEndpointSync(this, f) + + /** Direct-style variant of [[serverSecurityLogicSuccess]], using the [[Identity]] "effect". */ + def handleSecuritySuccess[PRINCIPAL](f: A => PRINCIPAL): PartialServerEndpointSync[A, PRINCIPAL, I, E, O, R] = + PartialServerEndpointSync(this, a => Right(f(a))) + + /** Direct-style variant of [[serverSecurityLogicError]], using the [[Identity]] "effect". */ + def handleSecurityError[PRINCIPAL](f: A => E): PartialServerEndpointSync[A, PRINCIPAL, I, E, O, R] = + PartialServerEndpointSync(this, a => Left(f(a))) + + /** Direct-style variant of [[serverSecurityLogicRecoverErrors]], using the [[Identity]] "effect". */ + def handleSecurityRecoverErrors[PRINCIPAL]( + f: A => PRINCIPAL + )(implicit eIsThrowable: E <:< Throwable, eClassTag: ClassTag[E]): PartialServerEndpointSync[A, PRINCIPAL, I, E, O, R] = + PartialServerEndpointSync(this, recoverErrors1[A, E, PRINCIPAL, Identity](f)(implicitly, implicitly)(IdentityMonad)) + + /** Direct-style variant of [[serverSecurityLogicOption]], using the [[Identity]] "effect". */ + def handleSecurityOption[PRINCIPAL](f: A => Option[PRINCIPAL])(implicit + eIsUnit: E =:= Unit + ): PartialServerEndpointSync[A, PRINCIPAL, I, Unit, O, R] = { + PartialServerEndpointSync( + this.asInstanceOf[Endpoint[A, I, Unit, O, R]], + a => + f(a) match { + case None => Left(()) + case Some(v) => Right(v) + } + ) + } + + // + + /** Direct-style variant of [[serverSecurityLogicWithOutput]], using the [[Identity]] "effect". */ + def handleSecurityWithOutput[PRINCIPAL]( + f: A => Either[E, (O, PRINCIPAL)] + ): PartialServerEndpointWithSecurityOutputSync[A, PRINCIPAL, I, E, O, Unit, R] = + PartialServerEndpointWithSecurityOutputSync(this.output, this.copy(output = emptyOutput), f) + + /** Direct-style variant of [[serverSecurityLogicSuccessWithOutput]], using the [[Identity]] "effect". */ + def handleSecuritySuccessWithOutput[PRINCIPAL]( + f: A => (O, PRINCIPAL) + ): PartialServerEndpointWithSecurityOutputSync[A, PRINCIPAL, I, E, O, Unit, R] = + PartialServerEndpointWithSecurityOutputSync(this.output, this.copy(output = emptyOutput), a => Right(f(a))) + + /** Direct-style variant of [[serverSecurityLogicRecoverErrorsWithOutput]], using the [[Identity]] "effect". */ + def handleSecurityRecoverErrorsWithOutput[PRINCIPAL]( + f: A => (O, PRINCIPAL) + )(implicit + eIsThrowable: E <:< Throwable, + eClassTag: ClassTag[E] + ): PartialServerEndpointWithSecurityOutputSync[A, PRINCIPAL, I, E, O, Unit, R] = + PartialServerEndpointWithSecurityOutputSync( + this.output, + this.copy(output = emptyOutput), + recoverErrors1[A, E, (O, PRINCIPAL), Identity](f)(implicitly, implicitly)(IdentityMonad) + ) + + /** Direct-style variant of [[serverSecurityLogicOptionWithOutput]], using the [[Identity]] "effect". */ + def handleSecurityOptionWithOutput[PRINCIPAL]( + f: A => Option[(O, PRINCIPAL)] + )(implicit eIsUnit: E =:= Unit): PartialServerEndpointWithSecurityOutputSync[A, PRINCIPAL, I, Unit, O, Unit, R] = { + PartialServerEndpointWithSecurityOutputSync( + this.output, + this.copy(output = emptyOutput).asInstanceOf[Endpoint[A, I, Unit, Unit, R]], + a => + f(a) match { + case None => Left(()) + case Some(v) => Right(v) + } + ) + } } case class EndpointInfo( diff --git a/core/src/main/scala/sttp/tapir/server/PartialServerEndpointSync.scala b/core/src/main/scala/sttp/tapir/server/PartialServerEndpointSync.scala new file mode 100644 index 0000000000..8f4b090cda --- /dev/null +++ b/core/src/main/scala/sttp/tapir/server/PartialServerEndpointSync.scala @@ -0,0 +1,139 @@ +package sttp.tapir.server + +import sttp.shared.Identity +import sttp.tapir._ +import sttp.tapir.internal._ + +import scala.reflect.ClassTag + +/** Direct-style variant of [[PartialServerEndpoint]], using the [[Identity]] "effect". */ +case class PartialServerEndpointSync[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, -R]( + endpoint: Endpoint[SECURITY_INPUT, INPUT, ERROR_OUTPUT, OUTPUT, R], + securityLogic: SECURITY_INPUT => Either[ERROR_OUTPUT, PRINCIPAL] +) extends EndpointInputsOps[SECURITY_INPUT, INPUT, ERROR_OUTPUT, OUTPUT, R] + with EndpointOutputsOps[SECURITY_INPUT, INPUT, ERROR_OUTPUT, OUTPUT, R] + with EndpointErrorOutputVariantsOps[SECURITY_INPUT, INPUT, ERROR_OUTPUT, OUTPUT, R] + with EndpointInfoOps[R] + with EndpointMetaOps { outer => + override type ThisType[-_R] = PartialServerEndpointSync[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, _R] + override type EndpointType[_A, _I, _E, _O, -_R] = PartialServerEndpointSync[_A, PRINCIPAL, _I, _E, _O, _R] + + override def securityInput: EndpointInput[SECURITY_INPUT] = endpoint.securityInput + override def input: EndpointInput[INPUT] = endpoint.input + override def errorOutput: EndpointOutput[ERROR_OUTPUT] = endpoint.errorOutput + override def output: EndpointOutput[OUTPUT] = endpoint.output + override def info: EndpointInfo = endpoint.info + + override private[tapir] def withInput[I2, R2]( + input: EndpointInput[I2] + ): PartialServerEndpointSync[SECURITY_INPUT, PRINCIPAL, I2, ERROR_OUTPUT, OUTPUT, R with R2] = + copy(endpoint = endpoint.copy(input = input)) + override private[tapir] def withOutput[O2, R2](output: EndpointOutput[O2]) = copy(endpoint = endpoint.copy(output = output)) + override private[tapir] def withErrorOutputVariant[E2, R2]( + errorOutput: EndpointOutput[E2], + embedE: ERROR_OUTPUT => E2 + ): PartialServerEndpointSync[SECURITY_INPUT, PRINCIPAL, INPUT, E2, OUTPUT, R with R2] = + this.copy( + endpoint = endpoint.copy(errorOutput = errorOutput), + securityLogic = a => + securityLogic(a) match { + case Left(e) => Left(embedE(e)) + case Right(o) => Right(o) + } + ) + override private[tapir] def withInfo(info: EndpointInfo) = copy(endpoint = endpoint.copy(info = info)) + + override protected def showType: String = "PartialServerEndpoint" + + def handle( + f: PRINCIPAL => INPUT => Either[ERROR_OUTPUT, OUTPUT] + ): ServerEndpoint.Full[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, R, Identity] = + ServerEndpoint[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, R, Identity](endpoint, _ => securityLogic, _ => f) + + def handleSuccess( + f: PRINCIPAL => INPUT => OUTPUT + ): ServerEndpoint.Full[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, R, Identity] = + ServerEndpoint[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, R, Identity]( + endpoint, + _ => securityLogic, + _ => u => i => Right(f(u)(i)) + ) + + def handleError( + f: PRINCIPAL => INPUT => ERROR_OUTPUT + ): ServerEndpoint.Full[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, R, Identity] = + ServerEndpoint[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, R, Identity]( + endpoint, + _ => securityLogic, + _ => u => i => Left(f(u)(i)) + ) + + def handleRecoverErrors( + f: PRINCIPAL => INPUT => OUTPUT + )(implicit + eIsThrowable: ERROR_OUTPUT <:< Throwable, + eClassTag: ClassTag[ERROR_OUTPUT] + ): ServerEndpoint.Full[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, R, Identity] = + ServerEndpoint[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, R, Identity]( + endpoint, + _ => securityLogic, + recoverErrors2[PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, Identity](f) + ) + + def handleOption(f: PRINCIPAL => INPUT => Option[OUTPUT])(implicit + eIsUnit: ERROR_OUTPUT =:= Unit + ): ServerEndpoint.Full[SECURITY_INPUT, PRINCIPAL, INPUT, Unit, OUTPUT, R, Identity] = + ServerEndpoint[SECURITY_INPUT, PRINCIPAL, INPUT, Unit, OUTPUT, R, Identity]( + endpoint.asInstanceOf[Endpoint[SECURITY_INPUT, INPUT, Unit, OUTPUT, R]], + _ => securityLogic.asInstanceOf[SECURITY_INPUT => Either[Unit, PRINCIPAL]], + _ => + u => + i => + f(u)(i) match { + case None => Left(()) + case Some(v) => Right(v) + } + ) + + /** If the error type is an `Either`, e.g. when using `errorOutEither`, this method accepts server logic that returns either success or + * the `Right` error type. Use of this method avoids having to wrap the returned error in `Right`. + */ + def handleRightErrorOrSuccess[LE, RE]( + f: PRINCIPAL => INPUT => Either[RE, OUTPUT] + )(implicit + eIsEither: Either[LE, RE] =:= ERROR_OUTPUT + ): ServerEndpoint.Full[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, R, Identity] = + ServerEndpoint[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, R, Identity]( + endpoint, + _ => securityLogic, + _ => + u => + i => { + f(u)(i) match { + case Left(e) => Left(Right(e)) + case Right(r) => Right(r) + } + } + ) + + /** If the error type is an `Either`, e.g. when using `errorOutEither`, this method accepts server logic that returns either success or + * the `Left` error type. Use of this method avoids having to wrap the returned error in `Left`. + */ + def handleLeftErrorOrSuccess[LE, RE]( + f: PRINCIPAL => INPUT => Either[LE, OUTPUT] + )(implicit + eIsEither: Either[LE, RE] =:= ERROR_OUTPUT + ): ServerEndpoint.Full[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, R, Identity] = + ServerEndpoint[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, OUTPUT, R, Identity]( + endpoint, + _ => securityLogic, + _ => + u => + i => { + f(u)(i) match { + case Left(e) => Left(Left(e)) + case Right(r) => Right(r) + } + } + ) +} diff --git a/core/src/main/scala/sttp/tapir/server/PartialServerEndpointWithSecurityOutputSync.scala b/core/src/main/scala/sttp/tapir/server/PartialServerEndpointWithSecurityOutputSync.scala new file mode 100644 index 0000000000..7a2074f57e --- /dev/null +++ b/core/src/main/scala/sttp/tapir/server/PartialServerEndpointWithSecurityOutputSync.scala @@ -0,0 +1,111 @@ +package sttp.tapir.server + +import sttp.monad.IdentityMonad +import sttp.shared.Identity +import sttp.tapir._ +import sttp.tapir.internal._ + +import scala.reflect.ClassTag + +/** Direct-style variant of [[PartialServerEndpointWithSecurityOutput]], using the [[Identity]] "effect". */ +case class PartialServerEndpointWithSecurityOutputSync[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, SECURITY_OUTPUT, OUTPUT, -R]( + securityOutput: EndpointOutput[SECURITY_OUTPUT], + endpoint: Endpoint[SECURITY_INPUT, INPUT, ERROR_OUTPUT, OUTPUT, R], + securityLogic: SECURITY_INPUT => Either[ERROR_OUTPUT, (SECURITY_OUTPUT, PRINCIPAL)] +) extends EndpointInputsOps[SECURITY_INPUT, INPUT, ERROR_OUTPUT, OUTPUT, R] + with EndpointOutputsOps[SECURITY_INPUT, INPUT, ERROR_OUTPUT, OUTPUT, R] + with EndpointErrorOutputVariantsOps[SECURITY_INPUT, INPUT, ERROR_OUTPUT, OUTPUT, R] + with EndpointInfoOps[R] + with EndpointMetaOps { outer => + override type ThisType[-_R] = + PartialServerEndpointWithSecurityOutputSync[SECURITY_INPUT, PRINCIPAL, INPUT, ERROR_OUTPUT, SECURITY_OUTPUT, OUTPUT, _R] + override type EndpointType[_A, _I, _E, _O, -_R] = + PartialServerEndpointWithSecurityOutputSync[_A, PRINCIPAL, _I, _E, SECURITY_OUTPUT, _O, _R] + + override def securityInput: EndpointInput[SECURITY_INPUT] = endpoint.securityInput + override def input: EndpointInput[INPUT] = endpoint.input + override def errorOutput: EndpointOutput[ERROR_OUTPUT] = endpoint.errorOutput + override def output: EndpointOutput[OUTPUT] = endpoint.output + override def info: EndpointInfo = endpoint.info + + override private[tapir] def withInput[I2, R2]( + input: EndpointInput[I2] + ): PartialServerEndpointWithSecurityOutputSync[SECURITY_INPUT, PRINCIPAL, I2, ERROR_OUTPUT, SECURITY_OUTPUT, OUTPUT, R with R2] = + copy(endpoint = endpoint.copy(input = input)) + override private[tapir] def withOutput[O2, R2](output: EndpointOutput[O2]) = copy(endpoint = endpoint.copy(output = output)) + override private[tapir] def withErrorOutputVariant[E2, R2]( + errorOutput: EndpointOutput[E2], + embedE: ERROR_OUTPUT => E2 + ): PartialServerEndpointWithSecurityOutputSync[SECURITY_INPUT, PRINCIPAL, INPUT, E2, SECURITY_OUTPUT, OUTPUT, R with R2] = + this.copy( + endpoint = endpoint.copy(errorOutput = errorOutput), + securityLogic = a => + securityLogic(a) match { + case Left(e) => Left(embedE(e)) + case Right(o) => Right(o) + } + ) + override private[tapir] def withInfo(info: EndpointInfo) = copy(endpoint = endpoint.copy(info = info)) + + override protected def showType: String = "PartialServerEndpointWithSecurityOutput" + + def handle( + f: PRINCIPAL => INPUT => Either[ERROR_OUTPUT, OUTPUT] + ): ServerEndpoint.Full[SECURITY_INPUT, (SECURITY_OUTPUT, PRINCIPAL), INPUT, ERROR_OUTPUT, (SECURITY_OUTPUT, OUTPUT), R, Identity] = + ServerEndpoint[SECURITY_INPUT, (SECURITY_OUTPUT, PRINCIPAL), INPUT, ERROR_OUTPUT, (SECURITY_OUTPUT, OUTPUT), R, Identity]( + endpoint.prependOut(securityOutput), + _ => securityLogic, + _ => so_u => i => f(so_u._2)(i).right.map(o => (so_u._1, o)) + ) + + def handleSuccess( + f: PRINCIPAL => INPUT => OUTPUT + ): ServerEndpoint.Full[SECURITY_INPUT, (SECURITY_OUTPUT, PRINCIPAL), INPUT, ERROR_OUTPUT, (SECURITY_OUTPUT, OUTPUT), R, Identity] = + ServerEndpoint[SECURITY_INPUT, (SECURITY_OUTPUT, PRINCIPAL), INPUT, ERROR_OUTPUT, (SECURITY_OUTPUT, OUTPUT), R, Identity]( + endpoint.prependOut(securityOutput), + _ => securityLogic, + _ => so_u => i => Right((so_u._1, f(so_u._2)(i))) + ) + + def handleError( + f: PRINCIPAL => INPUT => ERROR_OUTPUT + ): ServerEndpoint.Full[SECURITY_INPUT, (SECURITY_OUTPUT, PRINCIPAL), INPUT, ERROR_OUTPUT, (SECURITY_OUTPUT, OUTPUT), R, Identity] = + ServerEndpoint[SECURITY_INPUT, (SECURITY_OUTPUT, PRINCIPAL), INPUT, ERROR_OUTPUT, (SECURITY_OUTPUT, OUTPUT), R, Identity]( + endpoint.prependOut(securityOutput), + _ => securityLogic, + _ => so_u => i => Left(f(so_u._2)(i)) + ) + + def handleRecoverErrors( + f: PRINCIPAL => INPUT => OUTPUT + )(implicit + eIsThrowable: ERROR_OUTPUT <:< Throwable, + eClassTag: ClassTag[ERROR_OUTPUT] + ): ServerEndpoint.Full[SECURITY_INPUT, (SECURITY_OUTPUT, PRINCIPAL), INPUT, ERROR_OUTPUT, (SECURITY_OUTPUT, OUTPUT), R, Identity] = + ServerEndpoint[SECURITY_INPUT, (SECURITY_OUTPUT, PRINCIPAL), INPUT, ERROR_OUTPUT, (SECURITY_OUTPUT, OUTPUT), R, Identity]( + endpoint.prependOut(securityOutput), + _ => securityLogic, + _ => + recoverErrors2[(SECURITY_OUTPUT, PRINCIPAL), INPUT, ERROR_OUTPUT, (SECURITY_OUTPUT, OUTPUT), Identity](so_u => + i => (so_u._1, f(so_u._2)(i)) + )( + implicitly, + implicitly + )(IdentityMonad) + ) + + def handleOption(f: PRINCIPAL => INPUT => Option[OUTPUT])(implicit + eIsUnit: ERROR_OUTPUT =:= Unit + ): ServerEndpoint.Full[SECURITY_INPUT, (SECURITY_OUTPUT, PRINCIPAL), INPUT, Unit, (SECURITY_OUTPUT, OUTPUT), R, Identity] = + ServerEndpoint[SECURITY_INPUT, (SECURITY_OUTPUT, PRINCIPAL), INPUT, Unit, (SECURITY_OUTPUT, OUTPUT), R, Identity]( + endpoint.prependOut(securityOutput).asInstanceOf[Endpoint[SECURITY_INPUT, INPUT, Unit, (SECURITY_OUTPUT, OUTPUT), R]], + _ => securityLogic.asInstanceOf[SECURITY_INPUT => Identity[Either[Unit, (SECURITY_OUTPUT, PRINCIPAL)]]], + _ => + so_u => + i => + f(so_u._2)(i) match { + case None => Left(()) + case Some(v) => Right((so_u._1, v)) + } + ) +} diff --git a/core/src/test/scala/sttp/tapir/TestUtil.scala b/core/src/test/scala/sttp/tapir/TestUtil.scala index 6dbc8e1c46..bfbfb53d11 100644 --- a/core/src/test/scala/sttp/tapir/TestUtil.scala +++ b/core/src/test/scala/sttp/tapir/TestUtil.scala @@ -2,8 +2,9 @@ package sttp.tapir import sttp.model.Uri._ import sttp.model._ -import sttp.monad.MonadError +import sttp.monad.{IdentityMonad, MonadError} import sttp.monad.syntax._ +import sttp.shared.Identity import sttp.tapir.SchemaType.SProductField import sttp.tapir.model.{ConnectionInfo, ServerRequest} import sttp.tapir.server.ServerEndpoint @@ -13,20 +14,10 @@ import scala.collection.immutable object TestUtil { def field[T, U](_name: FieldName, _schema: Schema[U]): SchemaType.SProductField[T] = SProductField[T, U](_name, _schema, _ => None) - type Id[X] = X + implicit val idMonad: MonadError[Identity] = IdentityMonad - implicit val idMonadError: MonadError[Id] = new MonadError[Id] { - override def unit[T](t: T): Id[T] = t - override def map[T, T2](fa: Id[T])(f: T => T2): Id[T2] = f(fa) - override def flatMap[T, T2](fa: Id[T])(f: T => Id[T2]): Id[T2] = f(fa) - override def error[T](t: Throwable): Id[T] = throw t - override protected def handleWrappedError[T](rt: Id[T])(h: PartialFunction[Throwable, Id[T]]): Id[T] = rt - override def ensure[T](f: Id[T], e: => Id[Unit]): Id[T] = try f - finally e - } - - case class PersonsApi(logic: String => Id[Either[String, String]] = PersonsApi.defaultLogic) { - def serverEp: ServerEndpoint[Any, Id] = endpoint + case class PersonsApi(logic: String => Identity[Either[String, String]] = PersonsApi.defaultLogic) { + def serverEp: ServerEndpoint[Any, Identity] = endpoint .in("person") .in(query[String]("name")) .out(stringBody) @@ -35,7 +26,7 @@ object TestUtil { } object PersonsApi { - val defaultLogic: String => Id[Either[String, String]] = name => (if (name == "Jacob") Right("hello") else Left(":(")).unit + val defaultLogic: String => Identity[Either[String, String]] = name => (if (name == "Jacob") Right("hello") else Left(":(")).unit val request: String => ServerRequest = name => { new ServerRequest { diff --git a/doc/server/logic.md b/doc/server/logic.md index 595f12681d..bcb2cf6a11 100644 --- a/doc/server/logic.md +++ b/doc/server/logic.md @@ -13,6 +13,15 @@ For public endpoints (where the type of the security inputs is `Unit`), the serv `serverLogic(f: I => F[Either[E, O]]` method. For secure endpoints, you first need to provide the security logic using `serverSecurityLogic` and then the main logic. +```eval_rst +.. note:: + + If you are using a synchronous server (e.g. netty-sync, nima, or jdkhttp) the ``F[_]`` "effect" type is set to be + the identity type constructor, ``type Identity[X] = X``. For such cases, the server logic can be provided using the + ``.handle(f: I => Either[E, O])`` and ``.handleSecurity`` methods, which provide better type inference and + readability. +``` + Hence, apart from a `Endpoint[A, I, E, O, R]`, the server endpoint contains: * the server logic of type `I => F[Either[E, O]]` for public endpoint diff --git a/doc/server/netty.md b/doc/server/netty.md index 8729ea6882..f16dd47a1b 100644 --- a/doc/server/netty.md +++ b/doc/server/netty.md @@ -44,11 +44,17 @@ val binding: Future[NettyFutureServerBinding] = NettyFutureServer().addEndpoint(helloWorld).start() ``` -The `tapir-netty-server-sync` server uses `Id[T]` as its wrapper effect for compatibility, while `Id[A]` means in fact just `A`, representing direct style. It is -available only for Scala 3. +## Direct-style + +The `tapir-netty-server-sync` server uses `Identity[T]` as its wrapper effect for compatibility, while `Id[A]` means in +fact just `A`, representing direct style. It is available only for Scala 3. + See [examples/HelloWorldNettySyncServer.scala](https://github.com/softwaremill/tapir/blob/master/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettySyncServer.scala) for a full example. -To learn more about handling concurrency with Ox, see the [documentation](https://ox.softwaremill.com/). +To provide server logic for an endpoint when using the `-sync` server, you can use the dedicated `handle` +methods, and its variants. This provides better type inference. + +To learn more about handling concurrency with Ox, see the [documentation](https://ox.softwaremill.com/). ## Configuration diff --git a/doc/server/nima.md b/doc/server/nima.md index ab15ec2a35..21213506f7 100644 --- a/doc/server/nima.md +++ b/doc/server/nima.md @@ -13,7 +13,8 @@ dependency: ``` Loom-managed concurrency uses direct style instead of effect wrappers like `Future[T]` or `IO[T]`. Because of this, -Tapir endpoints defined for Nima server use `Id[T]`, which provides compatibility, while effectively means just `T`. +Tapir endpoints defined for Nima server use `Identity[T]`, which provides compatibility, while effectively means just +`T`. Such endpoints are then processed through `NimaServerInterpreter` in order to obtain an `io.helidon.webserver.http.Handler`: @@ -25,7 +26,7 @@ import sttp.tapir.server.nima.{Id, NimaServerInterpreter} val helloEndpoint = endpoint.get .in("hello") .out(stringBody) - .serverLogicSuccess[Id] { _ => + .handleSuccess { _ => Thread.sleep(1000) "hello, world!" } diff --git a/examples/src/main/scala/sttp/tapir/examples/ErrorUnionTypesHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/ErrorUnionTypesHttp4sServer.scala index a8224d241d..9c5d437a4a 100644 --- a/examples/src/main/scala/sttp/tapir/examples/ErrorUnionTypesHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/ErrorUnionTypesHttp4sServer.scala @@ -8,6 +8,7 @@ import org.http4s.server.Router import org.http4s.blaze.server.BlazeServerBuilder import sttp.client3.* import sttp.model.StatusCode +import sttp.shared.Identity import sttp.tapir.* import sttp.tapir.server.http4s.Http4sServerInterpreter import sttp.tapir.json.circe.* diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldArmeriaServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldArmeriaServer.scala index 514a73644b..7e14d83549 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldArmeriaServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldArmeriaServer.scala @@ -3,6 +3,7 @@ package sttp.tapir.examples import com.linecorp.armeria.server.Server import sttp.capabilities.armeria.ArmeriaStreams import sttp.client3.{HttpURLConnectionBackend, Identity, SttpBackend, UriContext, asStringAlways, basicRequest} +import sttp.shared.Identity import sttp.tapir.server.armeria.{ArmeriaFutureServerInterpreter, TapirService} import sttp.tapir.* diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala index c957690050..1dac16a447 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldHttp4sServer.scala @@ -6,6 +6,7 @@ import org.http4s.HttpRoutes import org.http4s.blaze.server.BlazeServerBuilder import org.http4s.server.Router import sttp.client3.* +import sttp.shared.Identity import sttp.tapir.* import sttp.tapir.server.http4s.Http4sServerInterpreter diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldJdkHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldJdkHttpServer.scala index 8cce9d7965..4aa2a27205 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldJdkHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldJdkHttpServer.scala @@ -1,7 +1,8 @@ package sttp.tapir.examples -import sttp.client3.{HttpURLConnectionBackend, Identity, Response, SttpBackend, UriContext, asStringAlways, basicRequest} +import sttp.client3.{HttpURLConnectionBackend, Response, SttpBackend, UriContext, asStringAlways, basicRequest} import sttp.model.StatusCode +import sttp.shared.Identity import sttp.tapir.server.jdkhttp.* import sttp.tapir.* @@ -15,9 +16,9 @@ object HelloWorldJdkHttpServer extends App { endpoint.get.in("second").out(stringBody) // Providing the server logic for the endpoints: here, just returning the passed name with `Hello, ` prepended - val helloWorldServerEndpoint = helloWorldEndpoint.handle(name => Right(s"Hello, $name!")) + val helloWorldServerEndpoint = helloWorldEndpoint.handleSuccess(name => s"Hello, $name!") - val secondServerEndpoint = secondEndpoint.handle(_ => Right("IT WORKS!")) + val secondServerEndpoint = secondEndpoint.handleSuccess(_ => "IT WORKS!") private val declaredPort = 9090 private val declaredHost = "localhost" diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyCatsServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyCatsServer.scala index efc9c955e7..eaf606a3b4 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyCatsServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyCatsServer.scala @@ -1,8 +1,9 @@ package sttp.tapir.examples import cats.effect.{IO, IOApp} -import sttp.client3.{HttpURLConnectionBackend, Identity, SttpBackend, UriContext, asStringAlways, basicRequest} +import sttp.client3.{HttpURLConnectionBackend, SttpBackend, UriContext, asStringAlways, basicRequest} import sttp.model.StatusCode +import sttp.shared.Identity import sttp.tapir.server.netty.cats.NettyCatsServer import sttp.tapir.* diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyFutureServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyFutureServer.scala index 05d5b3a799..1f3be12861 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyFutureServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettyFutureServer.scala @@ -1,7 +1,8 @@ package sttp.tapir.examples -import sttp.client3.{HttpURLConnectionBackend, Identity, SttpBackend, UriContext, asStringAlways, basicRequest} +import sttp.client3.{HttpURLConnectionBackend, SttpBackend, UriContext, asStringAlways, basicRequest} import sttp.model.StatusCode +import sttp.shared.Identity import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerBinding} import sttp.tapir.* diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettySyncServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettySyncServer.scala index f512b7f661..0580ebaab2 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettySyncServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldNettySyncServer.scala @@ -2,14 +2,14 @@ package sttp.tapir.examples import ox.* import sttp.tapir.* -import sttp.tapir.server.netty.sync.{Id, NettySyncServer} +import sttp.tapir.server.netty.sync.NettySyncServer object HelloWorldNettySyncServer: val helloWorld = endpoint.get .in("hello") .in(query[String]("name")) .out(stringBody) - .serverLogicSuccess[Id](name => s"Hello, $name!") + .handleSuccess(name => s"Hello, $name!") NettySyncServer().addEndpoint(helloWorld).startAndWait() @@ -20,7 +20,7 @@ object HelloWorldNettySyncServer2: .in("hello") .in(query[String]("name")) .out(stringBody) - .serverLogicSuccess[Id](name => s"Hello, $name!") + .handleSuccess(name => s"Hello, $name!") supervised { val serverBinding = useInScope(NettySyncServer().addEndpoint(helloWorld).start())(_.stop()) diff --git a/examples/src/main/scala/sttp/tapir/examples/HelloWorldPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/HelloWorldPekkoServer.scala index f8bcc3e382..95f6ff5bed 100644 --- a/examples/src/main/scala/sttp/tapir/examples/HelloWorldPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/HelloWorldPekkoServer.scala @@ -4,6 +4,7 @@ import org.apache.pekko.actor.ActorSystem import org.apache.pekko.http.scaladsl.Http import org.apache.pekko.http.scaladsl.server.Route import sttp.client3.* +import sttp.shared.Identity import sttp.tapir.* import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter diff --git a/examples/src/main/scala/sttp/tapir/examples/errors/CustomErrorsOnDecodeFailurePekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/errors/CustomErrorsOnDecodeFailurePekkoServer.scala index 11351c1476..4f41a1746c 100644 --- a/examples/src/main/scala/sttp/tapir/examples/errors/CustomErrorsOnDecodeFailurePekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/errors/CustomErrorsOnDecodeFailurePekkoServer.scala @@ -3,6 +3,7 @@ package sttp.tapir.examples.errors import org.apache.pekko.actor.ActorSystem import org.apache.pekko.http.scaladsl.Http import org.apache.pekko.http.scaladsl.server.Route +import sttp.shared.Identity import sttp.tapir.* import sttp.tapir.server.pekkohttp.{PekkoHttpServerInterpreter, PekkoHttpServerOptions} diff --git a/examples/src/main/scala/sttp/tapir/examples/errors/ErrorOutputsPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/errors/ErrorOutputsPekkoServer.scala index 8d06426278..f4ebff48e4 100644 --- a/examples/src/main/scala/sttp/tapir/examples/errors/ErrorOutputsPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/errors/ErrorOutputsPekkoServer.scala @@ -4,6 +4,7 @@ import org.apache.pekko.actor.ActorSystem import org.apache.pekko.http.scaladsl.Http import org.apache.pekko.http.scaladsl.server.Route import sttp.client3.* +import sttp.shared.Identity import sttp.tapir.generic.auto.* import sttp.tapir.* import sttp.tapir.server.pekkohttp.* diff --git a/examples/src/main/scala/sttp/tapir/examples/errors/IronRefinementErrorsNettyServer.scala b/examples/src/main/scala/sttp/tapir/examples/errors/IronRefinementErrorsNettyServer.scala index 7997c1c8d0..7889a86572 100644 --- a/examples/src/main/scala/sttp/tapir/examples/errors/IronRefinementErrorsNettyServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/errors/IronRefinementErrorsNettyServer.scala @@ -9,6 +9,7 @@ import io.github.iltotore.iron.{Constraint, refineEither} import io.circe.generic.auto.* import io.circe.{Decoder, Encoder} import sttp.client3.* +import sttp.shared.Identity import sttp.tapir.* import sttp.tapir.DecodeResult.Error import sttp.tapir.DecodeResult.Error.JsonDecodeException @@ -16,7 +17,7 @@ import sttp.tapir.server.interceptor.DecodeFailureContext import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler.FailureMessages import sttp.tapir.server.interceptor.decodefailure.{DecodeFailureInterceptor, DefaultDecodeFailureHandler} -import sttp.client3.{HttpURLConnectionBackend, Identity, SttpBackend, UriContext, asStringAlways, basicRequest} +import sttp.client3.{HttpURLConnectionBackend, SttpBackend, UriContext, asStringAlways, basicRequest} import sttp.model.StatusCode import sttp.tapir.server.netty.cats.NettyCatsServer import sttp.tapir.json.circe.* diff --git a/examples/src/main/scala/sttp/tapir/examples/multipart/MultipartFormUploadPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/multipart/MultipartFormUploadPekkoServer.scala index eb5b101ab7..d978daa7fd 100644 --- a/examples/src/main/scala/sttp/tapir/examples/multipart/MultipartFormUploadPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/multipart/MultipartFormUploadPekkoServer.scala @@ -6,6 +6,7 @@ import org.apache.pekko.actor.ActorSystem import org.apache.pekko.http.scaladsl.Http import org.apache.pekko.http.scaladsl.server.Route import sttp.client3.* +import sttp.shared.Identity import sttp.tapir.generic.auto.* import sttp.model.Part import sttp.tapir.* diff --git a/examples/src/main/scala/sttp/tapir/examples/security/BasicAuthenticationPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/security/BasicAuthenticationPekkoServer.scala index a8aa347ba9..f5fb7c9726 100644 --- a/examples/src/main/scala/sttp/tapir/examples/security/BasicAuthenticationPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/security/BasicAuthenticationPekkoServer.scala @@ -6,6 +6,7 @@ import org.apache.pekko.http.scaladsl.server.Route import sttp.client3.* import sttp.model.StatusCode import sttp.model.headers.WWWAuthenticateChallenge +import sttp.shared.Identity import sttp.tapir.* import sttp.tapir.model.* import sttp.tapir.server.pekkohttp.* diff --git a/examples/src/main/scala/sttp/tapir/examples/security/ExternalSecurityInterceptor.scala b/examples/src/main/scala/sttp/tapir/examples/security/ExternalSecurityInterceptor.scala index dc6f298813..c00dd3cb12 100644 --- a/examples/src/main/scala/sttp/tapir/examples/security/ExternalSecurityInterceptor.scala +++ b/examples/src/main/scala/sttp/tapir/examples/security/ExternalSecurityInterceptor.scala @@ -3,6 +3,7 @@ package sttp.tapir.examples.security import sttp.client3.* import sttp.model.StatusCode import sttp.monad.MonadError +import sttp.shared.Identity import sttp.tapir.server.netty.{NettyFutureServer, NettyFutureServerBinding, NettyFutureServerOptions} import sttp.tapir.* import sttp.tapir.server.interceptor.{ diff --git a/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicPekko.scala b/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicPekko.scala index f231477c3c..d2e3b1185e 100644 --- a/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicPekko.scala +++ b/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicPekko.scala @@ -5,6 +5,7 @@ import org.apache.pekko.http.scaladsl.Http import org.apache.pekko.http.scaladsl.server.Directives.* import org.apache.pekko.http.scaladsl.server.Route import sttp.client3.* +import sttp.shared.Identity import sttp.model.HeaderNames import sttp.tapir.* import sttp.tapir.server.{PartialServerEndpoint, ServerEndpoint} diff --git a/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicRefreshCookiesPekko.scala b/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicRefreshCookiesPekko.scala index 9b55a6da57..22b2369e67 100644 --- a/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicRefreshCookiesPekko.scala +++ b/examples/src/main/scala/sttp/tapir/examples/security/ServerSecurityLogicRefreshCookiesPekko.scala @@ -5,6 +5,7 @@ import org.apache.pekko.http.scaladsl.Http import org.apache.pekko.http.scaladsl.server.Directives.concat import org.apache.pekko.http.scaladsl.server.Route import sttp.client3.* +import sttp.shared.Identity import sttp.model.StatusCode import sttp.model.headers.CookieValueWithMeta import sttp.tapir.* diff --git a/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromFilesPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromFilesPekkoServer.scala index 08617f8785..594a1aa501 100644 --- a/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromFilesPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentFromFilesPekkoServer.scala @@ -5,6 +5,7 @@ import org.apache.pekko.http.scaladsl.Http import org.apache.pekko.http.scaladsl.server.Route import sttp.client3.* import sttp.model.{ContentRangeUnits, Header, HeaderNames, StatusCode} +import sttp.shared.Identity import sttp.tapir.* import sttp.tapir.files.* import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter diff --git a/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentSecurePekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentSecurePekkoServer.scala index 77eda683ac..d1f3d63520 100644 --- a/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentSecurePekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/static_content/StaticContentSecurePekkoServer.scala @@ -5,6 +5,7 @@ import org.apache.pekko.http.scaladsl.Http import org.apache.pekko.http.scaladsl.server.Route import sttp.client3.* import sttp.model.StatusCode +import sttp.shared.Identity import sttp.tapir.* import sttp.tapir.files.* import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter diff --git a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2Server.scala b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2Server.scala index 6b7f744876..6914631364 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2Server.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingHttp4sFs2Server.scala @@ -9,6 +9,7 @@ import org.http4s.server.Router import sttp.capabilities.fs2.Fs2Streams import sttp.client3.* import sttp.model.HeaderNames +import sttp.shared.Identity import sttp.tapir.* import sttp.tapir.server.http4s.Http4sServerInterpreter diff --git a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyFs2Server.scala b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyFs2Server.scala index 09fe290688..41610fda91 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyFs2Server.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyFs2Server.scala @@ -6,6 +6,7 @@ import fs2.{Chunk, Stream} import sttp.capabilities.fs2.Fs2Streams import sttp.client3.* import sttp.model.HeaderNames +import sttp.shared.Identity import sttp.tapir.* import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.netty.cats.{NettyCatsServer, NettyCatsServerBinding} diff --git a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyZioServer.scala b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyZioServer.scala index f9b78a4b8d..9502fa61e5 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyZioServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingNettyZioServer.scala @@ -3,6 +3,7 @@ package sttp.tapir.examples.streaming import sttp.capabilities.zio.ZioStreams import sttp.client3.* import sttp.model.HeaderNames +import sttp.shared.Identity import sttp.tapir.{CodecFormat, PublicEndpoint} import sttp.tapir.server.netty.zio.NettyZioServer import sttp.tapir.ztapir.* diff --git a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingPekkoServer.scala b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingPekkoServer.scala index d0461858c1..49e730b91c 100644 --- a/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingPekkoServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/streaming/StreamingPekkoServer.scala @@ -7,6 +7,7 @@ import org.apache.pekko.stream.scaladsl.Source import org.apache.pekko.util.ByteString import sttp.capabilities.pekko.PekkoStreams import sttp.client3.* +import sttp.shared.Identity import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter import sttp.tapir.* diff --git a/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketNettySyncServer.scala b/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketNettySyncServer.scala index 088168bb74..6dd5ca8926 100644 --- a/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketNettySyncServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/websocket/WebSocketNettySyncServer.scala @@ -4,7 +4,6 @@ import ox.* import ox.channels.* import sttp.capabilities.WebSockets import sttp.tapir.* -import sttp.tapir.server.netty.sync.Id import sttp.tapir.server.netty.sync.OxStreams import sttp.tapir.server.netty.sync.OxStreams.Pipe // alias for Ox ?=> Source[A] => Source[B] import sttp.tapir.server.netty.sync.NettySyncServer @@ -39,14 +38,14 @@ object WebSocketNettySyncServer: } // The WebSocket endpoint, builds the pipeline in serverLogicSuccess - val wsServerEndpoint = wsEndpoint.serverLogicSuccess[Id](_ => wsPipe) + val wsServerEndpoint = wsEndpoint.handleSuccess(_ => wsPipe) // A regular /GET endpoint val helloWorldEndpoint = endpoint.get.in("hello").in(query[String]("name")).out(stringBody) val helloWorldServerEndpoint = helloWorldEndpoint - .serverLogicSuccess[Id](name => s"Hello, $name!") + .handleSuccess(name => s"Hello, $name!") def main(args: Array[String]): Unit = NettySyncServer() diff --git a/examples2/src/main/scala/sttp/tapir/examples2/HelloWorldAkkaServer.scala b/examples2/src/main/scala/sttp/tapir/examples2/HelloWorldAkkaServer.scala index 2f96ca1027..be26b6a47e 100644 --- a/examples2/src/main/scala/sttp/tapir/examples2/HelloWorldAkkaServer.scala +++ b/examples2/src/main/scala/sttp/tapir/examples2/HelloWorldAkkaServer.scala @@ -3,6 +3,7 @@ package sttp.tapir.examples2 import akka.actor.ActorSystem import akka.http.scaladsl.Http import akka.http.scaladsl.server.Route +import sttp.shared.Identity import sttp.tapir._ import sttp.tapir.server.akkahttp.AkkaHttpServerInterpreter diff --git a/generated-doc/out/server/nima.md b/generated-doc/out/server/nima.md index fa6e250b69..45f1a2d14e 100644 --- a/generated-doc/out/server/nima.md +++ b/generated-doc/out/server/nima.md @@ -25,7 +25,7 @@ import sttp.tapir.server.nima.{Id, NimaServerInterpreter} val helloEndpoint = endpoint.get .in("hello") .out(stringBody) - .serverLogicSuccess[Id] { _ => + .serverLogicSuccess[Identity] { _ => Thread.sleep(1000) "hello, world!" } diff --git a/integrations/iron/src/main/scala/sttp/iron/codec/iron/TapirCodecIron.scala b/integrations/iron/src/main/scala/sttp/iron/codec/iron/TapirCodecIron.scala index de886ebd73..3c2b026435 100644 --- a/integrations/iron/src/main/scala/sttp/iron/codec/iron/TapirCodecIron.scala +++ b/integrations/iron/src/main/scala/sttp/iron/codec/iron/TapirCodecIron.scala @@ -10,6 +10,8 @@ import io.github.iltotore.iron.constraint.string.* import io.github.iltotore.iron.constraint.collection.* import io.github.iltotore.iron.constraint.numeric.* +import sttp.shared.Identity + import sttp.tapir.Codec import sttp.tapir.CodecFormat import sttp.tapir.DecodeResult diff --git a/metrics/datadog-metrics/src/test/scala/sttp/tapir/server/metrics/datadog/DatadogMetricsTest.scala b/metrics/datadog-metrics/src/test/scala/sttp/tapir/server/metrics/datadog/DatadogMetricsTest.scala index 764280d0fc..65da42b572 100644 --- a/metrics/datadog-metrics/src/test/scala/sttp/tapir/server/metrics/datadog/DatadogMetricsTest.scala +++ b/metrics/datadog-metrics/src/test/scala/sttp/tapir/server/metrics/datadog/DatadogMetricsTest.scala @@ -9,6 +9,7 @@ import org.scalatest.matchers.should.Matchers import org.scalatest.tagobjects.Retryable import org.scalatest.time.{Seconds, Span} import org.scalatest.{BeforeAndAfter, BeforeAndAfterAll, Canceled, Failed, Outcome} +import sttp.shared.Identity import sttp.tapir.TestUtil._ import sttp.tapir.capabilities.NoStreams import sttp.tapir.server.ServerEndpoint @@ -75,9 +76,9 @@ class DatadogMetricsTest extends AnyFlatSpec with Matchers with BeforeAndAfter w .port(statsdServerPort) .build() - val metrics = DatadogMetrics[Id](client).addRequestsActive() + val metrics = DatadogMetrics[Identity](client).addRequestsActive() val interpreter = - new ServerInterpreter[Any, Id, Unit, NoStreams]( + new ServerInterpreter[Any, Identity, Unit, NoStreams]( _ => List(serverEp), TestRequestBody, UnitToResponseBody, @@ -110,13 +111,13 @@ class DatadogMetricsTest extends AnyFlatSpec with Matchers with BeforeAndAfter w .port(statsdServerPort) .build() - val metrics = DatadogMetrics[Id](client).addRequestsTotal() + val metrics = DatadogMetrics[Identity](client).addRequestsTotal() val interpreter = - new ServerInterpreter[Any, Id, Unit, NoStreams]( + new ServerInterpreter[Any, Identity, Unit, NoStreams]( _ => List(serverEp), TestRequestBody, UnitToResponseBody, - List(metrics.metricsInterceptor(), new DecodeFailureInterceptor(DefaultDecodeFailureHandler[Id])), + List(metrics.metricsInterceptor(), new DecodeFailureInterceptor(DefaultDecodeFailureHandler[Identity])), _ => () ) @@ -137,15 +138,15 @@ class DatadogMetricsTest extends AnyFlatSpec with Matchers with BeforeAndAfter w // given val clock = new TestClock() - val waitServerEp: Long => ServerEndpoint[Any, Id] = millis => { + val waitServerEp: Long => ServerEndpoint[Any, Identity] = millis => { PersonsApi { name => clock.forward(millis) PersonsApi.defaultLogic(name) }.serverEp } - val waitBodyListener: Long => BodyListener[Id, String] = millis => - new BodyListener[Id, String] { - override def onComplete(body: String)(cb: Try[Unit] => Id[Unit]): String = { + val waitBodyListener: Long => BodyListener[Identity, String] = millis => + new BodyListener[Identity, String] { + override def onComplete(body: String)(cb: Try[Unit] => Identity[Unit]): String = { clock.forward(millis) cb(Success(())) body @@ -157,9 +158,9 @@ class DatadogMetricsTest extends AnyFlatSpec with Matchers with BeforeAndAfter w .port(statsdServerPort) .build() - val metrics = DatadogMetrics[Id](client).addRequestsDuration(clock = clock) + val metrics = DatadogMetrics[Identity](client).addRequestsDuration(clock = clock) def interpret(sleepHeaders: Long, sleepBody: Long) = - new ServerInterpreter[Any, Id, String, NoStreams]( + new ServerInterpreter[Any, Identity, String, NoStreams]( _ => List(waitServerEp(sleepHeaders)), TestRequestBody, StringToResponseBody, @@ -208,9 +209,9 @@ class DatadogMetricsTest extends AnyFlatSpec with Matchers with BeforeAndAfter w .port(statsdServerPort) .build() - val metrics = DatadogMetrics[Id](client).addRequestsTotal(labels) + val metrics = DatadogMetrics[Identity](client).addRequestsTotal(labels) val interpreter = - new ServerInterpreter[Any, Id, Unit, NoStreams]( + new ServerInterpreter[Any, Identity, Unit, NoStreams]( _ => List(serverEp), TestRequestBody, UnitToResponseBody, @@ -236,12 +237,12 @@ class DatadogMetricsTest extends AnyFlatSpec with Matchers with BeforeAndAfter w .port(statsdServerPort) .build() - val metrics = DatadogMetrics[Id](client).addRequestsTotal() - val interpreter = new ServerInterpreter[Any, Id, Unit, NoStreams]( + val metrics = DatadogMetrics[Identity](client).addRequestsTotal() + val interpreter = new ServerInterpreter[Any, Identity, Unit, NoStreams]( _ => List(serverEp), TestRequestBody, UnitToResponseBody, - List(metrics.metricsInterceptor(), new ExceptionInterceptor(DefaultExceptionHandler[Id])), + List(metrics.metricsInterceptor(), new ExceptionInterceptor(DefaultExceptionHandler[Identity])), _ => () ) diff --git a/metrics/opentelemetry-metrics/src/test/scala/sttp/tapir/server/metrics/opentelemetry/OpenTelemetryMetricsTest.scala b/metrics/opentelemetry-metrics/src/test/scala/sttp/tapir/server/metrics/opentelemetry/OpenTelemetryMetricsTest.scala index 593d4e37a7..fcc096a963 100644 --- a/metrics/opentelemetry-metrics/src/test/scala/sttp/tapir/server/metrics/opentelemetry/OpenTelemetryMetricsTest.scala +++ b/metrics/opentelemetry-metrics/src/test/scala/sttp/tapir/server/metrics/opentelemetry/OpenTelemetryMetricsTest.scala @@ -9,6 +9,7 @@ import org.scalatest.concurrent.ScalaFutures import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import org.scalatest.time.{Seconds, Span} +import sttp.shared.Identity import sttp.tapir.TestUtil._ import sttp.tapir.capabilities.NoStreams import sttp.tapir.server.TestUtil._ @@ -33,9 +34,9 @@ class OpenTelemetryMetricsTest extends AnyFlatSpec with Matchers { Thread.sleep(2000) PersonsApi.defaultLogic(name) }.serverEp - val metrics = OpenTelemetryMetrics[Id](meter).addRequestsActive() + val metrics = OpenTelemetryMetrics[Identity](meter).addRequestsActive() val interpreter = - new ServerInterpreter[Any, Id, String, NoStreams]( + new ServerInterpreter[Any, Identity, String, NoStreams]( _ => List(serverEp), TestRequestBody, StringToResponseBody, @@ -66,12 +67,12 @@ class OpenTelemetryMetricsTest extends AnyFlatSpec with Matchers { val provider = SdkMeterProvider.builder().registerMetricReader(reader).build() val meter = provider.get("tapir-instrumentation") val serverEp = PersonsApi().serverEp - val metrics = OpenTelemetryMetrics[Id](meter).addRequestsTotal() - val interpreter = new ServerInterpreter[Any, Id, Unit, NoStreams]( + val metrics = OpenTelemetryMetrics[Identity](meter).addRequestsTotal() + val interpreter = new ServerInterpreter[Any, Identity, Unit, NoStreams]( _ => List(serverEp), TestRequestBody, UnitToResponseBody, - List(metrics.metricsInterceptor(), new DecodeFailureInterceptor(DefaultDecodeFailureHandler[Id])), + List(metrics.metricsInterceptor(), new DecodeFailureInterceptor(DefaultDecodeFailureHandler[Identity])), _ => () ) @@ -113,16 +114,16 @@ class OpenTelemetryMetricsTest extends AnyFlatSpec with Matchers { val reader = InMemoryMetricReader.create() val provider = SdkMeterProvider.builder().registerMetricReader(reader).build() val meter = provider.get("tapir-instrumentation") - val waitServerEp: Int => ServerEndpoint[Any, Id] = millis => { + val waitServerEp: Int => ServerEndpoint[Any, Identity] = millis => { PersonsApi { name => Thread.sleep(millis) PersonsApi.defaultLogic(name) }.serverEp } - val metrics = OpenTelemetryMetrics[Id](meter).addRequestsDuration() + val metrics = OpenTelemetryMetrics[Identity](meter).addRequestsDuration() def interpret(sleep: Int) = - new ServerInterpreter[Any, Id, String, NoStreams]( + new ServerInterpreter[Any, Identity, String, NoStreams]( _ => List(waitServerEp(sleep)), TestRequestBody, StringToResponseBody, @@ -158,9 +159,9 @@ class OpenTelemetryMetricsTest extends AnyFlatSpec with Matchers { val reader = InMemoryMetricReader.create() val provider = SdkMeterProvider.builder().registerMetricReader(reader).build() val meter = provider.get("tapir-instrumentation") - val metrics = OpenTelemetryMetrics[Id](meter).addRequestsTotal(labels) + val metrics = OpenTelemetryMetrics[Identity](meter).addRequestsTotal(labels) val interpreter = - new ServerInterpreter[Any, Id, String, NoStreams]( + new ServerInterpreter[Any, Identity, String, NoStreams]( _ => List(serverEp), TestRequestBody, StringToResponseBody, @@ -181,12 +182,12 @@ class OpenTelemetryMetricsTest extends AnyFlatSpec with Matchers { val reader = InMemoryMetricReader.create() val provider = SdkMeterProvider.builder().registerMetricReader(reader).build() val meter = provider.get("tapir-instrumentation") - val metrics = OpenTelemetryMetrics[Id](meter).addRequestsTotal() - val interpreter = new ServerInterpreter[Any, Id, String, NoStreams]( + val metrics = OpenTelemetryMetrics[Identity](meter).addRequestsTotal() + val interpreter = new ServerInterpreter[Any, Identity, String, NoStreams]( _ => List(serverEp), TestRequestBody, StringToResponseBody, - List(metrics.metricsInterceptor(), new ExceptionInterceptor(DefaultExceptionHandler[Id])), + List(metrics.metricsInterceptor(), new ExceptionInterceptor(DefaultExceptionHandler[Identity])), _ => () ) diff --git a/metrics/prometheus-metrics/src/test/scala/sttp/tapir/server/metrics/prometheus/PrometheusMetricsTest.scala b/metrics/prometheus-metrics/src/test/scala/sttp/tapir/server/metrics/prometheus/PrometheusMetricsTest.scala index 2027cb8b51..06561d0843 100644 --- a/metrics/prometheus-metrics/src/test/scala/sttp/tapir/server/metrics/prometheus/PrometheusMetricsTest.scala +++ b/metrics/prometheus-metrics/src/test/scala/sttp/tapir/server/metrics/prometheus/PrometheusMetricsTest.scala @@ -12,6 +12,7 @@ import sttp.tapir.TestUtil._ import sttp.tapir.server.TestUtil._ import PrometheusMetrics._ import PrometheusMetricsTest._ +import sttp.shared.Identity import sttp.tapir.AttributeKey import sttp.tapir.capabilities.NoStreams import sttp.tapir.model.{ConnectionInfo, ServerRequest} @@ -36,9 +37,9 @@ class PrometheusMetricsTest extends AnyFlatSpec with Matchers { Thread.sleep(2000) PersonsApi.defaultLogic(name) }.serverEp - val metrics = PrometheusMetrics[Id]("tapir", new PrometheusRegistry()).addRequestsActive() + val metrics = PrometheusMetrics[Identity]("tapir", new PrometheusRegistry()).addRequestsActive() val interpreter = - new ServerInterpreter[Any, Id, String, NoStreams]( + new ServerInterpreter[Any, Identity, String, NoStreams]( _ => List(serverEp), TestRequestBody, StringToResponseBody, @@ -64,12 +65,12 @@ class PrometheusMetricsTest extends AnyFlatSpec with Matchers { "default metrics" should "collect requests total" in { // given val serverEp = PersonsApi().serverEp - val metrics = PrometheusMetrics[Id]("tapir", new PrometheusRegistry()).addRequestsTotal() - val interpreter = new ServerInterpreter[Any, Id, Unit, NoStreams]( + val metrics = PrometheusMetrics[Identity]("tapir", new PrometheusRegistry()).addRequestsTotal() + val interpreter = new ServerInterpreter[Any, Identity, Unit, NoStreams]( _ => List(serverEp), TestRequestBody, UnitToResponseBody, - List(metrics.metricsInterceptor(), new DecodeFailureInterceptor(DefaultDecodeFailureHandler[Id])), + List(metrics.metricsInterceptor(), new DecodeFailureInterceptor(DefaultDecodeFailureHandler[Identity])), _ => () ) @@ -88,24 +89,24 @@ class PrometheusMetricsTest extends AnyFlatSpec with Matchers { "default metrics" should "collect requests duration" in { // given val clock = new TestClock() - val waitServerEp: Long => ServerEndpoint[Any, Id] = millis => { + val waitServerEp: Long => ServerEndpoint[Any, Identity] = millis => { PersonsApi { name => clock.forward(millis) PersonsApi.defaultLogic(name) }.serverEp } - val waitBodyListener: Long => BodyListener[Id, String] = millis => - new BodyListener[Id, String] { - override def onComplete(body: String)(cb: Try[Unit] => Id[Unit]): String = { + val waitBodyListener: Long => BodyListener[Identity, String] = millis => + new BodyListener[Identity, String] { + override def onComplete(body: String)(cb: Try[Unit] => Identity[Unit]): String = { clock.forward(millis) cb(Success(())) body } } - val metrics = PrometheusMetrics[Id]("tapir", new PrometheusRegistry()).addRequestsDuration(clock = clock) + val metrics = PrometheusMetrics[Identity]("tapir", new PrometheusRegistry()).addRequestsDuration(clock = clock) def interpret(sleepHeaders: Long, sleepBody: Long) = - new ServerInterpreter[Any, Id, String, NoStreams]( + new ServerInterpreter[Any, Identity, String, NoStreams]( _ => List(waitServerEp(sleepHeaders)), TestRequestBody, StringToResponseBody, @@ -148,9 +149,9 @@ class PrometheusMetricsTest extends AnyFlatSpec with Matchers { val serverEp = PersonsApi().serverEp val labels = MetricLabels(forRequest = List("key" -> { case (_, _) => "value" }), forResponse = Nil) - val metrics = PrometheusMetrics[Id]("tapir", new PrometheusRegistry()).addRequestsTotal(labels) + val metrics = PrometheusMetrics[Identity]("tapir", new PrometheusRegistry()).addRequestsTotal(labels) val interpreter = - new ServerInterpreter[Any, Id, String, NoStreams]( + new ServerInterpreter[Any, Identity, String, NoStreams]( _ => List(serverEp), TestRequestBody, StringToResponseBody, @@ -168,9 +169,9 @@ class PrometheusMetricsTest extends AnyFlatSpec with Matchers { "interceptor" should "not collect metrics from prometheus endpoint" in { // given val serverEp = PersonsApi().serverEp - val metrics = PrometheusMetrics[Id]("tapir", new PrometheusRegistry()).addRequestsTotal() + val metrics = PrometheusMetrics[Identity]("tapir", new PrometheusRegistry()).addRequestsTotal() val interpreter = - new ServerInterpreter[Any, Id, String, NoStreams]( + new ServerInterpreter[Any, Identity, String, NoStreams]( _ => List(metrics.metricsEndpoint, serverEp), TestRequestBody, StringToResponseBody, @@ -188,9 +189,9 @@ class PrometheusMetricsTest extends AnyFlatSpec with Matchers { "metrics server endpoint" should "return encoded registry" in { // given - val metrics = PrometheusMetrics[Id]("tapir", new PrometheusRegistry()).addRequestsTotal() + val metrics = PrometheusMetrics[Identity]("tapir", new PrometheusRegistry()).addRequestsTotal() val interpreter = - new ServerInterpreter[Any, Id, String, NoStreams]( + new ServerInterpreter[Any, Identity, String, NoStreams]( _ => List(metrics.metricsEndpoint), TestRequestBody, StringToResponseBody, @@ -211,12 +212,12 @@ class PrometheusMetricsTest extends AnyFlatSpec with Matchers { "metrics" should "be collected on exception when response from exception handler" in { // given val serverEp = PersonsApi { _ => throw new RuntimeException("Ups") }.serverEp - val metrics = PrometheusMetrics[Id]("tapir", new PrometheusRegistry()).addRequestsTotal() - val interpreter = new ServerInterpreter[Any, Id, String, NoStreams]( + val metrics = PrometheusMetrics[Identity]("tapir", new PrometheusRegistry()).addRequestsTotal() + val interpreter = new ServerInterpreter[Any, Identity, String, NoStreams]( _ => List(serverEp), TestRequestBody, StringToResponseBody, - List(metrics.metricsInterceptor(), new ExceptionInterceptor(DefaultExceptionHandler[Id])), + List(metrics.metricsInterceptor(), new ExceptionInterceptor(DefaultExceptionHandler[Identity])), _ => () ) diff --git a/metrics/zio-metrics/src/test/scala/sttp/tapir/server/metrics/zio/ZioMetricsTest.scala b/metrics/zio-metrics/src/test/scala/sttp/tapir/server/metrics/zio/ZioMetricsTest.scala index 79c622199a..7aa849f581 100644 --- a/metrics/zio-metrics/src/test/scala/sttp/tapir/server/metrics/zio/ZioMetricsTest.scala +++ b/metrics/zio-metrics/src/test/scala/sttp/tapir/server/metrics/zio/ZioMetricsTest.scala @@ -1,5 +1,6 @@ package sttp.tapir.server.metrics.zio +import sttp.shared.Identity import sttp.tapir.TestUtil._ import sttp.tapir.capabilities.NoStreams import sttp.tapir.server.TestUtil._ @@ -21,13 +22,13 @@ object ZioMetricsTest extends ZIOSpecDefault { Thread.sleep(100) PersonsApi.defaultLogic(name) }.serverEp - val metrics: ZioMetrics[Id] = ZioMetrics(DefaultNamespace, List.empty).addRequestsActive() + val metrics: ZioMetrics[Identity] = ZioMetrics(DefaultNamespace, List.empty).addRequestsActive() val interpreter = - new ServerInterpreter[Any, Id, Unit, NoStreams]( + new ServerInterpreter[Any, Identity, Unit, NoStreams]( _ => List(serverEp), TestRequestBody, UnitToResponseBody, - List(metrics.metricsInterceptor(), new DecodeFailureInterceptor(DefaultDecodeFailureHandler[Id])), + List(metrics.metricsInterceptor(), new DecodeFailureInterceptor(DefaultDecodeFailureHandler[Identity])), _ => () ) @@ -56,13 +57,13 @@ object ZioMetricsTest extends ZIOSpecDefault { val serverEp = PersonsApi { name => PersonsApi.defaultLogic(name) }.serverEp - val metrics: ZioMetrics[Id] = ZioMetrics(DefaultNamespace, List.empty).addRequestsTotal() + val metrics: ZioMetrics[Identity] = ZioMetrics(DefaultNamespace, List.empty).addRequestsTotal() val interpreter = - new ServerInterpreter[Any, Id, Unit, NoStreams]( + new ServerInterpreter[Any, Identity, Unit, NoStreams]( _ => List(serverEp), TestRequestBody, UnitToResponseBody, - List(metrics.metricsInterceptor(), new DecodeFailureInterceptor(DefaultDecodeFailureHandler[Id])), + List(metrics.metricsInterceptor(), new DecodeFailureInterceptor(DefaultDecodeFailureHandler[Identity])), _ => () ) @@ -95,13 +96,13 @@ object ZioMetricsTest extends ZIOSpecDefault { val serverEp = PersonsApi { name => PersonsApi.defaultLogic(name) }.serverEp - val metrics: ZioMetrics[Id] = ZioMetrics(DefaultNamespace, List.empty).addRequestsDuration() + val metrics: ZioMetrics[Identity] = ZioMetrics(DefaultNamespace, List.empty).addRequestsDuration() val interpreter = - new ServerInterpreter[Any, Id, Unit, NoStreams]( + new ServerInterpreter[Any, Identity, Unit, NoStreams]( _ => List(serverEp), TestRequestBody, UnitToResponseBody, - List(metrics.metricsInterceptor(), new DecodeFailureInterceptor(DefaultDecodeFailureHandler[Id])), + List(metrics.metricsInterceptor(), new DecodeFailureInterceptor(DefaultDecodeFailureHandler[Identity])), _ => () ) diff --git a/perf-tests/src/main/scala/sttp/tapir/perf/nima/Nima.scala b/perf-tests/src/main/scala/sttp/tapir/perf/nima/Nima.scala index 1a4cdef2b5..f6b1dbeafa 100644 --- a/perf-tests/src/main/scala/sttp/tapir/perf/nima/Nima.scala +++ b/perf-tests/src/main/scala/sttp/tapir/perf/nima/Nima.scala @@ -2,18 +2,19 @@ package sttp.tapir.perf.nima import cats.effect.IO import io.helidon.webserver.WebServer +import sttp.shared.Identity import sttp.tapir.perf.apis._ import sttp.tapir.perf.Common._ -import sttp.tapir.server.nima.{Id, NimaServerInterpreter, NimaServerOptions} +import sttp.tapir.server.nima.{NimaServerInterpreter, NimaServerOptions} import sttp.tapir.server.ServerEndpoint object Tapir extends Endpoints { - def genEndpointsNId(count: Int): List[ServerEndpoint[Any, Id]] = genServerEndpoints[Id](count)(x => x: Id[String]) + def genEndpointsNId(count: Int): List[ServerEndpoint[Any, Identity]] = genServerEndpoints[Identity](count)(x => x: Identity[String]) } object Nima { - def runServer(endpoints: List[ServerEndpoint[Any, Id]], withServerLog: Boolean = false): IO[ServerRunner.KillSwitch] = { + def runServer(endpoints: List[ServerEndpoint[Any, Identity]], withServerLog: Boolean = false): IO[ServerRunner.KillSwitch] = { val declaredPort = Port val serverOptions = buildOptions(NimaServerOptions.customiseInterceptors, withServerLog) // Starting Nima server diff --git a/project/Versions.scala b/project/Versions.scala index 6993120d62..37678147cd 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -10,7 +10,7 @@ object Versions { val helidon = "4.0.0" val sttp = "3.9.7" val sttpModel = "1.7.11" - val sttpShared = "1.3.18" + val sttpShared = "1.3.19" val sttpApispec = "0.10.0" val akkaHttp = "10.2.10" val akkaStreams = "2.6.20" diff --git a/server/core/src/test/scala/sttp/tapir/server/TestUtil.scala b/server/core/src/test/scala/sttp/tapir/server/TestUtil.scala index 4cb6f5b882..6470dff71e 100644 --- a/server/core/src/test/scala/sttp/tapir/server/TestUtil.scala +++ b/server/core/src/test/scala/sttp/tapir/server/TestUtil.scala @@ -2,7 +2,7 @@ package sttp.tapir.server import sttp.capabilities.Streams import sttp.model._ -import sttp.tapir.TestUtil.Id +import sttp.shared.Identity import sttp.tapir._ import sttp.tapir.capabilities.NoStreams import sttp.tapir.model.ServerRequest @@ -12,9 +12,9 @@ import java.nio.charset.Charset import scala.util.{Success, Try} object TestUtil { - object TestRequestBody extends RequestBody[Id, NoStreams] { + object TestRequestBody extends RequestBody[Identity, NoStreams] { override val streams: Streams[NoStreams] = NoStreams - override def toRaw[R](serverRequest: ServerRequest, bodyType: RawBodyType[R], maxBytes: Option[Long]): Id[RawValue[R]] = ??? + override def toRaw[R](serverRequest: ServerRequest, bodyType: RawBodyType[R], maxBytes: Option[Long]): Identity[RawValue[R]] = ??? override def toStream(serverRequest: ServerRequest, maxBytes: Option[Long]): streams.BinaryStream = ??? } @@ -44,15 +44,15 @@ object TestUtil { ): String = "" } - implicit val unitBodyListener: BodyListener[Id, Unit] = new BodyListener[Id, Unit] { - override def onComplete(body: Unit)(cb: Try[Unit] => Id[Unit]): Unit = { + implicit val unitBodyListener: BodyListener[Identity, Unit] = new BodyListener[Identity, Unit] { + override def onComplete(body: Unit)(cb: Try[Unit] => Identity[Unit]): Unit = { cb(Success(())) () } } - implicit val stringBodyListener: BodyListener[Id, String] = new BodyListener[Id, String] { - override def onComplete(body: String)(cb: Try[Unit] => Id[Unit]): String = { + implicit val stringBodyListener: BodyListener[Identity, String] = new BodyListener[Identity, String] { + override def onComplete(body: String)(cb: Try[Unit] => Identity[Unit]): String = { cb(Success(())) body } diff --git a/server/core/src/test/scala/sttp/tapir/server/interpreter/ServerInterpreterTest.scala b/server/core/src/test/scala/sttp/tapir/server/interpreter/ServerInterpreterTest.scala index 834d98ee59..82118255c6 100644 --- a/server/core/src/test/scala/sttp/tapir/server/interpreter/ServerInterpreterTest.scala +++ b/server/core/src/test/scala/sttp/tapir/server/interpreter/ServerInterpreterTest.scala @@ -5,6 +5,7 @@ import org.scalatest.matchers.should.Matchers import sttp.model.Uri._ import sttp.model._ import sttp.monad.MonadError +import sttp.shared.Identity import sttp.tapir.TestUtil._ import sttp.tapir._ import sttp.tapir.capabilities.NoStreams @@ -39,11 +40,11 @@ class ServerInterpreterTest extends AnyFlatSpec with Matchers { val interceptor1 = new AddToTrailInterceptor(callTrail.append(_: String), "1") // should be called first, as it's the only request interceptor; creates an endpoint interceptor, which should be // added to the endpoint interceptor stack in the correct place - val interceptor2 = new RequestInterceptor[Id] { + val interceptor2 = new RequestInterceptor[Identity] { override def apply[R, B]( - responder: Responder[Id, B], - requestHandler: EndpointInterceptor[Id] => RequestHandler[Id, R, B] - ): RequestHandler[Id, R, B] = RequestHandler.from { (request, endpoints, monad) => + responder: Responder[Identity, B], + requestHandler: EndpointInterceptor[Identity] => RequestHandler[Identity, R, B] + ): RequestHandler[Identity, R, B] = RequestHandler.from { (request, endpoints, monad) => callTrail.append("2 request") requestHandler(new AddToTrailInterceptor(callTrail.append(_: String), "2")).apply(request, endpoints)(monad) } @@ -51,8 +52,8 @@ class ServerInterpreterTest extends AnyFlatSpec with Matchers { val interceptor3 = new AddToTrailInterceptor(callTrail.append(_: String), "3") val interpreter = - new ServerInterpreter[Any, Id, Unit, NoStreams]( - _ => List(endpoint.in(query[String]("x")).serverLogic[Id](_ => Right(()))), + new ServerInterpreter[Any, Identity, Unit, NoStreams]( + _ => List(endpoint.in(query[String]("x")).handle(_ => Right(()))), TestRequestBody, UnitToResponseBody, List(interceptor1, interceptor2, interceptor3), @@ -77,14 +78,14 @@ class ServerInterpreterTest extends AnyFlatSpec with Matchers { }(_.s) val interpreter = - new ServerInterpreter[Any, Id, Unit, NoStreams]( + new ServerInterpreter[Any, Identity, Unit, NoStreams]( _ => List( endpoint .securityIn(query[StringWrapper]("x")(Codec.listHead(addToTrailCodec("x")))) .in(query[StringWrapper]("y")(Codec.listHead(addToTrailCodec("y")))) .in(plainBody[StringWrapper](addToTrailCodec("z"))) - .serverSecurityLogic[Unit, Id](_ => Left(())) + .serverSecurityLogic[Unit, Identity](_ => Left(())) .serverLogic(_ => _ => Right(())) ), TestRequestBody, @@ -105,16 +106,16 @@ class ServerInterpreterTest extends AnyFlatSpec with Matchers { val customStatusCode = StatusCode.BadRequest val customBody = "Custom body" - val rejectInterceptor = new RejectInterceptor[Id]( + val rejectInterceptor = new RejectInterceptor[Identity]( DefaultRejectHandler((_, _) => ValuedEndpointOutput(statusCode.and(stringBody), (customStatusCode, customBody)), None) ) val interpreter = - new ServerInterpreter[Any, Id, String, NoStreams]( + new ServerInterpreter[Any, Identity, String, NoStreams]( _ => List( - endpoint.post.serverLogic[Id](_ => Right(())), - endpoint.put.serverLogic[Id](_ => Right(())) + endpoint.post.serverLogic[Identity](_ => Right(())), + endpoint.put.serverLogic[Identity](_ => Right(())) ), TestRequestBody, StringToResponseBody, @@ -129,29 +130,29 @@ class ServerInterpreterTest extends AnyFlatSpec with Matchers { response should matchPattern { case Response(ServerResponse(_, _, Some(_), _)) => } } - class AddToTrailInterceptor(addCallTrail: String => Unit, prefix: String) extends EndpointInterceptor[Id] { - override def apply[B](responder: Responder[Id, B], endpointHandler: EndpointHandler[Id, B]): EndpointHandler[Id, B] = - new EndpointHandler[Id, B] { + class AddToTrailInterceptor(addCallTrail: String => Unit, prefix: String) extends EndpointInterceptor[Identity] { + override def apply[B](responder: Responder[Identity, B], endpointHandler: EndpointHandler[Identity, B]): EndpointHandler[Identity, B] = + new EndpointHandler[Identity, B] { override def onDecodeSuccess[A, U, I]( - ctx: DecodeSuccessContext[Id, A, U, I] - )(implicit monad: MonadError[Id], bodyListener: BodyListener[Id, B]): Id[ServerResponse[B]] = { + ctx: DecodeSuccessContext[Identity, A, U, I] + )(implicit monad: MonadError[Identity], bodyListener: BodyListener[Identity, B]): Identity[ServerResponse[B]] = { addCallTrail(s"$prefix success") - endpointHandler.onDecodeSuccess(ctx)(idMonadError, bodyListener) + endpointHandler.onDecodeSuccess(ctx)(idMonad, bodyListener) } - override def onSecurityFailure[A](ctx: SecurityFailureContext[Id, A])(implicit - monad: MonadError[Id], - bodyListener: BodyListener[Id, B] - ): Id[ServerResponse[B]] = { + override def onSecurityFailure[A](ctx: SecurityFailureContext[Identity, A])(implicit + monad: MonadError[Identity], + bodyListener: BodyListener[Identity, B] + ): Identity[ServerResponse[B]] = { addCallTrail(s"$prefix security failure") - endpointHandler.onSecurityFailure(ctx)(idMonadError, bodyListener) + endpointHandler.onSecurityFailure(ctx)(idMonad, bodyListener) } override def onDecodeFailure( ctx: DecodeFailureContext - )(implicit monad: MonadError[Id], bodyListener: BodyListener[Id, B]): Id[Option[ServerResponse[B]]] = { + )(implicit monad: MonadError[Identity], bodyListener: BodyListener[Identity, B]): Identity[Option[ServerResponse[B]]] = { addCallTrail(s"$prefix failure") - endpointHandler.onDecodeFailure(ctx)(idMonadError, bodyListener) + endpointHandler.onDecodeFailure(ctx)(idMonad, bodyListener) } } } diff --git a/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/JdkHttpServer.scala b/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/JdkHttpServer.scala index cf44da0031..3c41fcd8e1 100644 --- a/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/JdkHttpServer.scala +++ b/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/JdkHttpServer.scala @@ -1,14 +1,15 @@ package sttp.tapir.server.jdkhttp import com.sun.net.httpserver._ +import sttp.shared.Identity import sttp.tapir.server.ServerEndpoint import java.net.InetSocketAddress import java.util.concurrent.Executor case class JdkHttpServer( - endpoints: Vector[ServerEndpoint[Any, Id]] = Vector.empty, - options: JdkHttpServerOptions = JdkHttpServerOptions.Default + endpoints: Vector[ServerEndpoint[Any, Identity]] = Vector.empty, + options: JdkHttpServerOptions = JdkHttpServerOptions.Default ) { /** Sets the port to which the server will be bound. */ @@ -55,9 +56,9 @@ case class JdkHttpServer( */ def options(o: JdkHttpServerOptions): JdkHttpServer = copy(options = o) - def addEndpoint(se: ServerEndpoint[Any, Id]): JdkHttpServer = addEndpoints(List(se)) + def addEndpoint(se: ServerEndpoint[Any, Identity]): JdkHttpServer = addEndpoints(List(se)) - def addEndpoints(ses: List[ServerEndpoint[Any, Id]]): JdkHttpServer = + def addEndpoints(ses: List[ServerEndpoint[Any, Identity]]): JdkHttpServer = copy(endpoints = endpoints ++ ses) /** Use this if you want to add more routes manually, outside of the routes defined with tapir. Afterwards it is necessary to call diff --git a/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/JdkHttpServerInterpreter.scala b/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/JdkHttpServerInterpreter.scala index 2bae0b1233..23a8445c64 100644 --- a/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/JdkHttpServerInterpreter.scala +++ b/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/JdkHttpServerInterpreter.scala @@ -2,6 +2,7 @@ package sttp.tapir.server.jdkhttp import com.sun.net.httpserver.{HttpExchange, HttpHandler} import sttp.model.{Header, HeaderNames} +import sttp.shared.Identity import sttp.tapir.capabilities.NoStreams import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.interceptor.RequestResult @@ -14,15 +15,15 @@ import scala.jdk.CollectionConverters._ trait JdkHttpServerInterpreter { def jdkHttpServerOptions: JdkHttpServerOptions - def toHandler(ses: List[ServerEndpoint[Any, Id]]): HttpHandler = { - val filteredEndpoints = FilterServerEndpoints[Any, Id](ses) + def toHandler(ses: List[ServerEndpoint[Any, Identity]]): HttpHandler = { + val filteredEndpoints = FilterServerEndpoints[Any, Identity](ses) val requestBody = new JdkHttpRequestBody(jdkHttpServerOptions.createFile, jdkHttpServerOptions.multipartFileThresholdBytes) val responseBody = new JdkHttpToResponseBody val interceptors = RejectInterceptor.disableWhenSingleEndpoint(jdkHttpServerOptions.interceptors, ses) - implicit val bodyListener: BodyListener[Id, JdkHttpResponseBody] = new JdkHttpBodyListener + implicit val bodyListener: BodyListener[Identity, JdkHttpResponseBody] = new JdkHttpBodyListener - val serverInterpreter = new ServerInterpreter[Any, Id, JdkHttpResponseBody, NoStreams]( + val serverInterpreter = new ServerInterpreter[Any, Identity, JdkHttpResponseBody, NoStreams]( filteredEndpoints, requestBody, responseBody, diff --git a/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/JdkHttpServerOptions.scala b/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/JdkHttpServerOptions.scala index fd163f218e..7166ae9abc 100644 --- a/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/JdkHttpServerOptions.scala +++ b/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/JdkHttpServerOptions.scala @@ -1,6 +1,7 @@ package sttp.tapir.server.jdkhttp import com.sun.net.httpserver.HttpsConfigurator +import sttp.shared.Identity import sttp.tapir.{Defaults, TapirFile} import sttp.tapir.model.ServerRequest import sttp.tapir.server.interceptor.log.{DefaultServerLog, ServerLog} @@ -45,21 +46,21 @@ import java.util.logging.{Level, Logger} * entirely in memory. Default is 50MB. */ case class JdkHttpServerOptions( - interceptors: List[Interceptor[Id]], - createFile: ServerRequest => TapirFile, - deleteFile: TapirFile => Unit, - send404WhenRequestNotHandled: Boolean = true, - basePath: String = "/", - port: Int = 0, - host: String = "0.0.0.0", - executor: Option[Executor] = None, - httpsConfigurator: Option[HttpsConfigurator] = None, - backlogSize: Int = 0, - multipartFileThresholdBytes: Long = 52_428_800 + interceptors: List[Interceptor[Identity]], + createFile: ServerRequest => TapirFile, + deleteFile: TapirFile => Unit, + send404WhenRequestNotHandled: Boolean = true, + basePath: String = "/", + port: Int = 0, + host: String = "0.0.0.0", + executor: Option[Executor] = None, + httpsConfigurator: Option[HttpsConfigurator] = None, + backlogSize: Int = 0, + multipartFileThresholdBytes: Long = 52_428_800 ) { require(0 <= port && port <= 65535, "Port has to be in 1-65535 range or 0 if random!") - def prependInterceptor(i: Interceptor[Id]): JdkHttpServerOptions = copy(interceptors = i :: interceptors) - def appendInterceptor(i: Interceptor[Id]): JdkHttpServerOptions = copy(interceptors = interceptors :+ i) + def prependInterceptor(i: Interceptor[Identity]): JdkHttpServerOptions = copy(interceptors = i :: interceptors) + def appendInterceptor(i: Interceptor[Identity]): JdkHttpServerOptions = copy(interceptors = interceptors :+ i) } object JdkHttpServerOptions { @@ -80,18 +81,18 @@ object JdkHttpServerOptions { ) } - private def default(interceptors: List[Interceptor[Id]]): JdkHttpServerOptions = + private def default(interceptors: List[Interceptor[Identity]]): JdkHttpServerOptions = JdkHttpServerOptions(interceptors, _ => Defaults.createTempFile(), Defaults.deleteFile()) - def customiseInterceptors: CustomiseInterceptors[Id, JdkHttpServerOptions] = + def customiseInterceptors: CustomiseInterceptors[Identity, JdkHttpServerOptions] = CustomiseInterceptors( - createOptions = (ci: CustomiseInterceptors[Id, JdkHttpServerOptions]) => default(ci.interceptors) + createOptions = (ci: CustomiseInterceptors[Identity, JdkHttpServerOptions]) => default(ci.interceptors) ).serverLog(defaultServerLog) private val log = Logger.getLogger(classOf[JdkHttpServerInterpreter].getName) - lazy val defaultServerLog: ServerLog[Id] = - DefaultServerLog[Id]( + lazy val defaultServerLog: ServerLog[Identity] = + DefaultServerLog[Identity]( doLogWhenReceived = debugLog(_, None), doLogWhenHandled = debugLog, doLogAllDecodeFailures = debugLog, diff --git a/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/internal/JdkHttpBodyListener.scala b/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/internal/JdkHttpBodyListener.scala index 7174335ac3..1402bd0604 100644 --- a/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/internal/JdkHttpBodyListener.scala +++ b/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/internal/JdkHttpBodyListener.scala @@ -1,12 +1,13 @@ package sttp.tapir.server.jdkhttp package internal +import sttp.shared.Identity import sttp.tapir.server.interpreter.BodyListener import java.io.InputStream import scala.util.{Success, Try} -private[jdkhttp] class JdkHttpBodyListener extends BodyListener[Id, JdkHttpResponseBody] { +private[jdkhttp] class JdkHttpBodyListener extends BodyListener[Identity, JdkHttpResponseBody] { override def onComplete(body: JdkHttpResponseBody)(cb: Try[Unit] => Unit): JdkHttpResponseBody = { val (is, maybeContentSize) = body diff --git a/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/internal/JdkHttpRequestBody.scala b/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/internal/JdkHttpRequestBody.scala index 51a27717b4..6dd6cc0689 100644 --- a/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/internal/JdkHttpRequestBody.scala +++ b/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/internal/JdkHttpRequestBody.scala @@ -4,6 +4,7 @@ package internal import com.sun.net.httpserver.HttpExchange import sttp.capabilities import sttp.model.Part +import sttp.shared.Identity import sttp.tapir.capabilities.NoStreams import sttp.tapir.model.ServerRequest import sttp.tapir.server.interpreter.{RawValue, RequestBody} @@ -15,7 +16,7 @@ import java.nio.ByteBuffer import java.nio.file.{Files, StandardCopyOption} private[jdkhttp] class JdkHttpRequestBody(createFile: ServerRequest => TapirFile, multipartFileThresholdBytes: Long) - extends RequestBody[Id, NoStreams] { + extends RequestBody[Identity, NoStreams] { override val streams: capabilities.Streams[NoStreams] = NoStreams override def toRaw[RAW](serverRequest: ServerRequest, bodyType: RawBodyType[RAW], maxBytes: Option[Long]): RawValue[RAW] = { diff --git a/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/internal/package.scala b/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/internal/package.scala index e62f1311e1..8e68f5cf35 100644 --- a/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/internal/package.scala +++ b/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/internal/package.scala @@ -1,19 +1,8 @@ package sttp.tapir.server.jdkhttp -import sttp.monad.MonadError +import sttp.monad.{IdentityMonad, MonadError} +import sttp.shared.Identity package object internal { - - private[jdkhttp] implicit val idMonad: MonadError[Id] = new MonadError[Id] { - override def unit[T](t: T): Id[T] = t - override def map[T, T2](fa: Id[T])(f: T => T2): Id[T2] = f(fa) - override def flatMap[T, T2](fa: Id[T])(f: T => Id[T2]): Id[T2] = f(fa) - override def error[T](t: Throwable): Id[T] = throw t - override protected def handleWrappedError[T](rt: Id[T])(h: PartialFunction[Throwable, Id[T]]): Id[T] = rt - override def eval[T](t: => T): Id[T] = t - override def ensure[T](f: Id[T], e: => Id[Unit]): Id[T] = - try f - finally e - } - + private[jdkhttp] implicit val idMonad: MonadError[Identity] = IdentityMonad } diff --git a/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/package.scala b/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/package.scala index 547f427e2a..dbb17dabf2 100644 --- a/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/package.scala +++ b/server/jdkhttp-server/src/main/scala/sttp/tapir/server/jdkhttp/package.scala @@ -1,9 +1,6 @@ package sttp.tapir.server -import sttp.tapir.Endpoint - import java.io.InputStream -import scala.reflect.ClassTag package object jdkhttp { @@ -11,76 +8,4 @@ package object jdkhttp { type HttpsConfigurator = com.sun.net.httpserver.HttpsConfigurator type JdkHttpResponseBody = (InputStream, Option[Long]) - - type Id[A] = A - - implicit class IdEndpointOps[A, I, E, O, R](private val endpoint: Endpoint[A, I, E, O, R]) extends AnyVal { - def handle(f: I => Either[E, O])(implicit aIsUnit: A =:= Unit): ServerEndpoint.Full[Unit, Unit, I, E, O, R, Id] = - endpoint.serverLogic[Id](f) - - def handleSuccess(f: I => O)(implicit aIsUnit: A =:= Unit): ServerEndpoint.Full[Unit, Unit, I, E, O, R, Id] = - endpoint.serverLogicSuccess[Id](f) - - def handleError(f: I => E)(implicit aIsUnit: A =:= Unit): ServerEndpoint.Full[Unit, Unit, I, E, O, R, Id] = - endpoint.serverLogicError[Id](f) - - def handleRecoverErrors(f: I => O)(implicit - eIsThrowable: E <:< Throwable, - eClassTag: ClassTag[E], - aIsUnit: A =:= Unit - ): ServerEndpoint.Full[Unit, Unit, I, E, O, R, Id] = - endpoint.serverLogicRecoverErrors[Id](f) - - def handleOption( - f: I => Option[O] - )(implicit aIsUnit: A =:= Unit, eIsUnit: E =:= Unit): ServerEndpoint.Full[Unit, Unit, I, Unit, O, R, Id] = - endpoint.serverLogicOption[Id](f) - - def handleSecurity[PRINCIPAL](f: A => Either[E, PRINCIPAL]): PartialServerEndpoint[A, PRINCIPAL, I, E, O, R, Id] = - endpoint.serverSecurityLogic[PRINCIPAL, Id](f) - - def handleSecuritySuccess[PRINCIPAL]( - f: A => PRINCIPAL - ): PartialServerEndpoint[A, PRINCIPAL, I, E, O, R, Id] = - endpoint.serverSecurityLogicSuccess[PRINCIPAL, Id](f) - - def handleSecurityError[PRINCIPAL]( - f: A => E - ): PartialServerEndpoint[A, PRINCIPAL, I, E, O, R, Id] = - endpoint.serverSecurityLogicError[PRINCIPAL, Id](f) - - def handleSecurityRecoverErrors[PRINCIPAL]( - f: A => PRINCIPAL - )(implicit eIsThrowable: E <:< Throwable, eClassTag: ClassTag[E]): PartialServerEndpoint[A, PRINCIPAL, I, E, O, R, Id] = - endpoint.serverSecurityLogicRecoverErrors[PRINCIPAL, Id](f) - - def handleSecurityOption[PRINCIPAL]( - f: A => Option[PRINCIPAL] - )(implicit eIsUnit: E =:= Unit): PartialServerEndpoint[A, PRINCIPAL, I, Unit, O, R, Id] = - endpoint.serverSecurityLogicOption[PRINCIPAL, Id](f) - - def handleSecurityWithOutput[PRINCIPAL]( - f: A => Either[E, (O, PRINCIPAL)] - ): PartialServerEndpointWithSecurityOutput[A, PRINCIPAL, I, E, O, Unit, R, Id] = - endpoint.serverSecurityLogicWithOutput[PRINCIPAL, Id](f) - - def handleSecuritySuccessWithOutput[PRINCIPAL]( - f: A => (O, PRINCIPAL) - ): PartialServerEndpointWithSecurityOutput[A, PRINCIPAL, I, E, O, Unit, R, Id] = - endpoint.serverSecurityLogicSuccessWithOutput[PRINCIPAL, Id](f) - - def handleSecurityRecoverErrorsWithOutput[PRINCIPAL]( - f: A => (O, PRINCIPAL) - )(implicit - eIsThrowable: E <:< Throwable, - eClassTag: ClassTag[E] - ): PartialServerEndpointWithSecurityOutput[A, PRINCIPAL, I, E, O, Unit, R, Id] = - endpoint.serverSecurityLogicRecoverErrorsWithOutput[PRINCIPAL, Id](f) - - def handleSecurityOptionWithOutput[PRINCIPAL]( - f: A => Option[(O, PRINCIPAL)] - )(implicit eIsUnit: E =:= Unit): PartialServerEndpointWithSecurityOutput[A, PRINCIPAL, I, Unit, O, Unit, R, Id] = - endpoint.serverSecurityLogicOptionWithOutput[PRINCIPAL, Id](f) - - } } diff --git a/server/jdkhttp-server/src/test/scala/sttp/tapir/server/jdkhttp/JdkHttpTestServerInterpreter.scala b/server/jdkhttp-server/src/test/scala/sttp/tapir/server/jdkhttp/JdkHttpTestServerInterpreter.scala index fcd76b626d..d26881b7a2 100644 --- a/server/jdkhttp-server/src/test/scala/sttp/tapir/server/jdkhttp/JdkHttpTestServerInterpreter.scala +++ b/server/jdkhttp-server/src/test/scala/sttp/tapir/server/jdkhttp/JdkHttpTestServerInterpreter.scala @@ -2,6 +2,7 @@ package sttp.tapir.server.jdkhttp import cats.data.NonEmptyList import cats.effect.{IO, Resource} import com.sun.net.httpserver.{HttpExchange, HttpHandler, HttpServer} +import sttp.shared.Identity import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.tests.TestServerInterpreter import sttp.tapir.tests._ @@ -10,8 +11,8 @@ import java.net.InetSocketAddress import scala.annotation.tailrec import scala.concurrent.duration.FiniteDuration -class JdkHttpTestServerInterpreter() extends TestServerInterpreter[Id, Any, JdkHttpServerOptions, HttpHandler] { - override def route(es: List[ServerEndpoint[Any, Id]], interceptors: Interceptors): HttpHandler = { +class JdkHttpTestServerInterpreter() extends TestServerInterpreter[Identity, Any, JdkHttpServerOptions, HttpHandler] { + override def route(es: List[ServerEndpoint[Any, Identity]], interceptors: Interceptors): HttpHandler = { val serverOptions: JdkHttpServerOptions = interceptors(JdkHttpServerOptions.customiseInterceptors).options.copy(send404WhenRequestNotHandled = false) JdkHttpServerInterpreter(serverOptions).toHandler(es) diff --git a/server/netty-server/src/main/scala/sttp/tapir/server/netty/internal/NettyRequestBody.scala b/server/netty-server/src/main/scala/sttp/tapir/server/netty/internal/NettyRequestBody.scala index f7cd386dbb..bcae1a9e39 100644 --- a/server/netty-server/src/main/scala/sttp/tapir/server/netty/internal/NettyRequestBody.scala +++ b/server/netty-server/src/main/scala/sttp/tapir/server/netty/internal/NettyRequestBody.scala @@ -8,12 +8,12 @@ import sttp.capabilities.Streams import sttp.model.HeaderNames import sttp.monad.MonadError import sttp.monad.syntax._ -import sttp.tapir.{FileRange, InputStreamRange, RawBodyType, TapirFile} import sttp.tapir.model.ServerRequest import sttp.tapir.server.interpreter.{RawValue, RequestBody} import sttp.tapir.server.netty.internal.reactivestreams.SubscriberInputStream +import sttp.tapir.{FileRange, InputStreamRange, RawBodyType, TapirFile} -import java.io.{ByteArrayInputStream, InputStream} +import java.io.InputStream import java.nio.ByteBuffer /** Common logic for processing request body in all Netty backends. It requires particular backends to implement a few operations. */ diff --git a/server/netty-server/src/main/scala/sttp/tapir/server/netty/internal/RunAsync.scala b/server/netty-server/src/main/scala/sttp/tapir/server/netty/internal/RunAsync.scala index dd1ba28117..2f392777e8 100644 --- a/server/netty-server/src/main/scala/sttp/tapir/server/netty/internal/RunAsync.scala +++ b/server/netty-server/src/main/scala/sttp/tapir/server/netty/internal/RunAsync.scala @@ -1,15 +1,15 @@ package sttp.tapir.server.netty.internal +import sttp.shared.Identity + import scala.concurrent.Future trait RunAsync[F[_]] { def apply(f: => F[Unit]): Unit } object RunAsync { - type Id[A] = A - - final val Id: RunAsync[Id] = new RunAsync[Id] { - override def apply(f: => Id[Unit]): Unit = f + final val Id: RunAsync[Identity] = new RunAsync[Identity] { + override def apply(f: => Identity[Unit]): Unit = f } final val Future: RunAsync[Future] = new RunAsync[Future] { diff --git a/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/NettySyncServer.scala b/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/NettySyncServer.scala index 5901be3fa8..2e8e11e693 100644 --- a/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/NettySyncServer.scala +++ b/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/NettySyncServer.scala @@ -7,6 +7,7 @@ import io.netty.channel.unix.DomainSocketAddress import io.netty.channel.{Channel, EventLoopGroup} import io.netty.util.concurrent.DefaultEventExecutor import sttp.capabilities.WebSockets +import sttp.shared.Identity import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.model.ServerResponse import sttp.tapir.server.netty.internal.{NettyBootstrap, NettyServerHandler} @@ -26,23 +27,23 @@ import scala.util.control.NonFatal * options. */ private[sync] case class NettySyncServerEndpointListOverridenOptions( - ses: List[ServerEndpoint[OxStreams & WebSockets, Id]], - overridenOptions: NettySyncServerOptions + ses: List[ServerEndpoint[OxStreams & WebSockets, Identity]], + overridenOptions: NettySyncServerOptions ) case class NettySyncServer( - endpoints: List[ServerEndpoint[OxStreams & WebSockets, Id]], - endpointsWithOptions: List[NettySyncServerEndpointListOverridenOptions], - options: NettySyncServerOptions, - config: NettyConfig + endpoints: List[ServerEndpoint[OxStreams & WebSockets, Identity]], + endpointsWithOptions: List[NettySyncServerEndpointListOverridenOptions], + options: NettySyncServerOptions, + config: NettyConfig ): private val executor = Executors.newVirtualThreadPerTaskExecutor() - def addEndpoint(se: ServerEndpoint[OxStreams & WebSockets, Id]): NettySyncServer = addEndpoints(List(se)) - def addEndpoint(se: ServerEndpoint[OxStreams & WebSockets, Id], overrideOptions: NettySyncServerOptions): NettySyncServer = + def addEndpoint(se: ServerEndpoint[OxStreams & WebSockets, Identity]): NettySyncServer = addEndpoints(List(se)) + def addEndpoint(se: ServerEndpoint[OxStreams & WebSockets, Identity], overrideOptions: NettySyncServerOptions): NettySyncServer = addEndpoints(List(se), overrideOptions) - def addEndpoints(ses: List[ServerEndpoint[OxStreams & WebSockets, Id]]): NettySyncServer = copy(endpoints = endpoints ++ ses) - def addEndpoints(ses: List[ServerEndpoint[OxStreams & WebSockets, Id]], overrideOptions: NettySyncServerOptions): NettySyncServer = + def addEndpoints(ses: List[ServerEndpoint[OxStreams & WebSockets, Identity]]): NettySyncServer = copy(endpoints = endpoints ++ ses) + def addEndpoints(ses: List[ServerEndpoint[OxStreams & WebSockets, Identity]], overrideOptions: NettySyncServerOptions): NettySyncServer = copy(endpointsWithOptions = endpointsWithOptions :+ NettySyncServerEndpointListOverridenOptions(ses, overrideOptions)) def options(o: NettySyncServerOptions): NettySyncServer = copy(options = o) @@ -81,7 +82,7 @@ case class NettySyncServer( never } - private[netty] def start(routes: List[Route[Id]]): NettySyncServerBinding = + private[netty] def start(routes: List[Route[Identity]]): NettySyncServerBinding = startUsingSocketOverride[InetSocketAddress](routes, None) match case (socket, stop) => NettySyncServerBinding(socket, stop) @@ -97,12 +98,12 @@ case class NettySyncServer( ) startUsingSocketOverride(routes, socketOverride) - private def startUsingSocketOverride[SA <: SocketAddress](routes: List[Route[Id]], socketOverride: Option[SA]): (SA, () => Unit) = + private def startUsingSocketOverride[SA <: SocketAddress](routes: List[Route[Identity]], socketOverride: Option[SA]): (SA, () => Unit) = val eventLoopGroup = config.eventLoopConfig.initEventLoopGroup() val route = Route.combine(routes) def unsafeRunF( - callToExecute: () => Id[ServerResponse[NettyResponse]] + callToExecute: () => Identity[ServerResponse[NettyResponse]] ): (Future[ServerResponse[NettyResponse]], () => Future[Unit]) = val scalaPromise = Promise[ServerResponse[NettyResponse]]() val jFuture: JFuture[?] = executor.submit(new Runnable { diff --git a/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/NettySyncServerInterpreter.scala b/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/NettySyncServerInterpreter.scala index f00ba3c2c8..7deedb3ad1 100644 --- a/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/NettySyncServerInterpreter.scala +++ b/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/NettySyncServerInterpreter.scala @@ -4,6 +4,7 @@ import internal.{NettySyncRequestBody, NettySyncToResponseBody} import internal.ox.OxDispatcher import sttp.capabilities.WebSockets import sttp.monad.syntax._ +import sttp.shared.Identity import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.interceptor.reject.RejectInterceptor import sttp.tapir.server.interceptor.RequestResult @@ -18,18 +19,18 @@ trait NettySyncServerInterpreter: * processor. */ def toRoute( - ses: List[ServerEndpoint[OxStreams & WebSockets, Id]], - oxDispatcher: OxDispatcher + ses: List[ServerEndpoint[OxStreams & WebSockets, Identity]], + oxDispatcher: OxDispatcher ): IdRoute = - implicit val bodyListener: BodyListener[Id, NettyResponse] = new NettyBodyListener(RunAsync.Id) - val serverInterpreter = new ServerInterpreter[OxStreams with WebSockets, Id, NettyResponse, OxStreams]( + implicit val bodyListener: BodyListener[Identity, NettyResponse] = new NettyBodyListener(RunAsync.Id) + val serverInterpreter = new ServerInterpreter[OxStreams with WebSockets, Identity, NettyResponse, OxStreams]( FilterServerEndpoints(ses), new NettySyncRequestBody(nettyServerOptions.createFile), new NettySyncToResponseBody(RunAsync.Id, oxDispatcher), RejectInterceptor.disableWhenSingleEndpoint(nettyServerOptions.interceptors, ses), nettyServerOptions.deleteFile ) - val handler: Route[Id] = { (request: NettyServerRequest) => + val handler: Route[Identity] = { (request: NettyServerRequest) => serverInterpreter(request) .map { case RequestResult.Response(response) => Some(response) diff --git a/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/NettySyncServerOptions.scala b/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/NettySyncServerOptions.scala index c79992717b..f7b94960ae 100644 --- a/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/NettySyncServerOptions.scala +++ b/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/NettySyncServerOptions.scala @@ -1,6 +1,7 @@ package sttp.tapir.server.netty.sync import org.slf4j.LoggerFactory +import sttp.shared.Identity import sttp.tapir.model.ServerRequest import sttp.tapir.server.interceptor.log.{DefaultServerLog, ServerLog} import sttp.tapir.server.netty.internal.NettyDefaults @@ -8,12 +9,12 @@ import sttp.tapir.server.interceptor.{CustomiseInterceptors, Interceptor} import sttp.tapir.{Defaults, TapirFile} case class NettySyncServerOptions( - interceptors: List[Interceptor[Id]], - createFile: ServerRequest => TapirFile, - deleteFile: TapirFile => Unit + interceptors: List[Interceptor[Identity]], + createFile: ServerRequest => TapirFile, + deleteFile: TapirFile => Unit ): - def prependInterceptor(i: Interceptor[Id]): NettySyncServerOptions = copy(interceptors = i :: interceptors) - def appendInterceptor(i: Interceptor[Id]): NettySyncServerOptions = copy(interceptors = interceptors :+ i) + def prependInterceptor(i: Interceptor[Identity]): NettySyncServerOptions = copy(interceptors = i :: interceptors) + def appendInterceptor(i: Interceptor[Identity]): NettySyncServerOptions = copy(interceptors = interceptors :+ i) object NettySyncServerOptions: @@ -23,7 +24,7 @@ object NettySyncServerOptions: def default: NettySyncServerOptions = customiseInterceptors.options private def default( - interceptors: List[Interceptor[Id]] + interceptors: List[Interceptor[Identity]] ): NettySyncServerOptions = NettySyncServerOptions( interceptors, @@ -34,15 +35,15 @@ object NettySyncServerOptions: /** Customise the interceptors that are being used when exposing endpoints as a server. By default uses TCP sockets (the most common * case), but this can be later customised using [[NettySyncServerOptions#nettyOptions()]]. */ - def customiseInterceptors: CustomiseInterceptors[Id, NettySyncServerOptions] = + def customiseInterceptors: CustomiseInterceptors[Identity, NettySyncServerOptions] = CustomiseInterceptors( - createOptions = (ci: CustomiseInterceptors[Id, NettySyncServerOptions]) => default(ci.interceptors) + createOptions = (ci: CustomiseInterceptors[Identity, NettySyncServerOptions]) => default(ci.interceptors) ).serverLog(defaultServerLog) private val log = LoggerFactory.getLogger(getClass.getName) - lazy val defaultServerLog: ServerLog[Id] = - DefaultServerLog[Id]( + lazy val defaultServerLog: ServerLog[Identity] = + DefaultServerLog[Identity]( doLogWhenReceived = debugLog(_, None), doLogWhenHandled = debugLog, doLogAllDecodeFailures = debugLog, diff --git a/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/internal/NettySyncRequestBody.scala b/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/internal/NettySyncRequestBody.scala index f6fc2dae23..d6cc30a3f3 100644 --- a/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/internal/NettySyncRequestBody.scala +++ b/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/internal/NettySyncRequestBody.scala @@ -4,16 +4,17 @@ import io.netty.handler.codec.http.HttpContent import org.playframework.netty.http.StreamedHttpRequest import org.reactivestreams.Publisher import sttp.capabilities -import sttp.monad.MonadError +import sttp.monad.{IdentityMonad, MonadError} +import sttp.shared.Identity import sttp.tapir.TapirFile import sttp.tapir.model.ServerRequest import sttp.tapir.server.netty.internal.NettyRequestBody import sttp.tapir.server.netty.internal.reactivestreams.{FileWriterSubscriber, SimpleSubscriber} import sttp.tapir.server.netty.sync.* -private[sync] class NettySyncRequestBody(val createFile: ServerRequest => TapirFile) extends NettyRequestBody[Id, OxStreams]: +private[sync] class NettySyncRequestBody(val createFile: ServerRequest => TapirFile) extends NettyRequestBody[Identity, OxStreams]: - override given monad: MonadError[Id] = idMonad + override given monad: MonadError[Identity] = IdentityMonad override val streams: capabilities.Streams[OxStreams] = OxStreams override def publisherToBytes(publisher: Publisher[HttpContent], contentLength: Option[Long], maxBytes: Option[Long]): Array[Byte] = diff --git a/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/internal/NettySyncToResponseBody.scala b/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/internal/NettySyncToResponseBody.scala index b205ba61c7..57bd6673a7 100644 --- a/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/internal/NettySyncToResponseBody.scala +++ b/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/internal/NettySyncToResponseBody.scala @@ -5,6 +5,7 @@ import io.netty.channel.ChannelHandlerContext import sttp.capabilities import sttp.model.HasHeaders import sttp.monad.MonadError +import sttp.shared.Identity import sttp.tapir.server.interpreter.ToResponseBody import sttp.tapir.server.netty.NettyResponse import sttp.tapir.server.netty.NettyResponseContent.ReactiveWebSocketProcessorNettyResponseContent @@ -15,7 +16,7 @@ import sttp.tapir.* import java.nio.charset.Charset -private[sync] class NettySyncToResponseBody(runAsync: RunAsync[Id], oxDispatcher: OxDispatcher)(using me: MonadError[Id]) +private[sync] class NettySyncToResponseBody(runAsync: RunAsync[Identity], oxDispatcher: OxDispatcher)(using me: MonadError[Identity]) extends ToResponseBody[NettyResponse, OxStreams]: val delegate = new NettyToResponseBody(runAsync)(me) diff --git a/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/sync.scala b/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/sync.scala index 382f0cb008..2aabd05f95 100644 --- a/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/sync.scala +++ b/server/netty-server/sync/src/main/scala/sttp/tapir/server/netty/sync/sync.scala @@ -1,19 +1,8 @@ package sttp.tapir.server.netty -import sttp.monad.MonadError +import sttp.monad.{MonadError, IdentityMonad} +import sttp.shared.Identity package object sync: - type Id[X] = X - type IdRoute = Route[Id] - - private[sync] implicit val idMonad: MonadError[Id] = new MonadError[Id] { - override def unit[T](t: T): Id[T] = t - override def map[T, T2](fa: Id[T])(f: T => T2): Id[T2] = f(fa) - override def flatMap[T, T2](fa: Id[T])(f: T => Id[T2]): Id[T2] = f(fa) - override def error[T](t: Throwable): Id[T] = throw t - override protected def handleWrappedError[T](rt: Id[T])(h: PartialFunction[Throwable, Id[T]]): Id[T] = rt - override def eval[T](t: => T): Id[T] = t - override def ensure[T](f: Id[T], e: => Id[Unit]): Id[T] = - try f - finally e - } + type IdRoute = Route[Identity] + private[sync] implicit val idMonad: MonadError[Identity] = IdentityMonad diff --git a/server/netty-server/sync/src/test/scala/sttp/tapir/server/netty/sync/NettySyncServerTest.scala b/server/netty-server/sync/src/test/scala/sttp/tapir/server/netty/sync/NettySyncServerTest.scala index b37d73ac1f..d0301fabbb 100644 --- a/server/netty-server/sync/src/test/scala/sttp/tapir/server/netty/sync/NettySyncServerTest.scala +++ b/server/netty-server/sync/src/test/scala/sttp/tapir/server/netty/sync/NettySyncServerTest.scala @@ -16,6 +16,7 @@ import sttp.capabilities.WebSockets import sttp.capabilities.fs2.Fs2Streams import sttp.client3.* import sttp.model.* +import sttp.shared.Identity import sttp.tapir.* import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.tests.* @@ -35,7 +36,7 @@ class NettySyncServerTest extends AsyncFunSuite with BeforeAndAfterAll { val interpreter = new NettySyncTestServerInterpreter(eventLoopGroup) val createServerTest = new NettySyncCreateServerTest(backend, interpreter) - val sleeper: Sleeper[Id] = (duration: FiniteDuration) => Thread.sleep(duration.toMillis) + val sleeper: Sleeper[Identity] = (duration: FiniteDuration) => Thread.sleep(duration.toMillis) val tests = new AllServerTests(createServerTest, interpreter, backend, staticContent = false, multipart = false) @@ -96,7 +97,7 @@ class NettySyncServerTest extends AsyncFunSuite with BeforeAndAfterAll { class NettySyncCreateServerTest( backend: SttpBackend[IO, Fs2Streams[IO] & WebSockets], interpreter: NettySyncTestServerInterpreter -) extends CreateServerTest[Id, OxStreams & WebSockets, NettySyncServerOptions, IdRoute] { +) extends CreateServerTest[Identity, OxStreams & WebSockets, NettySyncServerOptions, IdRoute] { private val logger = LoggerFactory.getLogger(getClass.getName) @@ -105,13 +106,13 @@ class NettySyncCreateServerTest( testNameSuffix: String = "", interceptors: Interceptors = identity )( - fn: I => Id[Either[E, O]] + fn: I => Identity[Either[E, O]] )(runTest: (SttpBackend[IO, Fs2Streams[IO] & WebSockets], Uri) => IO[Assertion]): Test = { testServerLogic(e.serverLogic(fn), testNameSuffix, interceptors)(runTest) } override def testServerLogic( - e: ServerEndpoint[OxStreams & WebSockets, Id], + e: ServerEndpoint[OxStreams & WebSockets, Identity], testNameSuffix: String = "", interceptors: Interceptors = identity )( @@ -121,7 +122,7 @@ class NettySyncCreateServerTest( } override def testServerLogicWithStop( - e: ServerEndpoint[OxStreams & WebSockets, Id], + e: ServerEndpoint[OxStreams & WebSockets, Identity], testNameSuffix: String = "", interceptors: Interceptors = identity, gracefulShutdownTimeout: Option[FiniteDuration] = None @@ -160,7 +161,7 @@ class NettySyncCreateServerTest( } } - def testServer(name: String, es: NonEmptyList[ServerEndpoint[OxStreams & WebSockets, Id]])( + def testServer(name: String, es: NonEmptyList[ServerEndpoint[OxStreams & WebSockets, Identity]])( runTest: (SttpBackend[IO, Fs2Streams[IO] & WebSockets], Uri) => IO[Assertion] ): Test = { Test(name) { diff --git a/server/netty-server/sync/src/test/scala/sttp/tapir/server/netty/sync/NettySyncTestServerInterpreter.scala b/server/netty-server/sync/src/test/scala/sttp/tapir/server/netty/sync/NettySyncTestServerInterpreter.scala index 7144039ab6..ee038bc859 100644 --- a/server/netty-server/sync/src/test/scala/sttp/tapir/server/netty/sync/NettySyncTestServerInterpreter.scala +++ b/server/netty-server/sync/src/test/scala/sttp/tapir/server/netty/sync/NettySyncTestServerInterpreter.scala @@ -5,6 +5,7 @@ import cats.effect.{IO, Resource} import io.netty.channel.nio.NioEventLoopGroup import internal.ox.OxDispatcher import ox.* +import sttp.shared.Identity import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.netty.NettyConfig import sttp.tapir.server.tests.TestServerInterpreter @@ -14,15 +15,15 @@ import scala.concurrent.duration.FiniteDuration import sttp.capabilities.WebSockets class NettySyncTestServerInterpreter(eventLoopGroup: NioEventLoopGroup) - extends TestServerInterpreter[Id, OxStreams with WebSockets, NettySyncServerOptions, IdRoute] { - override def route(es: List[ServerEndpoint[OxStreams with WebSockets, Id]], interceptors: Interceptors): IdRoute = { + extends TestServerInterpreter[Identity, OxStreams with WebSockets, NettySyncServerOptions, IdRoute] { + override def route(es: List[ServerEndpoint[OxStreams with WebSockets, Identity]], interceptors: Interceptors): IdRoute = { val serverOptions: NettySyncServerOptions = interceptors(NettySyncServerOptions.customiseInterceptors).options supervised { // not a correct way, but this method is only used in a few tests which don't test anything related to scopes NettySyncServerInterpreter(serverOptions).toRoute(es, OxDispatcher.create) } } - def route(es: List[ServerEndpoint[OxStreams with WebSockets, Id]], interceptors: Interceptors)(using Ox): IdRoute = { + def route(es: List[ServerEndpoint[OxStreams with WebSockets, Identity]], interceptors: Interceptors)(using Ox): IdRoute = { val serverOptions: NettySyncServerOptions = interceptors(NettySyncServerOptions.customiseInterceptors).options supervised { // not a correct way, but this method is only used in a few tests which don't test anything related to scopes NettySyncServerInterpreter(serverOptions).toRoute(es, OxDispatcher.create) @@ -54,9 +55,9 @@ class NettySyncTestServerInterpreter(eventLoopGroup: NioEventLoopGroup) useInScope(NettySyncServer(options, customizedConfig).start(routes.toList))(_.stop()) def scopedServerWithInterceptorsStop( - endpoint: ServerEndpoint[OxStreams with WebSockets, Id], - interceptors: Interceptors = identity, - gracefulShutdownTimeout: Option[FiniteDuration] = None + endpoint: ServerEndpoint[OxStreams with WebSockets, Identity], + interceptors: Interceptors = identity, + gracefulShutdownTimeout: Option[FiniteDuration] = None )(using Ox): NettySyncServerBinding = val config = NettyConfig.default.eventLoopGroup(eventLoopGroup).randomPort.withDontShutdownEventLoopGroupOnClose.noGracefulShutdown @@ -65,8 +66,8 @@ class NettySyncTestServerInterpreter(eventLoopGroup: NioEventLoopGroup) useInScope(NettySyncServer(customizedConfig).addEndpoint(endpoint, options).start())(_.stop()) def scopedServerWithStop( - endpoints: NonEmptyList[ServerEndpoint[OxStreams with WebSockets, Id]], - gracefulShutdownTimeout: Option[FiniteDuration] = None + endpoints: NonEmptyList[ServerEndpoint[OxStreams with WebSockets, Identity]], + gracefulShutdownTimeout: Option[FiniteDuration] = None )(using Ox): NettySyncServerBinding = val config = NettyConfig.default.eventLoopGroup(eventLoopGroup).randomPort.withDontShutdownEventLoopGroupOnClose.noGracefulShutdown diff --git a/server/netty-server/sync/src/test/scala/sttp/tapir/server/netty/sync/perf/NettySyncServerRunner.scala b/server/netty-server/sync/src/test/scala/sttp/tapir/server/netty/sync/perf/NettySyncServerRunner.scala index a21fc268f4..b020b85df1 100644 --- a/server/netty-server/sync/src/test/scala/sttp/tapir/server/netty/sync/perf/NettySyncServerRunner.scala +++ b/server/netty-server/sync/src/test/scala/sttp/tapir/server/netty/sync/perf/NettySyncServerRunner.scala @@ -2,12 +2,12 @@ package sttp.tapir.server.netty.sync.perf import ox.* import ox.channels.* +import sttp.shared.Identity import sttp.tapir.server.netty.sync.NettySyncServerOptions import sttp.tapir.server.netty.sync.NettySyncServerBinding import sttp.tapir.server.netty.sync.NettySyncServer import sttp.tapir.* -import sttp.tapir.server.netty.sync.Id import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.model.EndpointExtensions.* import sttp.tapir.server.netty.sync.OxStreams @@ -72,7 +72,7 @@ object NettySyncServerRunner { .ignorePong(true) .autoPing(None) ) - val wsServerEndpoint = wsEndpoint.serverLogicSuccess[Id](_ => wsPipe) + val wsServerEndpoint = wsEndpoint.handleSuccess(_ => wsPipe) val endpoints = genEndpointsId(1) @@ -94,5 +94,5 @@ object NettySyncServerRunner { } def genServerEndpoints[F[_]](routeCount: Int)(reply: String => F[String]): List[ServerEndpoint[Any, F]] = serverEndpoints[F](reply).flatMap(gen => (0 to routeCount).map(i => gen(i))) - def genEndpointsId(count: Int): List[ServerEndpoint[Any, Id]] = genServerEndpoints[Id](count)(x => x: Id[String]) + def genEndpointsId(count: Int): List[ServerEndpoint[Any, Identity]] = genServerEndpoints[Identity](count)(x => x: Identity[String]) } diff --git a/server/nima-server/src/main/scala/sttp/tapir/server/nima/NimaServerInterpreter.scala b/server/nima-server/src/main/scala/sttp/tapir/server/nima/NimaServerInterpreter.scala index 9a28839b32..ceb26fe874 100644 --- a/server/nima-server/src/main/scala/sttp/tapir/server/nima/NimaServerInterpreter.scala +++ b/server/nima-server/src/main/scala/sttp/tapir/server/nima/NimaServerInterpreter.scala @@ -2,6 +2,7 @@ package sttp.tapir.server.nima import io.helidon.http.Status import io.helidon.webserver.http.{Handler, ServerRequest => HelidonServerRequest, ServerResponse => HelidonServerResponse} +import sttp.shared.Identity import sttp.tapir.capabilities.NoStreams import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.interceptor.RequestResult @@ -14,16 +15,16 @@ import java.io.InputStream trait NimaServerInterpreter { def nimaServerOptions: NimaServerOptions - def toHandler(ses: List[ServerEndpoint[Any, Id]]): Handler = { - val filteredEndpoints = FilterServerEndpoints[Any, Id](ses) + def toHandler(ses: List[ServerEndpoint[Any, Identity]]): Handler = { + val filteredEndpoints = FilterServerEndpoints[Any, Identity](ses) val requestBody = new NimaRequestBody(nimaServerOptions.createFile) val responseBody = new NimaToResponseBody val interceptors = RejectInterceptor.disableWhenSingleEndpoint(nimaServerOptions.interceptors, ses) (helidonRequest: HelidonServerRequest, helidonResponse: HelidonServerResponse) => { - implicit val bodyListener: BodyListener[Id, InputStream] = new NimaBodyListener(helidonResponse) + implicit val bodyListener: BodyListener[Identity, InputStream] = new NimaBodyListener(helidonResponse) - val serverInterpreter = new ServerInterpreter[Any, Id, InputStream, NoStreams]( + val serverInterpreter = new ServerInterpreter[Any, Identity, InputStream, NoStreams]( filteredEndpoints, requestBody, responseBody, diff --git a/server/nima-server/src/main/scala/sttp/tapir/server/nima/NimaServerOptions.scala b/server/nima-server/src/main/scala/sttp/tapir/server/nima/NimaServerOptions.scala index e325920906..65f1851ca6 100644 --- a/server/nima-server/src/main/scala/sttp/tapir/server/nima/NimaServerOptions.scala +++ b/server/nima-server/src/main/scala/sttp/tapir/server/nima/NimaServerOptions.scala @@ -1,35 +1,36 @@ package sttp.tapir.server.nima import org.slf4j.LoggerFactory +import sttp.shared.Identity import sttp.tapir.{Defaults, TapirFile} import sttp.tapir.model.ServerRequest import sttp.tapir.server.interceptor.log.{DefaultServerLog, ServerLog} import sttp.tapir.server.interceptor.{CustomiseInterceptors, Interceptor} case class NimaServerOptions( - interceptors: List[Interceptor[Id]], - createFile: ServerRequest => TapirFile, - deleteFile: TapirFile => Unit + interceptors: List[Interceptor[Identity]], + createFile: ServerRequest => TapirFile, + deleteFile: TapirFile => Unit ) { - def prependInterceptor(i: Interceptor[Id]): NimaServerOptions = copy(interceptors = i :: interceptors) - def appendInterceptor(i: Interceptor[Id]): NimaServerOptions = copy(interceptors = interceptors :+ i) + def prependInterceptor(i: Interceptor[Identity]): NimaServerOptions = copy(interceptors = i :: interceptors) + def appendInterceptor(i: Interceptor[Identity]): NimaServerOptions = copy(interceptors = interceptors :+ i) } object NimaServerOptions { val Default: NimaServerOptions = customiseInterceptors.options - private def default(interceptors: List[Interceptor[Id]]): NimaServerOptions = + private def default(interceptors: List[Interceptor[Identity]]): NimaServerOptions = NimaServerOptions(interceptors, _ => Defaults.createTempFile(), Defaults.deleteFile()) - def customiseInterceptors: CustomiseInterceptors[Id, NimaServerOptions] = + def customiseInterceptors: CustomiseInterceptors[Identity, NimaServerOptions] = CustomiseInterceptors( - createOptions = (ci: CustomiseInterceptors[Id, NimaServerOptions]) => default(ci.interceptors) + createOptions = (ci: CustomiseInterceptors[Identity, NimaServerOptions]) => default(ci.interceptors) ).serverLog(defaultServerLog) private val log = LoggerFactory.getLogger(classOf[NimaServerInterpreter].getName) - lazy val defaultServerLog: ServerLog[Id] = - DefaultServerLog[Id]( + lazy val defaultServerLog: ServerLog[Identity] = + DefaultServerLog[Identity]( doLogWhenReceived = debugLog(_, None), doLogWhenHandled = debugLog, doLogAllDecodeFailures = debugLog, diff --git a/server/nima-server/src/main/scala/sttp/tapir/server/nima/internal/NimaBodyListener.scala b/server/nima-server/src/main/scala/sttp/tapir/server/nima/internal/NimaBodyListener.scala index bc7970ae9f..479f2e8737 100644 --- a/server/nima-server/src/main/scala/sttp/tapir/server/nima/internal/NimaBodyListener.scala +++ b/server/nima-server/src/main/scala/sttp/tapir/server/nima/internal/NimaBodyListener.scala @@ -1,13 +1,13 @@ package sttp.tapir.server.nima.internal import io.helidon.webserver.http.{ServerResponse => JavaNimaServerResponse} +import sttp.shared.Identity import sttp.tapir.server.interpreter.BodyListener -import sttp.tapir.server.nima.Id import java.io.InputStream import scala.util.{Success, Try} -private[nima] class NimaBodyListener(res: JavaNimaServerResponse) extends BodyListener[Id, InputStream] { +private[nima] class NimaBodyListener(res: JavaNimaServerResponse) extends BodyListener[Identity, InputStream] { override def onComplete(body: InputStream)(cb: Try[Unit] => Unit): InputStream = { res.whenSent(() => cb(Success(()))) body diff --git a/server/nima-server/src/main/scala/sttp/tapir/server/nima/internal/NimaRequestBody.scala b/server/nima-server/src/main/scala/sttp/tapir/server/nima/internal/NimaRequestBody.scala index a9d31f11c2..556b1a4834 100644 --- a/server/nima-server/src/main/scala/sttp/tapir/server/nima/internal/NimaRequestBody.scala +++ b/server/nima-server/src/main/scala/sttp/tapir/server/nima/internal/NimaRequestBody.scala @@ -2,16 +2,16 @@ package sttp.tapir.server.nima.internal import io.helidon.webserver.http.{ServerRequest => JavaNimaServerRequest} import sttp.capabilities +import sttp.shared.Identity import sttp.tapir.{FileRange, InputStreamRange, RawBodyType, TapirFile} import sttp.tapir.capabilities.NoStreams import sttp.tapir.model.ServerRequest import sttp.tapir.server.interpreter.{RawValue, RequestBody} -import sttp.tapir.server.nima.Id import java.nio.ByteBuffer import java.nio.file.{Files, StandardCopyOption} -private[nima] class NimaRequestBody(createFile: ServerRequest => TapirFile) extends RequestBody[Id, NoStreams] { +private[nima] class NimaRequestBody(createFile: ServerRequest => TapirFile) extends RequestBody[Identity, NoStreams] { override val streams: capabilities.Streams[NoStreams] = NoStreams override def toRaw[RAW](serverRequest: ServerRequest, bodyType: RawBodyType[RAW], maxBytes: Option[Long]): RawValue[RAW] = { diff --git a/server/nima-server/src/main/scala/sttp/tapir/server/nima/internal/package.scala b/server/nima-server/src/main/scala/sttp/tapir/server/nima/internal/package.scala index 31eea37cf9..47666a6110 100644 --- a/server/nima-server/src/main/scala/sttp/tapir/server/nima/internal/package.scala +++ b/server/nima-server/src/main/scala/sttp/tapir/server/nima/internal/package.scala @@ -1,17 +1,8 @@ package sttp.tapir.server.nima -import sttp.monad.MonadError +import sttp.monad.{IdentityMonad, MonadError} +import sttp.shared.Identity package object internal { - private[nima] implicit val idMonad: MonadError[Id] = new MonadError[Id] { - override def unit[T](t: T): Id[T] = t - override def map[T, T2](fa: Id[T])(f: T => T2): Id[T2] = f(fa) - override def flatMap[T, T2](fa: Id[T])(f: T => Id[T2]): Id[T2] = f(fa) - override def error[T](t: Throwable): Id[T] = throw t - override protected def handleWrappedError[T](rt: Id[T])(h: PartialFunction[Throwable, Id[T]]): Id[T] = rt - override def eval[T](t: => T): Id[T] = t - override def ensure[T](f: Id[T], e: => Id[Unit]): Id[T] = - try f - finally e - } + private[nima] implicit val idMonad: MonadError[Identity] = IdentityMonad } diff --git a/server/nima-server/src/main/scala/sttp/tapir/server/nima/nima.scala b/server/nima-server/src/main/scala/sttp/tapir/server/nima/nima.scala deleted file mode 100644 index 23124a1db5..0000000000 --- a/server/nima-server/src/main/scala/sttp/tapir/server/nima/nima.scala +++ /dev/null @@ -1,5 +0,0 @@ -package sttp.tapir.server - -package object nima { - type Id[X] = X -} diff --git a/server/nima-server/src/test/scala/sttp/tapir/server/nima/NimaTestServerInterpreter.scala b/server/nima-server/src/test/scala/sttp/tapir/server/nima/NimaTestServerInterpreter.scala index 64b3498d6c..5d66c68b19 100644 --- a/server/nima-server/src/test/scala/sttp/tapir/server/nima/NimaTestServerInterpreter.scala +++ b/server/nima-server/src/test/scala/sttp/tapir/server/nima/NimaTestServerInterpreter.scala @@ -4,6 +4,7 @@ import cats.data.NonEmptyList import cats.effect.{IO, Resource} import io.helidon.webserver.WebServer import io.helidon.webserver.http.{Handler, HttpRouting} +import sttp.shared.Identity import sttp.tapir.server.ServerEndpoint import sttp.tapir.server.tests.TestServerInterpreter import sttp.tapir.tests._ @@ -11,8 +12,8 @@ import sttp.tapir.tests._ import scala.concurrent.duration.FiniteDuration import java.time.Duration -class NimaTestServerInterpreter() extends TestServerInterpreter[Id, Any, NimaServerOptions, Handler] { - override def route(es: List[ServerEndpoint[Any, Id]], interceptors: Interceptors): Handler = { +class NimaTestServerInterpreter() extends TestServerInterpreter[Identity, Any, NimaServerOptions, Handler] { + override def route(es: List[ServerEndpoint[Any, Identity]], interceptors: Interceptors): Handler = { val serverOptions: NimaServerOptions = interceptors(NimaServerOptions.customiseInterceptors).options NimaServerInterpreter(serverOptions).toHandler(es) } diff --git a/server/sttp-stub-server/src/test/scala/sttp/tapir/server/stub/SttpStubServerTest.scala b/server/sttp-stub-server/src/test/scala/sttp/tapir/server/stub/SttpStubServerTest.scala index 30f4ae6c10..7af3788e27 100644 --- a/server/sttp-stub-server/src/test/scala/sttp/tapir/server/stub/SttpStubServerTest.scala +++ b/server/sttp-stub-server/src/test/scala/sttp/tapir/server/stub/SttpStubServerTest.scala @@ -5,10 +5,10 @@ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import sttp.capabilities.Streams import sttp.client3._ -import sttp.client3.monad._ import sttp.client3.testing.SttpBackendStub import sttp.model.{Header, MediaType, StatusCode} -import sttp.monad.MonadError +import sttp.monad.{IdentityMonad, MonadError} +import sttp.shared.Identity import sttp.tapir._ import sttp.tapir.client.sttp._ import sttp.tapir.generic.auto._ @@ -17,7 +17,7 @@ import sttp.tapir.json.circe._ class SttpStubServerTest extends AnyFlatSpec with Matchers { behavior of "SttpStubServer" - implicit val idMonad: MonadError[Identity] = IdMonad + implicit val idMonad: MonadError[Identity] = IdentityMonad it should "combine tapir endpoint with sttp stub" in { // given diff --git a/server/sttp-stub-server/src/test/scala/sttp/tapir/server/stub/TapirStubInterpreterTest.scala b/server/sttp-stub-server/src/test/scala/sttp/tapir/server/stub/TapirStubInterpreterTest.scala index 270fb57edd..3fbc0050fd 100644 --- a/server/sttp-stub-server/src/test/scala/sttp/tapir/server/stub/TapirStubInterpreterTest.scala +++ b/server/sttp-stub-server/src/test/scala/sttp/tapir/server/stub/TapirStubInterpreterTest.scala @@ -6,6 +6,7 @@ import sttp.client3._ import sttp.client3.monad.IdMonad import sttp.client3.testing.SttpBackendStub import sttp.model.StatusCode +import sttp.shared.Identity import sttp.tapir._ import sttp.tapir.client.sttp.SttpClientInterpreter import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler diff --git a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerSecurityTests.scala b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerSecurityTests.scala index 42a7939001..11aad3c58e 100644 --- a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerSecurityTests.scala +++ b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerSecurityTests.scala @@ -7,18 +7,11 @@ import sttp.model.Uri.QuerySegment import sttp.model.headers.WWWAuthenticateChallenge import sttp.model.{StatusCode, _} import sttp.monad.MonadError +import sttp.shared.Identity import sttp.tapir._ import sttp.tapir.model.UsernamePassword import sttp.tapir.server.interceptor.decodefailure.DefaultDecodeFailureHandler -import sttp.tapir.tests.Security.{ - in_security_apikey_header_in_amount_out_string, - in_security_apikey_header_out_string, - in_security_apikey_query_out_string, - in_security_basic_out_string, - in_security_bearer_out_string, - in_security_option_basic_option_bearer_out_string, - in_security_option_basic_out_string -} +import sttp.tapir.tests.Security.{in_security_apikey_header_in_amount_out_string, in_security_apikey_header_out_string, in_security_apikey_query_out_string, in_security_basic_out_string, in_security_bearer_out_string, in_security_option_basic_option_bearer_out_string, in_security_option_basic_out_string} import sttp.tapir.tests.Test class ServerSecurityTests[F[_], S, OPTIONS, ROUTE](createServerTest: CreateServerTest[F, S, OPTIONS, ROUTE])(implicit m: MonadError[F])