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

zio.schema.optics.ZioOpticsBuilder.scala Maven / Gradle / Ivy

The newest version!
package zio.schema.optics

import scala.collection.immutable.ListMap

import zio.optics._
import zio.prelude.NonEmptyMap
import zio.schema._
import zio.{ Chunk, ChunkBuilder }

/**
 * This is an example implementation demonstrating how to derive zio-optics using
 * a zio-schema AccessorBuilder.
 *
 * Because we go through DynamicValues, this implementation is not at all performant
 * so shouldn't be used where performance is potentially an issue.
 */
object ZioOpticsBuilder extends AccessorBuilder {

  type Lens[F, S, A]   = Optic[S, S, A, OpticFailure, OpticFailure, A, S]
  type Prism[F, S, A]  = ZPrism[S, S, A, A]
  type Traversal[S, A] = ZTraversal[S, S, A, A]

  override def makeLens[F, S, A](
    product: Schema.Record[S],
    term: Schema.Field[S, A]
  ): Optic[S, S, A, OpticFailure, OpticFailure, A, S] =
    Optic(
      getOptic = ZioOpticsBuilder.makeLensGet(product, term),
      setOptic = ZioOpticsBuilder.makeLensSet(product, term)
    )

  override def makePrism[F, S, A](
    sum: Schema.Enum[S],
    term: Schema.Case[S, A]
  ): ZPrism[S, S, A, A] =
    ZPrism(
      get = ZioOpticsBuilder.makePrismGet(term),
      set = (piece: A) => Right(piece.asInstanceOf[S])
    )

  override def makeTraversal[S, A](
    collection: Schema.Collection[S, A],
    element: Schema[A]
  ): Optic[S, S, Chunk[A], OpticFailure, OpticFailure, Chunk[A], S] =
    collection match {
      case seq @ Schema.Sequence(_, _, _, _, _) =>
        ZTraversal[S, S, A, A](
          ZioOpticsBuilder.makeSeqTraversalGet(seq.toChunk),
          ZioOpticsBuilder.makeSeqTraversalSet(seq)
        )
      case seq @ Schema.NonEmptySequence(_, _, _, _, _) =>
        ZTraversal[S, S, A, A](
          ZioOpticsBuilder.makeSeqTraversalGet(seq.toChunk),
          ZioOpticsBuilder.makeNonEmptySeqTraversalSet(seq)
        )
      case Schema.Map(_: Schema[k], _: Schema[v], _) =>
        ZTraversal(
          ZioOpticsBuilder.makeMapTraversalGet[k, v],
          ZioOpticsBuilder.makeMapTraversalSet[k, v]
        )
      case Schema.NonEmptyMap(_: Schema[k], _: Schema[v], _) =>
        ZTraversal(
          ZioOpticsBuilder.makeMapTraversalGet[k, v],
          ZioOpticsBuilder.makeNonEmptyMapTraversalSet[k, v]
        )
      case Schema.Set(_, _) =>
        ZTraversal(
          ZioOpticsBuilder.makeSetTraversalGet[A],
          ZioOpticsBuilder.makeSetTraversalSet[A]
        )
    }

  private[optics] def makeLensGet[S, A](
    product: Schema.Record[S],
    term: Schema.Field[S, A]
  ): S => Either[(OpticFailure, S), A] = { (whole: S) =>
    product.toDynamic(whole) match {
      case DynamicValue.Record(_, values) =>
        values
          .get(term.name)
          .map { dynamicField =>
            term.schema.fromDynamic(dynamicField) match {
              case Left(error)  => Left(OpticFailure(error) -> whole)
              case Right(value) => Right(value)
            }
          }
          .getOrElse(Left(OpticFailure(s"No term found with label ${term.name}") -> whole))
      case _ => Left(OpticFailure(s"Unexpected dynamic value for whole") -> whole)
    }
  }

  private[optics] def makeLensSet[S, A](
    product: Schema.Record[S],
    term: Schema.Field[S, A]
  ): A => S => Either[(OpticFailure, S), S] = { (piece: A) => (whole: S) =>
    product.toDynamic(whole) match {
      case DynamicValue.Record(name, values) =>
        val updated = spliceRecord(values, term.name, term.name -> term.schema.toDynamic(piece))
        product.fromDynamic(DynamicValue.Record(name, updated)) match {
          case Left(error)  => Left(OpticFailure(error) -> whole)
          case Right(value) => Right(value)
        }
      case _ => Left(OpticFailure(s"Unexpected dynamic value for whole") -> whole)
    }
  }

