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

endpoints4s.fetch.Urls.scala Maven / Gradle / Ivy

package endpoints4s.fetch

import endpoints4s.PartialInvariantFunctor
import endpoints4s.Tupler
import endpoints4s.Validated
import endpoints4s.algebra
import endpoints4s.algebra.Documentation

import scala.collection.compat.Factory
import scala.scalajs.js

/** [[algebra.Urls]] interpreter that builds URLs (in a JavaScript runtime environment).
  *
  * @group interpreters
  */
trait Urls extends algebra.Urls {

  //#segment
  /** Defines how to build a path segment from an `A` */
  trait Segment[A] {

    /** @return An URL encoded path segment (e.g. "foo%2Fbar") */
    def encode(a: A): String
  }
  //#segment

  implicit lazy val segmentPartialInvariantFunctor: PartialInvariantFunctor[Segment] =
    new PartialInvariantFunctor[Segment] {
      def xmapPartial[A, B](
          fa: Segment[A],
          f: A => Validated[B],
          g: B => A
      ): Segment[B] = (b: B) => fa.encode(g(b))
    }

  implicit lazy val stringSegment: Segment[String] =
    (s: String) => js.URIUtils.encodeURIComponent(s)

  /** Defines how to build a query string from an `A`
    */
  trait QueryString[A] {

    /** @return A query string fragment (e.g. "foo=bar&baz=a%20b") */
    def encode(a: A): Option[String]
  }

  implicit lazy val queryStringPartialInvariantFunctor: PartialInvariantFunctor[QueryString] =
    new PartialInvariantFunctor[QueryString] {
      def xmapPartial[A, B](
          fa: QueryString[A],
          f: A => Validated[B],
          g: B => A
      ): QueryString[B] = (b: B) => fa.encode(g(b))
    }

  def combineQueryStrings[A, B](first: QueryString[A], second: QueryString[B])(implicit
      tupler: Tupler[A, B]
  ): QueryString[tupler.Out] =
    (ab: tupler.Out) => {
      val (a, b) = tupler.unapply(ab)
      (first.encode(a), second.encode(b)) match {
        case (Some(left), Some(right)) => Some(s"$left&$right")
        case (Some(left), None)        => Some(left)
        case (None, Some(right))       => Some(right)
        case (None, None)              => None
      }
    }

  def qs[A](name: String, docs: Documentation)(implicit
      param: QueryStringParam[A]
  ): QueryString[A] =
    a => {
      val params = param.encode(a)
      if (params.isEmpty) None
      else Some(params.map(v => s"$name=$v").mkString("&"))
    }

  type WithDefault[A] = Option[A]

  override def optQsWithDefault[A](name: String, default: A, docs: Documentation = None)(implicit
      value: QueryStringParam[A]
  ): QueryString[WithDefault[A]] =
    qs(name, docs)(optionalQueryStringParam(value))

  /** Defines how to build a query string parameter value from an `A` */
  trait QueryStringParam[A] {

    /** @return An URL encoded query string parameter list of values (e.g. "a%20b") */
    def encode(a: A): List[String]
  }

  implicit lazy val queryStringParamPartialInvariantFunctor
      : PartialInvariantFunctor[QueryStringParam] =
    new PartialInvariantFunctor[QueryStringParam] {
      def xmapPartial[A, B](
          fa: QueryStringParam[A],
          f: A => Validated[B],
          g: B => A
      ): QueryStringParam[B] =
        (b: B) => fa.encode(g(b))
    }

  implicit def optionalQueryStringParam[A](implicit
      param: QueryStringParam[A]
  ): QueryStringParam[Option[A]] = {
    case Some(a) => param.encode(a)
    case None    => Nil
  }

  implicit def repeatedQueryStringParam[A, CC[X] <: Iterable[X]](implicit
      param: QueryStringParam[A],
      factory: Factory[A, CC[A]]
  ): QueryStringParam[CC[A]] =
    as => as.iterator.flatMap(param.encode).toList

  implicit lazy val stringQueryString: QueryStringParam[String] =
    (s: String) => js.URIUtils.encodeURIComponent(s) :: Nil

  /** Builds an URL path from an `A` */
  trait Path[A] extends Url[A]

  implicit lazy val pathPartialInvariantFunctor: PartialInvariantFunctor[Path] =
    new PartialInvariantFunctor[Path] {
      def xmapPartial[A, B](
          fa: Path[A],
          f: A => Validated[B],
          g: B => A
      ): Path[B] = (b: B) => fa.encode(g(b))
    }

  def staticPathSegment(segment: String) = (_: Unit) => segment

  def segment[A](name: String, docs: Documentation)(implicit
      s: Segment[A]
  ): Path[A] = a => s.encode(a)

  def remainingSegments(name: String, docs: Documentation): Path[String] =
    s => s

  def chainPaths[A, B](first: Path[A], second: Path[B])(implicit
      tupler: Tupler[A, B]
  ): Path[tupler.Out] =
    (out: tupler.Out) => {
      val (a, b) = tupler.unapply(out)
      first.encode(a) ++ "/" ++ second.encode(b)
    }

  /** Builds an URL from an `A` */
  trait Url[A] {
    def encode(a: A): String
  }

  def urlWithQueryString[A, B](path: Path[A], qs: QueryString[B])(implicit
      tupler: Tupler[A, B]
  ): Url[tupler.Out] =
    (ab: tupler.Out) => {
      val (a, b) = tupler.unapply(ab)
      qs.encode(b) match {
        case Some(q) => s"${path.encode(a)}?$q"
        case None    => path.encode(a)
      }
    }

  implicit lazy val urlPartialInvariantFunctor: PartialInvariantFunctor[Url] =
    new PartialInvariantFunctor[Url] {
      def xmapPartial[A, B](
          fa: Url[A],
          f: A => Validated[B],
          g: B => A
      ): Url[B] = (b: B) => fa.encode(g(b))
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy