From 8b080e3fab78f37502a898ed93bbafa01321b707 Mon Sep 17 00:00:00 2001 From: Ondra Pelech Date: Fri, 31 May 2024 12:31:37 +0200 Subject: [PATCH 1/3] Apply helper methods for implicits --- .../src/main/scala/org/virtuslab/yaml/YamlCodec.scala | 6 +++++- .../src/main/scala/org/virtuslab/yaml/YamlDecoder.scala | 9 ++++++++- .../src/main/scala/org/virtuslab/yaml/YamlEncoder.scala | 3 +++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/core/shared/src/main/scala/org/virtuslab/yaml/YamlCodec.scala b/core/shared/src/main/scala/org/virtuslab/yaml/YamlCodec.scala index 2c7a124fb..ae70eb086 100644 --- a/core/shared/src/main/scala/org/virtuslab/yaml/YamlCodec.scala +++ b/core/shared/src/main/scala/org/virtuslab/yaml/YamlCodec.scala @@ -5,4 +5,8 @@ package org.virtuslab.yaml */ trait YamlCodec[T] extends YamlDecoder[T] with YamlEncoder[T] -object YamlCodec extends YamlCodecCompanionCrossCompat +object YamlCodec extends YamlCodecCompanionCrossCompat { + + def apply[T](implicit self: YamlCodec[T]): YamlCodec[T] = self + +} diff --git a/core/shared/src/main/scala/org/virtuslab/yaml/YamlDecoder.scala b/core/shared/src/main/scala/org/virtuslab/yaml/YamlDecoder.scala index 0e02193f3..bd8847cb9 100644 --- a/core/shared/src/main/scala/org/virtuslab/yaml/YamlDecoder.scala +++ b/core/shared/src/main/scala/org/virtuslab/yaml/YamlDecoder.scala @@ -35,6 +35,13 @@ trait YamlDecoder[T] { self => } object YamlDecoder extends YamlDecoderCompanionCrossCompat { + + def apply[T](implicit self: YamlDecoder[T]): YamlDecoder[T] = self + + def from[T](pf: PartialFunction[Node, Either[ConstructError, T]])(implicit + classTag: ClassTag[T] + ): YamlDecoder[T] = apply[T](pf) + def apply[T]( pf: PartialFunction[Node, Either[ConstructError, T]] )(implicit classTag: ClassTag[T]): YamlDecoder[T] = @@ -147,7 +154,7 @@ object YamlDecoder extends YamlDecoderCompanionCrossCompat { } } - implicit def forOption[T](implicit c: YamlDecoder[T]): YamlDecoder[Option[T]] = YamlDecoder { + implicit def forOption[T](implicit c: YamlDecoder[T]): YamlDecoder[Option[T]] = YamlDecoder.from { node => if (node.tag == Tag.nullTag) Right(None) else c.construct(node).map(Option(_)) diff --git a/core/shared/src/main/scala/org/virtuslab/yaml/YamlEncoder.scala b/core/shared/src/main/scala/org/virtuslab/yaml/YamlEncoder.scala index b888a7663..eb9cc1872 100644 --- a/core/shared/src/main/scala/org/virtuslab/yaml/YamlEncoder.scala +++ b/core/shared/src/main/scala/org/virtuslab/yaml/YamlEncoder.scala @@ -8,6 +8,9 @@ trait YamlEncoder[T] { } object YamlEncoder extends YamlEncoderCrossCompanionCompat { + + def apply[T](implicit self: YamlEncoder[T]): YamlEncoder[T] = self + implicit def forByte: YamlEncoder[Byte] = v => Node.ScalarNode(v.toString) implicit def forChar: YamlEncoder[Char] = v => Node.ScalarNode(v.toString) implicit def forShort: YamlEncoder[Short] = v => Node.ScalarNode(v.toString) From e781b41e948ae655401427ec536e0a2dcca9dfc7 Mon Sep 17 00:00:00 2001 From: Ondra Pelech Date: Fri, 31 May 2024 13:07:13 +0200 Subject: [PATCH 2/3] YamlCodec helper methods --- .../scala/org/virtuslab/yaml/YamlCodec.scala | 17 ++++++++++++++++- .../scala/org/virtuslab/yaml/YamlDecoder.scala | 7 +++++++ .../scala/org/virtuslab/yaml/YamlEncoder.scala | 6 +++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/core/shared/src/main/scala/org/virtuslab/yaml/YamlCodec.scala b/core/shared/src/main/scala/org/virtuslab/yaml/YamlCodec.scala index ae70eb086..5afb4a13f 100644 --- a/core/shared/src/main/scala/org/virtuslab/yaml/YamlCodec.scala +++ b/core/shared/src/main/scala/org/virtuslab/yaml/YamlCodec.scala @@ -3,10 +3,25 @@ package org.virtuslab.yaml /** * A type class that provides both-way conversion between [[Node]] and [[T]] */ -trait YamlCodec[T] extends YamlDecoder[T] with YamlEncoder[T] +trait YamlCodec[T] extends YamlDecoder[T] with YamlEncoder[T] { self => + + def imap[T1](f: T => T1)(g: T1 => T): YamlCodec[T1] = + YamlCodec.from(self.map(f), self.contramap(g)) + + def iemap[T1](f: T => Either[ConstructError, T1])(g: T1 => T): YamlCodec[T1] = + YamlCodec.from(self.flatMap(f), self.contramap(g)) +} object YamlCodec extends YamlCodecCompanionCrossCompat { def apply[T](implicit self: YamlCodec[T]): YamlCodec[T] = self + def from[A](decoder: YamlDecoder[A], encoder: YamlEncoder[A]): YamlCodec[A] = new YamlCodec[A] { + + override def construct(node: Node)(implicit settings: LoadSettings): Either[ConstructError, A] = + decoder.construct(node) + + override def asNode(obj: A): Node = + encoder.asNode(obj) + } } diff --git a/core/shared/src/main/scala/org/virtuslab/yaml/YamlDecoder.scala b/core/shared/src/main/scala/org/virtuslab/yaml/YamlDecoder.scala index bd8847cb9..cee777e8d 100644 --- a/core/shared/src/main/scala/org/virtuslab/yaml/YamlDecoder.scala +++ b/core/shared/src/main/scala/org/virtuslab/yaml/YamlDecoder.scala @@ -32,6 +32,13 @@ trait YamlDecoder[T] { self => ): Either[ConstructError, T1] = self.construct(node).map(f) } + + final def flatMap[T1](f: T => Either[ConstructError, T1]): YamlDecoder[T1] = new YamlDecoder[T1] { + override def construct(node: Node)(implicit + settings: LoadSettings + ): Either[ConstructError, T1] = + self.construct(node).flatMap(f) + } } object YamlDecoder extends YamlDecoderCompanionCrossCompat { diff --git a/core/shared/src/main/scala/org/virtuslab/yaml/YamlEncoder.scala b/core/shared/src/main/scala/org/virtuslab/yaml/YamlEncoder.scala index eb9cc1872..57adf5660 100644 --- a/core/shared/src/main/scala/org/virtuslab/yaml/YamlEncoder.scala +++ b/core/shared/src/main/scala/org/virtuslab/yaml/YamlEncoder.scala @@ -3,8 +3,12 @@ package org.virtuslab.yaml /** * A type class that provides a conversion from a given type [[T]] into [[Node]] */ -trait YamlEncoder[T] { +trait YamlEncoder[T] { self => def asNode(obj: T): Node + + final def contramap[T1](f: T1 => T): YamlEncoder[T1] = new YamlEncoder[T1] { + override def asNode(obj: T1): Node = self.asNode(f(obj)) + } } object YamlEncoder extends YamlEncoderCrossCompanionCompat { From 6eab7df7aa7f870b13cdacdbbbfd92385af3ef94 Mon Sep 17 00:00:00 2001 From: Ondra Pelech Date: Fri, 31 May 2024 18:14:20 +0200 Subject: [PATCH 3/3] add tests --- .../virtuslab/yaml/YamlCodecCrossCompat.scala | 14 +++++------ .../scala/org/virtuslab/yaml/YamlCodec.scala | 23 +++++++++++-------- .../org/virtuslab/yaml/YamlEncoder.scala | 2 +- .../virtuslab/yaml/decoder/DecoderSuite.scala | 14 +++++++++++ 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/core/shared/src/main/scala-3/org/virtuslab/yaml/YamlCodecCrossCompat.scala b/core/shared/src/main/scala-3/org/virtuslab/yaml/YamlCodecCrossCompat.scala index b703d9279..344047a3d 100644 --- a/core/shared/src/main/scala-3/org/virtuslab/yaml/YamlCodecCrossCompat.scala +++ b/core/shared/src/main/scala-3/org/virtuslab/yaml/YamlCodecCrossCompat.scala @@ -3,13 +3,11 @@ package org.virtuslab.yaml import scala.deriving.Mirror private[yaml] trait YamlCodecCompanionCrossCompat { + + def make[A](implicit decoder: YamlDecoder[A], encoder: YamlEncoder[A]): YamlCodec[A] + inline def derived[T](using m: Mirror.Of[T]): YamlCodec[T] = - new YamlCodec[T]: - val decoder = YamlDecoder.derived[T] - val encoder = YamlEncoder.derived[T] - - def construct(node: Node)(using - settings: LoadSettings = LoadSettings.empty - ): Either[ConstructError, T] = decoder.construct(node) - def asNode(obj: T): Node = encoder.asNode(obj) + val decoder = YamlDecoder.derived[T] + val encoder = YamlEncoder.derived[T] + make(decoder, encoder) } diff --git a/core/shared/src/main/scala/org/virtuslab/yaml/YamlCodec.scala b/core/shared/src/main/scala/org/virtuslab/yaml/YamlCodec.scala index 5afb4a13f..8b9dfc81c 100644 --- a/core/shared/src/main/scala/org/virtuslab/yaml/YamlCodec.scala +++ b/core/shared/src/main/scala/org/virtuslab/yaml/YamlCodec.scala @@ -5,23 +5,26 @@ package org.virtuslab.yaml */ trait YamlCodec[T] extends YamlDecoder[T] with YamlEncoder[T] { self => - def imap[T1](f: T => T1)(g: T1 => T): YamlCodec[T1] = - YamlCodec.from(self.map(f), self.contramap(g)) + def mapInvariant[T1](f: T => T1)(g: T1 => T): YamlCodec[T1] = + YamlCodec.make(self.map(f), self.mapContra(g)) - def iemap[T1](f: T => Either[ConstructError, T1])(g: T1 => T): YamlCodec[T1] = - YamlCodec.from(self.flatMap(f), self.contramap(g)) + def mapInvariantError[T1](f: T => Either[ConstructError, T1])(g: T1 => T): YamlCodec[T1] = + YamlCodec.make(self.flatMap(f), self.mapContra(g)) } object YamlCodec extends YamlCodecCompanionCrossCompat { def apply[T](implicit self: YamlCodec[T]): YamlCodec[T] = self - def from[A](decoder: YamlDecoder[A], encoder: YamlEncoder[A]): YamlCodec[A] = new YamlCodec[A] { + def make[A](implicit decoder: YamlDecoder[A], encoder: YamlEncoder[A]): YamlCodec[A] = + new YamlCodec[A] { - override def construct(node: Node)(implicit settings: LoadSettings): Either[ConstructError, A] = - decoder.construct(node) + override def construct(node: Node)(implicit + settings: LoadSettings + ): Either[ConstructError, A] = + decoder.construct(node) - override def asNode(obj: A): Node = - encoder.asNode(obj) - } + override def asNode(obj: A): Node = + encoder.asNode(obj) + } } diff --git a/core/shared/src/main/scala/org/virtuslab/yaml/YamlEncoder.scala b/core/shared/src/main/scala/org/virtuslab/yaml/YamlEncoder.scala index 57adf5660..e45f26eb4 100644 --- a/core/shared/src/main/scala/org/virtuslab/yaml/YamlEncoder.scala +++ b/core/shared/src/main/scala/org/virtuslab/yaml/YamlEncoder.scala @@ -6,7 +6,7 @@ package org.virtuslab.yaml trait YamlEncoder[T] { self => def asNode(obj: T): Node - final def contramap[T1](f: T1 => T): YamlEncoder[T1] = new YamlEncoder[T1] { + final def mapContra[T1](f: T1 => T): YamlEncoder[T1] = new YamlEncoder[T1] { override def asNode(obj: T1): Node = self.asNode(f(obj)) } } diff --git a/core/shared/src/test/scala-3/org/virtuslab/yaml/decoder/DecoderSuite.scala b/core/shared/src/test/scala-3/org/virtuslab/yaml/decoder/DecoderSuite.scala index d18ac38aa..ccbc8f8c0 100644 --- a/core/shared/src/test/scala-3/org/virtuslab/yaml/decoder/DecoderSuite.scala +++ b/core/shared/src/test/scala-3/org/virtuslab/yaml/decoder/DecoderSuite.scala @@ -191,6 +191,20 @@ class DecoderSuite extends munit.FunSuite: assertEquals(yaml.as[Spec], Right(expectedSpec)) } + test("codec mapping") { + case class Capital(value: String) + + object Capital: + given YamlCodec[Capital] = + YamlCodec.make[String].mapInvariant(s => Capital(s.toUpperCase))(_.value.toUpperCase) + + assertEquals( + "- hello\n- world\n".as[List[Capital]], + Right(List(Capital("HELLO"), Capital("WORLD"))) + ) + assertEquals(List(Capital("hello"), Capital("world")).asYaml, "- HELLO\n- WORLD\n") + } + test("alias for scalar node") { val yaml = s"""|- &a 5