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

io.gatling.internal.quicklens.package.scala Maven / Gradle / Ivy

/*
 * Copyright 2011-2024 GatlingCorp (https://gatling.io)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// the original sources from https://github.com/softwaremill/quicklens are lacking file headers.
// we'll add them as soon as they are added upstream.
package io.gatling.internal

import scala.annotation.compileTimeOnly
import scala.collection.Factory
import scala.language.experimental.macros
import scala.language.higherKinds

package object quicklens extends LowPriorityImplicits {

  private[quicklens] def canOnlyBeUsedInsideModify(method: String) =
    s"$method can only be used inside modify"

  /**
   * Create an object allowing modifying the given (deeply nested) field accessible in a `case class` hierarchy via `path` on the given `obj`.
   *
   * All modifications are side-effect free and create copies of the original objects.
   *
   * You can use `.each` to traverse options, lists, etc.
   */
  def modify[T, U](obj: T)(path: T => U): PathModify[T, U] = macro QuicklensMacros.modify_impl[T, U]

  /**
   * Create an object allowing modifying the given (deeply nested) fields accessible in a `case class` hierarchy via `paths` on the given `obj`.
   *
   * All modifications are side-effect free and create copies of the original objects.
   *
   * You can use `.each` to traverse options, lists, etc.
   */
  def modifyAll[T, U](obj: T)(path1: T => U, paths: (T => U)*): PathModify[T, U] =
    macro QuicklensMacros.modifyAll_impl[T, U]

  implicit class ModifyPimp[T](t: T) {

    /**
     * Create an object allowing modifying the given (deeply nested) field accessible in a `case class` hierarchy via `path` on the given `obj`.
     *
     * All modifications are side-effect free and create copies of the original objects.
     *
     * You can use `.each` to traverse options, lists, etc.
     */
    def modify[U](path: T => U): PathModify[T, U] = macro QuicklensMacros.modifyPimp_impl[T, U]

    /**
     * Create an object allowing modifying the given (deeply nested) fields accessible in a `case class` hierarchy via `paths` on the given `obj`.
     *
     * All modifications are side-effect free and create copies of the original objects.
     *
     * You can use `.each` to traverse options, lists, etc.
     */
    def modifyAll[U](path1: T => U, paths: (T => U)*): PathModify[T, U] = macro QuicklensMacros.modifyAllPimp_impl[T, U]
  }

  final case class PathModify[T, U](obj: T, doModify: (T, U => U) => T) {

    /**
     * Transform the value of the field(s) using the given function.
     *
     * @return
     *   A copy of the root object with the (deeply nested) field(s) modified.
     */
    def using(mod: U => U): T = doModify(obj, mod)

    /**
     * An alias for [[using]]. Explicit calls to [[using]] are preferred over this alias, but quicklens provides this option because code auto-formatters (like
     * scalafmt) will generally not keep [[modify]]/[[using]] pairs on the same line, leading to code like
     * {{{
     * x
     *   .modify(_.foo)
     *   .using(newFoo :: _)
     *   .modify(_.bar)
     *   .using(_ + newBar)
     * }}}
     * When using [[apply]], scalafmt will allow
     * {{{
     * x
     *   .modify(_.foo)(newFoo :: _)
     *   .modify(_.bar)(_ + newBar)
     * }}}
     */
    final def apply(mod: U => U): T = using(mod)

    /**
     * Transform the value of the field(s) using the given function, if the condition is true. Otherwise, returns the original object unchanged.
     *
     * @return
     *   A copy of the root object with the (deeply nested) field(s) modified, if `condition` is true.
     */
    def usingIf(condition: Boolean)(mod: U => U): T = if (condition) doModify(obj, mod) else obj

    /**
     * Set the value of the field(s) to a new value.
     *
     * @return
     *   A copy of the root object with the (deeply nested) field(s) set to the new value.
     */
    def setTo(v: U): T = doModify(obj, _ => v)

    /**
     * Set the value of the field(s) to a new value, if it is defined. Otherwise, returns the original object unchanged.
     *
     * @return
     *   A copy of the root object with the (deeply nested) field(s) set to the new value, if it is defined.
     */
    def setToIfDefined(v: Option[U]): T = v.fold(obj)(setTo)

    /**
     * Set the value of the field(s) to a new value, if the condition is true. Otherwise, returns the original object unchanged.
     *
     * @return
     *   A copy of the root object with the (deeply nested) field(s) set to the new value, if `condition` is true.
     */
    def setToIf(condition: Boolean)(v: => U): T = if (condition) setTo(v) else obj
  }

  implicit class AbstractPathModifyPimp[T, U](f1: T => PathModify[T, U]) {
    def andThenModify[V](f2: U => PathModify[U, V]): T => PathModify[T, V] = { t: T =>
      PathModify[T, V](t, (t, vv) => f1(t).doModify(t, u => f2(u).doModify(u, vv)))
    }
  }

  def modifyLens[T]: LensHelper[T] = LensHelper[T]()

  def modifyAllLens[T]: MultiLensHelper[T] = MultiLensHelper[T]()

  final case class LensHelper[T] private () {

    def apply[U](path: T => U): PathLazyModify[T, U] = macro QuicklensMacros.modifyLazy_impl[T, U]
  }

  final case class MultiLensHelper[T] private () {

    def apply[U](path1: T => U, paths: (T => U)*): PathLazyModify[T, U] = macro QuicklensMacros.modifyLazyAll_impl[T, U]
  }

  final case class PathLazyModify[T, U](doModify: (T, U => U) => T) {

    self =>

    /**
     * see [[PathModify.using]]
     */
    def using(mod: U => U): T => T = obj => doModify(obj, mod)

    /**
     * see [[PathModify.usingIf]]
     */
    def usingIf(condition: Boolean)(mod: U => U): T => T =
      obj =>
        if (condition) doModify(obj, mod)
        else obj

    /**
     * see [[PathModify.setTo]]
     */
    def setTo(v: U): T => T = obj => doModify(obj, _ => v)

    /**
     * see [[PathModify.setToIfDefined]]
     */
    def setToIfDefined(v: Option[U]): T => T = v.fold((obj: T) => obj)(setTo)

    /**
     * see [[PathModify.setToIf]]
     */
    def setToIf(condition: Boolean)(v: => U): T => T =
      if (condition) setTo(v)
      else obj => obj

    /**
     * see [[AbstractPathModifyPimp]]
     */
    def andThenModify[V](f2: PathLazyModify[U, V]): PathLazyModify[T, V] =
      PathLazyModify[T, V]((t, vv) => self.doModify(t, u => f2.doModify(u, vv)))
  }

  trait QuicklensFunctor[F[_], A] {
    def map(fa: F[A])(f: A => A): F[A]
    def each(fa: F[A])(f: A => A): F[A] = map(fa)(f)
    def eachWhere(fa: F[A], p: A => Boolean)(f: A => A): F[A] = map(fa) { a =>
      if (p(a)) f(a) else a
    }
  }

  implicit def optionQuicklensFunctor[A]: QuicklensFunctor[Option, A] with QuicklensSingleAtFunctor[Option, A] =
    new QuicklensFunctor[Option, A] with QuicklensSingleAtFunctor[Option, A] {
      override def map(fa: Option[A])(f: A => A): Option[A] = fa.map(f)
      override def at(fa: Option[A])(f: A => A): Option[A] = Some(fa.map(f).get)
      override def atOrElse(fa: Option[A], default: => A)(f: A => A): Option[A] = fa.orElse(Some(default)).map(f)
      override def index(fa: Option[A])(f: A => A): Option[A] = fa.map(f)
    }

  // Currently only used for [[Option]], but could be used for [[Right]]-biased [[Either]]s.
  trait QuicklensSingleAtFunctor[F[_], T] {
    def at(fa: F[T])(f: T => T): F[T]
    def atOrElse(fa: F[T], default: => T)(f: T => T): F[T]
    def index(fa: F[T])(f: T => T): F[T]
  }

  implicit class QuicklensSingleAt[F[_], T](t: F[T])(implicit f: QuicklensSingleAtFunctor[F, T]) {
    @compileTimeOnly(canOnlyBeUsedInsideModify("at"))
    def at: T = sys.error("")

    @compileTimeOnly(canOnlyBeUsedInsideModify("at"))
    def atOrElse(default: => T): T = sys.error("")

    @compileTimeOnly(canOnlyBeUsedInsideModify("index"))
    def index: T = sys.error("")
  }

  implicit def traversableQuicklensFunctor[F[_], A](implicit
      fac: Factory[A, F[A]],
      ev: F[A] => Iterable[A]
  ): QuicklensFunctor[F, A] =
    new QuicklensFunctor[F, A] {
      override def map(fa: F[A])(f: A => A) = ev(fa).map(f).to(fac)
    }

  implicit class QuicklensAt[F[_], T](t: F[T])(implicit f: QuicklensAtFunctor[F, T]) {
    @compileTimeOnly(canOnlyBeUsedInsideModify("at"))
    def at(idx: Int): T = sys.error("")
    @compileTimeOnly(canOnlyBeUsedInsideModify("index"))
    def index(idx: Int): T = sys.error("")
  }

  trait QuicklensAtFunctor[F[_], T] {
    def at(fa: F[T], idx: Int)(f: T => T): F[T]
    def index(fa: F[T], idx: Int)(f: T => T): F[T]
  }

  implicit class QuicklensMapAt[M[KT, TT], K, T](t: M[K, T])(implicit
      f: QuicklensMapAtFunctor[M, K, T]
  ) {
    @compileTimeOnly(canOnlyBeUsedInsideModify("at"))
    def at(idx: K): T = sys.error("")

    @compileTimeOnly(canOnlyBeUsedInsideModify("atOrElse"))
    def atOrElse(idx: K, default: => T): T = sys.error("")

    @compileTimeOnly(canOnlyBeUsedInsideModify("index"))
    def index(idx: K): T = sys.error("")

    @compileTimeOnly(canOnlyBeUsedInsideModify("each"))
    def each: T = sys.error("")
  }

  trait QuicklensMapAtFunctor[F[_, _], K, T] {
    def at(fa: F[K, T], idx: K)(f: T => T): F[K, T]
    def atOrElse(fa: F[K, T], idx: K, default: => T)(f: T => T): F[K, T]
    def index(fa: F[K, T], idx: K)(f: T => T): F[K, T]
    def each(fa: F[K, T])(f: T => T): F[K, T]
  }

  implicit def mapQuicklensFunctor[M[KT, TT] <: Map[KT, TT], K, T](implicit
      fac: Factory[(K, T), M[K, T]]
  ): QuicklensMapAtFunctor[M, K, T] = new QuicklensMapAtFunctor[M, K, T] {
    override def at(fa: M[K, T], key: K)(f: T => T): M[K, T] =
      fa.updated(key, f(fa(key))).to(fac)
    override def atOrElse(fa: M[K, T], key: K, default: => T)(f: T => T): M[K, T] =
      fa.updated(key, f(fa.getOrElse(key, default))).to(fac)
    override def index(fa: M[K, T], key: K)(f: T => T): M[K, T] =
      fa.get(key).map(f).fold(fa)(t => fa.updated(key, t).to(fac))
    override def each(fa: M[K, T])(f: (T) => T): M[K, T] =
      fa.view.mapValues(f).to(fac)
  }

  @SuppressWarnings(Array("org.wartremover.warts.SeqApply", "org.wartremover.warts.SeqUpdated"))
  implicit def seqQuicklensAtFunctor[F[_], T](implicit
      fac: Factory[T, F[T]],
      ev: F[T] => Seq[T]
  ): QuicklensAtFunctor[F, T] =
    new QuicklensAtFunctor[F, T] {
      override def at(fa: F[T], idx: Int)(f: T => T): F[T] =
        fa.updated(idx, f(fa(idx))).to(fac)
      override def index(fa: F[T], idx: Int)(f: T => T): F[T] =
        if (idx < fa.size) fa.updated(idx, f(fa(idx))).to(fac) else fa
    }

  implicit class QuicklensWhen[A](value: A) {
    @compileTimeOnly(canOnlyBeUsedInsideModify("when"))
    def when[B <: A]: B = sys.error("")
  }

  implicit class QuicklensEither[T[_, _], L, R](e: T[L, R])(implicit f: QuicklensEitherFunctor[T, L, R]) {
    @compileTimeOnly(canOnlyBeUsedInsideModify("eachLeft"))
    def eachLeft: L = sys.error("")

    @compileTimeOnly(canOnlyBeUsedInsideModify("eachRight"))
    def eachRight: R = sys.error("")
  }

  trait QuicklensEitherFunctor[T[_, _], L, R] {
    def eachLeft(e: T[L, R])(f: L => L): T[L, R]
    def eachRight(e: T[L, R])(f: R => R): T[L, R]
  }

  implicit def eitherQuicklensFunctor[T[_, _], L, R]: QuicklensEitherFunctor[Either, L, R] =
    new QuicklensEitherFunctor[Either, L, R] {
      override def eachLeft(e: Either[L, R])(f: (L) => L) = e.left.map(f)
      override def eachRight(e: Either[L, R])(f: (R) => R) = e.map(f)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy