Skip to content

Commit

Permalink
Merge pull request #303 from sideeffffect/apply-helper
Browse files Browse the repository at this point in the history
  • Loading branch information
lbialy authored May 31, 2024
2 parents d3f3a3b + 6eab7df commit c78ba30
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
26 changes: 24 additions & 2 deletions core/shared/src/main/scala/org/virtuslab/yaml/YamlCodec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,28 @@ 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 =>

object YamlCodec extends YamlCodecCompanionCrossCompat
def mapInvariant[T1](f: T => T1)(g: T1 => T): YamlCodec[T1] =
YamlCodec.make(self.map(f), self.mapContra(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 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 asNode(obj: A): Node =
encoder.asNode(obj)
}
}
16 changes: 15 additions & 1 deletion core/shared/src/main/scala/org/virtuslab/yaml/YamlDecoder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,23 @@ 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 {

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] =
Expand Down Expand Up @@ -147,7 +161,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(_))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@ 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 mapContra[T1](f: T1 => T): YamlEncoder[T1] = new YamlEncoder[T1] {
override def asNode(obj: T1): Node = self.asNode(f(obj))
}
}

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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit c78ba30

Please sign in to comment.