diff --git a/smithy4play/src/main/scala/de/innfactory/smithy4play/AutoRoutableController.scala b/smithy4play/src/main/scala/de/innfactory/smithy4play/AutoRoutableController.scala index fdf6f394..4d8042a5 100644 --- a/smithy4play/src/main/scala/de/innfactory/smithy4play/AutoRoutableController.scala +++ b/smithy4play/src/main/scala/de/innfactory/smithy4play/AutoRoutableController.scala @@ -1,5 +1,6 @@ package de.innfactory.smithy4play +import com.github.plokhotnyuk.jsoniter_scala.core.ReaderConfig import de.innfactory.smithy4play.middleware.MiddlewareBase import play.api.mvc.ControllerComponents import play.api.routing.Router.Routes @@ -17,9 +18,9 @@ trait AutoRoutableController { service: smithy4s.Service[Alg], ec: ExecutionContext, cc: ControllerComponents - ): Seq[MiddlewareBase] => Routes = (middlewares: Seq[MiddlewareBase]) => - new SmithyPlayRouter[Alg, F](impl, service).routes(middlewares) + ): (Seq[MiddlewareBase], ReaderConfig) => Routes = (middlewares: Seq[MiddlewareBase], readerConfig: ReaderConfig) => + new SmithyPlayRouter[Alg, F](impl, service).routes(middlewares, readerConfig) - val router: Seq[MiddlewareBase] => Routes + val router: (Seq[MiddlewareBase], ReaderConfig) => Routes } diff --git a/smithy4play/src/main/scala/de/innfactory/smithy4play/AutoRouter.scala b/smithy4play/src/main/scala/de/innfactory/smithy4play/AutoRouter.scala index 1ef8c6c2..198a7264 100644 --- a/smithy4play/src/main/scala/de/innfactory/smithy4play/AutoRouter.scala +++ b/smithy4play/src/main/scala/de/innfactory/smithy4play/AutoRouter.scala @@ -1,5 +1,6 @@ package de.innfactory.smithy4play +import com.github.plokhotnyuk.jsoniter_scala.core.ReaderConfig import com.typesafe.config.Config import de.innfactory.smithy4play.middleware.{ MiddlewareBase, MiddlewareRegistryBase, ValidateAuthMiddleware } import io.github.classgraph.{ ClassGraph, ScanResult } @@ -22,7 +23,8 @@ class AutoRouter @Inject( config: Config ) extends BaseRouter { - private val pkg = config.getString("smithy4play.autoRoutePackage") + private val pkg = config.getString("smithy4play.autoRoutePackage") + private val readerConfig = ReaderConfig.fromApplicationConfig(config) override val controllers: Seq[Routes] = { val classGraphScanner: ScanResult = new ClassGraph().enableAllInfo().acceptPackages(pkg).scan() @@ -39,7 +41,7 @@ class AutoRouter @Inject( private def createFromClass(clazz: Class[_], middlewares: Seq[MiddlewareBase]): Routes = app.injector.instanceOf(clazz) match { - case c: AutoRoutableController => c.router(middlewares) + case c: AutoRoutableController => c.router(middlewares, readerConfig) } } diff --git a/smithy4play/src/main/scala/de/innfactory/smithy4play/AutoRoutingMacro.scala b/smithy4play/src/main/scala/de/innfactory/smithy4play/AutoRoutingMacro.scala index 541bf294..b27b6c1f 100644 --- a/smithy4play/src/main/scala/de/innfactory/smithy4play/AutoRoutingMacro.scala +++ b/smithy4play/src/main/scala/de/innfactory/smithy4play/AutoRoutingMacro.scala @@ -14,7 +14,9 @@ object AutoRoutingMacro { with ..$parentss with de.innfactory.smithy4play.AutoRoutableController { $self => - override val router: Seq[de.innfactory.smithy4play.middleware.MiddlewareBase] => play.api.routing.Router.Routes = this + override val router: + (Seq[de.innfactory.smithy4play.middleware.MiddlewareBase], com.github.plokhotnyuk.jsoniter_scala.core.ReaderConfig) + => play.api.routing.Router.Routes = this ..$body } """ case _ => diff --git a/smithy4play/src/main/scala/de/innfactory/smithy4play/CodecDecider.scala b/smithy4play/src/main/scala/de/innfactory/smithy4play/CodecDecider.scala index f6b9c481..3c769e65 100644 --- a/smithy4play/src/main/scala/de/innfactory/smithy4play/CodecDecider.scala +++ b/smithy4play/src/main/scala/de/innfactory/smithy4play/CodecDecider.scala @@ -1,5 +1,6 @@ package de.innfactory.smithy4play +import com.github.plokhotnyuk.jsoniter_scala.core.ReaderConfig import play.api.http.MimeTypes import smithy4s.capability.instances.either._ import smithy4s.codecs.Writer.CachedCompiler @@ -11,12 +12,13 @@ import smithy4s.schema.CachedSchemaCompiler import smithy4s.xml.Xml import smithy4s.{ codecs, Blob } -object CodecDecider { +case class CodecDecider(readerConfig: ReaderConfig) { private val jsonCodecs = Json.payloadCodecs .withJsoniterCodecCompiler( Json.jsoniter ) + .withJsoniterReaderConfig(readerConfig) private val jsonEncoder: BlobEncoder.Compiler = jsonCodecs.encoders private val jsonDecoder: BlobDecoder.Compiler = jsonCodecs.decoders diff --git a/smithy4play/src/main/scala/de/innfactory/smithy4play/SmithyPlayEndpoint.scala b/smithy4play/src/main/scala/de/innfactory/smithy4play/SmithyPlayEndpoint.scala index 9ca51aeb..64b682f5 100644 --- a/smithy4play/src/main/scala/de/innfactory/smithy4play/SmithyPlayEndpoint.scala +++ b/smithy4play/src/main/scala/de/innfactory/smithy4play/SmithyPlayEndpoint.scala @@ -1,11 +1,8 @@ package de.innfactory.smithy4play -import alloy.SimpleRestJson -import aws.protocols.RestXml import cats.data.{ EitherT, Kleisli } import de.innfactory.smithy4play import de.innfactory.smithy4play.middleware.MiddlewareBase -import play.api.http.MimeTypes import play.api.mvc._ import smithy4s.codecs.PayloadError import smithy4s.http._ @@ -13,19 +10,15 @@ import smithy4s.kinds.FunctorInterpreter import smithy4s.schema.Schema import smithy4s.{ Blob, Endpoint, Service } +import javax.inject.Inject import scala.concurrent.{ ExecutionContext, Future } -class SmithyPlayEndpoint[Alg[_[_, _, _, _, _]], F[_] <: ContextRoute[_], Op[ - _, - _, - _, - _, - _ -], I, E, O, SI, SO]( +class SmithyPlayEndpoint[Alg[_[_, _, _, _, _]], F[_] <: ContextRoute[_], Op[_, _, _, _, _], I, E, O, SI, SO]( service: Service[Alg], impl: FunctorInterpreter[Op, F], middleware: Seq[MiddlewareBase], - endpoint: Endpoint[Op, I, E, O, SI, SO] + endpoint: Endpoint[Op, I, E, O, SI, SO], + codecDecider: CodecDecider )(implicit cc: ControllerComponents, ec: ExecutionContext) extends AbstractController(cc) { @@ -71,7 +64,7 @@ class SmithyPlayEndpoint[Alg[_[_, _, _, _, _]], F[_] <: ContextRoute[_], Op[ private def mapToEndpointResult( statusCode: Int )(output: O)(implicit defaultContentType: ContentType): HttpResponse[Blob] = - CodecDecider + codecDecider .httpMessageEncoder(Seq(defaultContentType.value)) .fromSchema(outputSchema) .write( @@ -106,7 +99,7 @@ class SmithyPlayEndpoint[Alg[_[_, _, _, _, _]], F[_] <: ContextRoute[_], Op[ )(implicit defaultContentType: ContentType): EitherT[Future, ContextRouteError, I] = EitherT { Future { - val codec = CodecDecider.requestDecoder(Seq(defaultContentType.value)) + val codec = codecDecider.requestDecoder(Seq(defaultContentType.value)) codec .fromSchema(inputSchema) .decode({ diff --git a/smithy4play/src/main/scala/de/innfactory/smithy4play/SmithyPlayRouter.scala b/smithy4play/src/main/scala/de/innfactory/smithy4play/SmithyPlayRouter.scala index d4a0b588..268c8432 100644 --- a/smithy4play/src/main/scala/de/innfactory/smithy4play/SmithyPlayRouter.scala +++ b/smithy4play/src/main/scala/de/innfactory/smithy4play/SmithyPlayRouter.scala @@ -1,6 +1,8 @@ package de.innfactory.smithy4play import cats.implicits.toTraverseOps +import com.github.plokhotnyuk.jsoniter_scala.core.ReaderConfig +import com.typesafe.config.Config import de.innfactory.smithy4play.middleware.MiddlewareBase import play.api.mvc.{ AbstractController, ControllerComponents, Handler, RequestHeader } import play.api.routing.Router.Routes @@ -21,12 +23,13 @@ class SmithyPlayRouter[Alg[_[_, _, _, _, _]], F[ )(implicit cc: ControllerComponents, ec: ExecutionContext) extends AbstractController(cc) { - def routes(middlewares: Seq[MiddlewareBase]): Routes = { + def routes(middlewares: Seq[MiddlewareBase], readerConfig: ReaderConfig): Routes = { val interpreter: PolyFunction5[service.Operation, Kind1[F]#toKind5] = service.toPolyFunction[Kind1[F]#toKind5](impl) val endpoints: Seq[service.Endpoint[_, _, _, _, _]] = service.endpoints val httpEndpoints: Seq[Either[HttpEndpoint.HttpEndpointError, HttpEndpoint[_]]] = endpoints.map(ep => HttpEndpoint.cast(ep.schema)) + val codecDecider = CodecDecider(readerConfig) new PartialFunction[RequestHeader, Handler] { override def isDefinedAt(x: RequestHeader): Boolean = { @@ -48,7 +51,8 @@ class SmithyPlayRouter[Alg[_[_, _, _, _, _]], F[ service, interpreter, middlewares, - endpointAndHttpEndpoint._1 + endpointAndHttpEndpoint._1, + codecDecider ).handler(v1) } match { case Right(value) => value diff --git a/smithy4play/src/main/scala/de/innfactory/smithy4play/client/GenericAPIClient.scala b/smithy4play/src/main/scala/de/innfactory/smithy4play/client/GenericAPIClient.scala index ffd12415..29ff88e0 100644 --- a/smithy4play/src/main/scala/de/innfactory/smithy4play/client/GenericAPIClient.scala +++ b/smithy4play/src/main/scala/de/innfactory/smithy4play/client/GenericAPIClient.scala @@ -1,7 +1,8 @@ package de.innfactory.smithy4play.client import cats.data.Kleisli -import de.innfactory.smithy4play.{ ClientResponse, RunnableClientRequest } +import com.github.plokhotnyuk.jsoniter_scala.core.ReaderConfig +import de.innfactory.smithy4play.{ ClientResponse, CodecDecider, RunnableClientRequest } import smithy4s.Service import smithy4s.kinds.{ Kind1, PolyFunction5 } @@ -10,10 +11,12 @@ import scala.concurrent.ExecutionContext private class GenericAPIClient[Alg[_[_, _, _, _, _]]]( service: Service[Alg], client: RequestClient, + readerConfig: ReaderConfig, additionalSuccessCodes: List[Int] = List.empty )(implicit ec: ExecutionContext) { - private val smithyPlayClient = new SmithyPlayClient("/", service, client, additionalSuccessCodes) + private val smithyPlayClient = + new SmithyPlayClient("/", service, client, CodecDecider(readerConfig), additionalSuccessCodes) /* Takes a service and creates a Transformation[Op, ClientRequest] */ private def transformer(): Alg[Kind1[RunnableClientRequest]#toKind5] = @@ -53,31 +56,35 @@ object GenericAPIClient { def withClientAndHeaders( client: RequestClient, additionalHeaders: Option[Map[String, Seq[String]]], - additionalSuccessCodes: List[Int] = List.empty + additionalSuccessCodes: List[Int] = List.empty, + readerConfig: ReaderConfig = ReaderConfig )(implicit ec: ExecutionContext): Alg[Kind1[ClientResponse]#toKind5] = - apply(service, additionalHeaders, additionalSuccessCodes, client) + apply(service, additionalHeaders, additionalSuccessCodes, client, readerConfig) def withClient( client: RequestClient, - additionalSuccessCodes: List[Int] = List.empty + additionalSuccessCodes: List[Int] = List.empty, + readerConfig: ReaderConfig = ReaderConfig )(implicit ec: ExecutionContext): Alg[Kind1[RunnableClientRequest]#toKind5] = - apply(service, client, additionalSuccessCodes) + apply(service, client, additionalSuccessCodes, readerConfig) } def apply[Alg[_[_, _, _, _, _]]]( serviceI: Service[Alg], client: RequestClient, - additionalSuccessCodes: List[Int] + additionalSuccessCodes: List[Int], + readerConfig: ReaderConfig )(implicit ec: ExecutionContext): Alg[Kind1[RunnableClientRequest]#toKind5] = - new GenericAPIClient(serviceI, client, additionalSuccessCodes).transformer() + new GenericAPIClient(serviceI, client, readerConfig, additionalSuccessCodes).transformer() def apply[Alg[_[_, _, _, _, _]]]( serviceI: Service[Alg], additionalHeaders: Option[Map[String, Seq[String]]], additionalSuccessCodes: List[Int], - client: RequestClient + client: RequestClient, + readerConfig: ReaderConfig )(implicit ec: ExecutionContext): Alg[Kind1[ClientResponse]#toKind5] = - new GenericAPIClient(serviceI, client, additionalSuccessCodes).transformer(additionalHeaders) + new GenericAPIClient(serviceI, client, readerConfig, additionalSuccessCodes).transformer(additionalHeaders) } diff --git a/smithy4play/src/main/scala/de/innfactory/smithy4play/client/SmithyPlayClient.scala b/smithy4play/src/main/scala/de/innfactory/smithy4play/client/SmithyPlayClient.scala index 4cd2446f..2ccdba4d 100644 --- a/smithy4play/src/main/scala/de/innfactory/smithy4play/client/SmithyPlayClient.scala +++ b/smithy4play/src/main/scala/de/innfactory/smithy4play/client/SmithyPlayClient.scala @@ -1,7 +1,8 @@ package de.innfactory.smithy4play.client import cats.implicits.toBifunctorOps -import de.innfactory.smithy4play.ClientResponse +import com.github.plokhotnyuk.jsoniter_scala.core.ReaderConfig +import de.innfactory.smithy4play.{ ClientResponse, CodecDecider } import smithy4s.Blob import smithy4s.http.{ CaseInsensitive, HttpEndpoint } @@ -11,6 +12,7 @@ class SmithyPlayClient[Alg[_[_, _, _, _, _]], F[_]]( baseUri: String, val service: smithy4s.Service[Alg], client: RequestClient, + codecDecider: CodecDecider, additionalSuccessCodes: List[Int] = List.empty )(implicit executionContext: ExecutionContext) { @@ -31,7 +33,8 @@ class SmithyPlayClient[Alg[_[_, _, _, _, _]], F[_]]( httpEndpoint = httpEndpoint, input = service.input(op), serviceHints = service.hints, - client = client + client = client, + codecDecider = codecDecider ).send() ) .toOption diff --git a/smithy4play/src/main/scala/de/innfactory/smithy4play/client/SmithyPlayClientEndpoint.scala b/smithy4play/src/main/scala/de/innfactory/smithy4play/client/SmithyPlayClientEndpoint.scala index 855c1fc4..74c1d947 100644 --- a/smithy4play/src/main/scala/de/innfactory/smithy4play/client/SmithyPlayClientEndpoint.scala +++ b/smithy4play/src/main/scala/de/innfactory/smithy4play/client/SmithyPlayClientEndpoint.scala @@ -17,7 +17,8 @@ private[smithy4play] class SmithyPlayClientEndpoint[Op[_, _, _, _, _], I, E, O, additionalSuccessCodes: List[Int], httpEndpoint: HttpEndpoint[I], input: I, - client: RequestClient + client: RequestClient, + codecDecider: CodecDecider )(implicit executionContext: ExecutionContext) { private implicit val inputSchema: Schema[I] = endpoint.input @@ -53,7 +54,7 @@ private[smithy4play] class SmithyPlayClientEndpoint[Op[_, _, _, _, _], I, E, O, } private def writeInputToBlob(input: I, contentType: Seq[String]): EndpointRequest = { - val codecs = CodecDecider.requestEncoder(contentType) + val codecs = codecDecider.requestEncoder(contentType) codecs.fromSchema(inputSchema).write(PlayHttpRequest(Blob.empty, Metadata.empty), input) } @@ -72,7 +73,7 @@ private[smithy4play] class SmithyPlayClientEndpoint[Op[_, _, _, _, _], I, E, O, Future { val headers = response.headers.map(x => (x._1, x._2)) val contentType = headers.getOrElse(contentTypeKey, Seq(serviceContentType)) - val codec = CodecDecider.httpResponseDecoder(contentType) + val codec = codecDecider.httpResponseDecoder(contentType) codec .fromSchema(outputSchema) diff --git a/smithy4play/src/main/scala/de/innfactory/smithy4play/package.scala b/smithy4play/src/main/scala/de/innfactory/smithy4play/package.scala index 0ab98f10..c20831b5 100644 --- a/smithy4play/src/main/scala/de/innfactory/smithy4play/package.scala +++ b/smithy4play/src/main/scala/de/innfactory/smithy4play/package.scala @@ -3,6 +3,8 @@ package de.innfactory import alloy.SimpleRestJson import aws.protocols.RestXml import cats.data.{ EitherT, Kleisli } +import com.github.plokhotnyuk.jsoniter_scala.core.ReaderConfig +import com.typesafe.config.Config import de.innfactory.smithy4play.client.SmithyPlayClientEndpointErrorResponse import org.slf4j import play.api.Logger @@ -15,6 +17,7 @@ import smithy4s.http.{ CaseInsensitive, HttpEndpoint, HttpResponse, Metadata } import scala.annotation.{ compileTimeOnly, StaticAnnotation } import scala.concurrent.Future import scala.language.experimental.macros +import scala.util.Try import scala.util.matching.Regex import scala.xml.Elem @@ -65,6 +68,42 @@ package object smithy4play { } } + implicit class EnhancedReaderConfig(readerConfig: ReaderConfig) { + + def fromApplicationConfig(config: Config): ReaderConfig = { + val maxCharBufSize = + Try(config.getInt("smithy4play.jsoniter.maxCharBufSize")).toOption + val preferredBufSize = + Try(config.getInt("smithy4play.jsoniter.preferredBufSize")).toOption + val preferredCharBufSize = + Try(config.getInt("smithy4play.jsoniter.preferredCharBufSize")).toOption + val hexDumpSize = + Try(config.getInt("smithy4play.jsoniter.hexDumpSize")).toOption + val maxBufSize = + Try(config.getInt("smithy4play.jsoniter.MaxBufSize")).toOption + val throwReaderExceptionWithStackTrace = + Try(config.getBoolean("smithy4play.jsoniter.throwReaderExceptionWithStackTrace")).toOption + val appendHexDumpToParseException = + Try(config.getBoolean("smithy4play.jsoniter.appendHexDumpToParseException")).toOption + val checkForEndOfInput = + Try(config.getBoolean("smithy4play.jsoniter.checkForEndOfInput")).toOption + + readerConfig + .withMaxCharBufSize(maxCharBufSize.getOrElse(readerConfig.maxCharBufSize)) + .withPreferredBufSize(preferredBufSize.getOrElse(readerConfig.preferredBufSize)) + .withCheckForEndOfInput(checkForEndOfInput.getOrElse(readerConfig.checkForEndOfInput)) + .withPreferredCharBufSize(preferredCharBufSize.getOrElse(readerConfig.preferredCharBufSize)) + .withHexDumpSize(hexDumpSize.getOrElse(readerConfig.hexDumpSize)) + .withMaxBufSize(maxBufSize.getOrElse(readerConfig.maxBufSize)) + .withAppendHexDumpToParseException( + appendHexDumpToParseException.getOrElse(readerConfig.appendHexDumpToParseException) + ) + .withThrowReaderExceptionWithStackTrace( + throwReaderExceptionWithStackTrace.getOrElse(readerConfig.throwReaderExceptionWithStackTrace) + ) + } + } + implicit class EnhancedThrowable(throwable: Throwable) { private val regex1: Regex = """(?s), offset: (?:0x)?[0-9a-fA-F]+, buf:.*""".r private val regex2: Regex = """(.*), offset: .*, buf:.* (\(path:.*\))""".r @@ -77,7 +116,6 @@ package object smithy4play { case msg => regex1.replaceAllIn(msg, "") } ) - } private[smithy4play] case class Smithy4PlayError( diff --git a/smithy4playTest/test/XmlControllerTest.scala b/smithy4playTest/test/XmlControllerTest.scala index 57b86ed2..d023a5a6 100644 --- a/smithy4playTest/test/XmlControllerTest.scala +++ b/smithy4playTest/test/XmlControllerTest.scala @@ -1,20 +1,16 @@ -import de.innfactory.smithy4play.CodecDecider import de.innfactory.smithy4play.client.GenericAPIClient.EnhancedGenericAPIClient import de.innfactory.smithy4play.client.SmithyPlayTestUtils._ import models.NodeImplicits.NodeEnhancer import models.TestBase import play.api.Application import play.api.inject.guice.GuiceApplicationBuilder -import play.api.libs.json.{ JsValue, Json, OFormat } +import play.api.libs.json.{Json, OFormat} import play.api.test.FakeRequest import play.api.test.Helpers._ -import smithy4s.Blob import smithy4s.http.CaseInsensitive -import testDefinitions.test.{ XmlTestInputBody, XmlControllerDefGen, XmlTestOutput } +import testDefinitions.test.{XmlControllerDefGen, XmlTestInputBody, XmlTestOutput} -import scala.xml._ import scala.concurrent.ExecutionContext.Implicits.global -import scala.xml.{ Elem, Node, PrettyPrinter } class XmlControllerTest extends TestBase {