diff --git a/build.sbt b/build.sbt index ea173403..3d906003 100644 --- a/build.sbt +++ b/build.sbt @@ -41,6 +41,7 @@ lazy val model = Settings.Libraries.CatsTestkit.value ++ Settings.Libraries.Circe.value ++ Settings.Libraries.DisciplineMUnit.value ++ + Settings.Libraries.Monocle.value ++ Settings.Libraries.MUnit.value ) diff --git a/model/src/main/scala/clue/data/InputOptics.scala b/model/src/main/scala/clue/data/InputOptics.scala new file mode 100644 index 00000000..56a1bcf9 --- /dev/null +++ b/model/src/main/scala/clue/data/InputOptics.scala @@ -0,0 +1,23 @@ +// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA) +// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause + +package clue.data + +import monocle.PPrism +import monocle.Prism + +trait InputOptics { + final def pAssign[A, B]: PPrism[Input[A], Input[B], A, B] = + PPrism[Input[A], Input[B], A, B](_.toOption.map(Right(_)).getOrElse(Left(Ignore)))(Assign.apply) + + final def assign[A]: Prism[Input[A], A] = + pAssign[A, A] + + final def ignore[A]: Prism[Input[A], Unit] = + Prism[Input[A], Unit] { case Ignore => Some(()); case _ => None }(_ => Ignore) + + final def unassign[A]: Prism[Input[A], Unit] = + Prism[Input[A], Unit] { case Unassign => Some(()); case _ => None }(_ => Unassign) +} + +object optics extends InputOptics diff --git a/model/src/main/scala/clue/data/syntax/package.scala b/model/src/main/scala/clue/data/syntax/package.scala index 23e0c0dc..6df3e483 100644 --- a/model/src/main/scala/clue/data/syntax/package.scala +++ b/model/src/main/scala/clue/data/syntax/package.scala @@ -3,6 +3,10 @@ package clue.data +import monocle.PLens +import monocle.POptional +import clue.data.optics + package object syntax { implicit final class AnyToInputOps[A](private val a: A) extends AnyVal { def assign: Input[A] = Assign(a) @@ -12,4 +16,17 @@ package object syntax { def orIgnore: Input[A] = Input.orIgnore(a) def orUnassign: Input[A] = Input.orUnassign(a) } + + implicit final class LensToLensOps[S, T, A, B](private val lens: PLens[S, T, A, B]) + extends AnyVal { + // Copied verbatim from monocle, which has it as a private member + private def adapt[A1, B1](implicit evA: A =:= A1, evB: B =:= B1): PLens[S, T, A1, B1] = + evB.substituteCo[PLens[S, T, A1, *]](evA.substituteCo[PLens[S, T, *, B]](lens)) + + def assign[A1, B1](implicit + ev1: A =:= Input[A1], + ev2: B =:= Input[B1] + ): POptional[S, T, A1, B1] = + adapt[Input[A1], Input[B1]].andThen(optics.pAssign[A1, B1]) + } }