  private[optics] def makePrismGet[S, A](
    term: Schema.Case[S, A]
  ): S => Either[(OpticFailure, S), A] = { (whole: S) =>
    term.deconstructOption(whole) match {
      case Some(a) => Right(a)
      case None    => Left(OpticFailure(s"Cannot deconstruct to term ${term.id}") -> whole)
    }
  }

  private[optics] def makeSeqTraversalGet[S, A](
    toChunk: S => Chunk[A]
  ): S => Either[(OpticFailure, S), Chunk[A]] = { (whole: S) =>
    Right(toChunk(whole))
  }

  private[optics] def makeSeqTraversalSet[S, A](
    collection: Schema.Sequence[S, A, _]
  ): Chunk[A] => S => Either[(OpticFailure, S), S] = { (piece: Chunk[A]) => (whole: S) =>
    val builder       = ChunkBuilder.make[A]()
    val leftIterator  = collection.toChunk(whole).iterator
    val rightIterator = piece.iterator
    while (leftIterator.hasNext && rightIterator.hasNext) {
      val _ = leftIterator.next()
      builder += rightIterator.next()
    }
    while (leftIterator.hasNext) {
      builder += leftIterator.next()
    }
    Right(collection.fromChunk(builder.result()))
  }
  private[optics] def makeNonEmptySeqTraversalSet[S, A](
    collection: Schema.NonEmptySequence[S, A, _]
  ): Chunk[A] => S => Either[(OpticFailure, S), S] = { (piece: Chunk[A]) => (whole: S) =>
    val builder       = ChunkBuilder.make[A]()
    val leftIterator  = collection.toChunk(whole).iterator
    val rightIterator = piece.iterator
    while (leftIterator.hasNext && rightIterator.hasNext) {
      val _ = leftIterator.next()
      builder += rightIterator.next()
    }
    while (leftIterator.hasNext) {
      builder += leftIterator.next()
    }
    Right(collection.fromChunk(builder.result()))
  }

  private[optics] def makeMapTraversalGet[K, V](whole: Map[K, V]): Either[(OpticFailure, Map[K, V]), Chunk[(K, V)]] =
    Right(Chunk.fromIterable(whole))

  private[optics] def makeMapTraversalGet[K, V](
    whole: NonEmptyMap[K, V]
  ): Either[(OpticFailure, NonEmptyMap[K, V]), Chunk[(K, V)]] =
    Right(Chunk.fromIterable(whole.toMap))

  private[optics] def makeMapTraversalSet[K, V]
    : Chunk[(K, V)] => Map[K, V] => Either[(OpticFailure, Map[K, V]), Map[K, V]] = {
    (piece: Chunk[(K, V)]) => (whole: Map[K, V]) =>
      Right(whole ++ piece.toList)
  }
  private[optics] def makeNonEmptyMapTraversalSet[K, V]
    : Chunk[(K, V)] => NonEmptyMap[K, V] => Either[(OpticFailure, NonEmptyMap[K, V]), NonEmptyMap[K, V]] = {
    (piece: Chunk[(K, V)]) => (whole: NonEmptyMap[K, V]) =>
      Right(whole ++ piece.toList)
  }

  private[optics] def makeSetTraversalGet[A](whole: Set[A]): Either[(OpticFailure, Set[A]), Chunk[A]] =
    Right(Chunk.fromIterable(whole))

  private[optics] def makeSetTraversalSet[A]: Chunk[A] => Set[A] => Either[(OpticFailure, Set[A]), Set[A]] = {
    (piece: Chunk[A]) => (whole: Set[A]) =>
      Right(whole ++ piece.toSet)
  }

  private def spliceRecord(
    fields: ListMap[String, DynamicValue],
    label: String,
    splicedField: (String, DynamicValue)
  ): ListMap[String, DynamicValue] =
    fields.foldLeft[ListMap[String, DynamicValue]](ListMap.empty) {
      case (acc, (nextLabel, _)) if nextLabel == label => acc + splicedField
      case (acc, nextField)                            => acc + nextField
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy