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

endpoints4s.algebra.server.ServerTestBase.scala Maven / Gradle / Ivy

The newest version!
package endpoints4s.algebra.server

import java.nio.charset.StandardCharsets

import org.apache.pekko.actor.ActorSystem
import org.apache.pekko.http.scaladsl.Http
import org.apache.pekko.http.scaladsl.model.headers.`Content-Type`
import org.apache.pekko.http.scaladsl.model.{HttpRequest, HttpResponse}
import org.apache.pekko.util.ByteString
import endpoints4s.algebra
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.{BeforeAndAfter, BeforeAndAfterAll}

import scala.concurrent.duration._
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec

import scala.concurrent.{ExecutionContext, Future}

trait ServerTestBase[T <: algebra.Endpoints]
    extends AnyWordSpec
    with Matchers
    with ScalaFutures
    with BeforeAndAfterAll
    with BeforeAndAfter {

  override implicit def patienceConfig: PatienceConfig =
    PatienceConfig(20.seconds, 20.millisecond)

  val serverApi: T

  /** @param url An URL definition (e.g., `path / "foo"`)
    * @param urlCandidate An URL candidate (e.g., "/foo", "/bar")
    * @return Whether the URL candidate matched the URL definition, or not, or if
    *         decoding failed.
    */
  def decodeUrl[A](url: serverApi.Url[A])(urlCandidate: String): DecodedUrl[A]

  /** @param runTests A function that is called after the server is started and before it is stopped. It takes
    *                 the TCP port number as parameter.
    */
  def serveEndpoint[Req, Resp](
      endpoint: serverApi.Endpoint[Req, Resp],
      response: => Resp
  )(runTests: Int => Unit): Unit

  trait EndpointWithImplementation {
    type Req
    type Resp
    def endpoint: serverApi.Endpoint[Req, Resp]
    def impl: Req => Resp
  }
  def EndpointWithImplementation[Req1, Resp1](
      e: serverApi.Endpoint[Req1, Resp1],
      i: Req1 => Resp1
  ): EndpointWithImplementation = new EndpointWithImplementation {
    type Req = Req1
    type Resp = Resp1
    def endpoint = e
    def impl = i
  }

  /** @param endpoints One or more endpoints to serve
    * @param runTests A function that is called after the server is started and before it is stopped. It takes
    *                 the TCP port number as parameter.
    */
  def serveManyEndpoints(
      endpoints: EndpointWithImplementation*
  )(runTests: Int => Unit): Unit

  /** @param runTests A function that is called after the server is started and before it is stopped. It takes
    *                 the TCP port number as parameter.
    */
  def serveIdentityEndpoint[Resp](
      endpoint: serverApi.Endpoint[Resp, Resp]
  )(runTests: Int => Unit): Unit

  private[server] implicit val actorSystem: ActorSystem = ActorSystem()
  implicit val executionContext: ExecutionContext = actorSystem.dispatcher

  val httpClient = Http()

  def sendAndDecodeEntityAsText(
      request: HttpRequest
  ): Future[(HttpResponse, String)] = {
    send(request).map { case (response, entity) =>
      (response, decodeEntityAsText(response, entity))
    }
  }

  def send(request: HttpRequest): Future[(HttpResponse, ByteString)] = {
    httpClient.singleRequest(request).flatMap { response =>
      response.entity.toStrict(patienceConfig.timeout).map { entity =>
        (response, entity.data)
      }
    }
  }

  def decodeEntityAsText(response: HttpResponse, entity: ByteString): String = {
    val charset =
      response
        .header[`Content-Type`]
        .flatMap(_.contentType.charsetOption.map(_.nioCharset()))
        .getOrElse(StandardCharsets.UTF_8)
    entity.decodeString(charset)
  }

}

/** @tparam A The result of decoding an URL candidate
  */
sealed trait DecodedUrl[+A] extends Serializable
object DecodedUrl {

  /** The URL candidate matched the given URL definition, and a `A` value was extracted from it */
  case class Matched[+A](value: A) extends DecodedUrl[A]

  /** The URL candidate didn’t match the given URL definition */
  case object NotMatched extends DecodedUrl[Nothing]

  /** The URL candidate matched the given URL definition, but the decoding process failed */
  case class Malformed(errors: Seq[String]) extends DecodedUrl[Nothing]
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy