All Downloads are FREE. Search and download functionalities are using the official Maven repository.

playground.DynamicServiceProxy.scala Maven / Gradle / Ivy

The newest version!
package playground

import cats.MonadThrow
import cats.syntax.all.*
import smithy4s.Document
import smithy4s.Endpoint
import smithy4s.Service
import smithy4s.kinds.*
import smithy4s.schema.Schema

class DynamicServiceProxy[Alg[_[_, _, _, _, _]], Op[_, _, _, _, _]](
  service: Service.Aux[Alg, Op]
) {

  def tryProxy[AlgStatic[_[_, _, _, _, _]], OpStatic[_, _, _, _, _], F[_]: MonadThrow](
    interp: FunctorAlgebra[AlgStatic, F]
  )(
    implicit serviceStatic: Service.Aux[AlgStatic, OpStatic]
  ): Option[FunctorInterpreter[Op, F]] =
    Option.when(service.id == serviceStatic.id)(proxy(interp)(serviceStatic))

  def proxy[AlgStatic[_[_, _, _, _, _]], OpStatic[_, _, _, _, _], F[_]: MonadThrow](
    interp: FunctorAlgebra[AlgStatic, F]
  )(
    serviceStatic: Service.Aux[AlgStatic, OpStatic]
  ): FunctorInterpreter[Op, F] = {
    val grp = serviceStatic.endpoints.groupBy(_.id).fmap(_.head)

    type Proxy[I, E, O, SE, EO] = I => F[O]

    def makeProxy[A, B](
      schemaIn: Schema[A],
      schemaOut: Schema[B],
    ): A => F[B] = {
      val inputEncoder = Document.Encoder.fromSchema(schemaIn)
      val outputDecoder = Document.Decoder.fromSchema(schemaOut)

      in => outputDecoder.decode(inputEncoder.encode(in)).liftTo[F]
    }

    val endpointMapping =
      new smithy4s.kinds.PolyFunction5[Endpoint[Op, *, *, *, *, *], Proxy] {
        private val trans = serviceStatic.toPolyFunction(interp)

        private def applyWithStatic[I, E, O, SI, SO, STI, STE, STO, STSI, STSO](
          endpoint: Endpoint[Op, I, E, O, SI, SO],
          endpointStatic: Endpoint[OpStatic, STI, STE, STO, STSI, STSO],
        ): I => F[O] = {
          val mapInput = makeProxy(endpoint.input, endpointStatic.input)
          val mapOutput = makeProxy(endpointStatic.output, endpoint.output)

          def errorMapper[A]: Throwable => F[A] =
            endpointStatic.error match {
              case None => _.raiseError[F, A]
              case Some(errorableStatic) =>
                val errorable = endpoint.error.get // should be there at this point
                val mapError = makeProxy(errorableStatic.schema, errorable.schema)

                e =>
                  errorableStatic.liftError(e) match {
                    case None => e.raiseError[F, A]
                    case Some(liftedStatic) =>
                      mapError(liftedStatic)
                        .flatMap(errorable.unliftError(_).raiseError[F, A])
                  }
            }

          input =>
            mapInput(input)
              .map(endpointStatic.wrap)
              .flatMap(trans(_))
              .handleErrorWith(errorMapper)
              .flatMap(mapOutput)
        }

        def apply[I, E, O, SI, SO](
          endpoint: Endpoint[Op, I, E, O, SI, SO]
        ): I => F[O] = applyWithStatic(endpoint, grp(endpoint.id))
      }

    service.functorInterpreter(endpointMapping)
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy