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

io.github.paoloboni.json.SumDecoder.scala Maven / Gradle / Ivy

There is a newer version: 1.6.1
Show newest version
/*
 * Copyright (c) 2021 Paolo Boni
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

package io.github.paoloboni.json

import io.circe.Decoder

case class Discriminator(name: String)

case class Perhaps[E](value: Option[E]):
  def fold[F](ifAbsent: => F)(ifPresent: E => F): F = value.fold(ifAbsent)(ifPresent)

object Perhaps:
  implicit def perhaps[E](implicit ev: E = null): Perhaps[E] = Perhaps(Option(ev))

trait SumDecoder[T] extends Decoder[T]

object SumDecoder:
  import scala.deriving._
  import scala.compiletime._
  import io.circe.DecodingFailure
  import io.circe.HCursor

  inline private def label[A]: String = constValue[A].asInstanceOf[String]

  inline def summonAllLabels[A <: Tuple]: List[String] =
    inline erasedValue[A] match
      case _: EmptyTuple => Nil
      case _: (t *: ts)  => label[t] :: summonAllLabels[ts]

  inline def summonAllDecoders[A <: Tuple]: List[Decoder[_]] =
    inline erasedValue[A] match
      case _: EmptyTuple => Nil
      case _: (t *: ts)  => summonInline[Decoder[t]] :: summonAllDecoders[ts]

  inline given derived[T](using m: Mirror.Of[T], discriminator: Perhaps[Discriminator]): SumDecoder[T] =
    inline m match {
      case s: Mirror.SumOf[T] =>
        val labels   = summonAllLabels[s.MirroredElemLabels]
        val decoders = summonAllDecoders[s.MirroredElemTypes]
        new SumDecoder[T]() {
          def apply(c: HCursor): Decoder.Result[T] = {
            val discriminatorKey = discriminator.fold[String]("type")(_.name)
            c.downField(discriminatorKey).as[String] match {
              case Right(value) if (labels.contains(value)) =>
                (labels zip decoders).find(_._1 == value) match {
                  case Some((_, decoder)) => decoder.asInstanceOf[Decoder[T]].apply(c)
                  case _                  => Left(DecodingFailure("decoding failure", c.history))
                }
              case Right(unexpected) => Left(DecodingFailure(s"Unexpected filterType $unexpected", c.history))
              case Left(failure)     => Left(failure)
            }
          }
        }
      case _ => throw MatchError("only sum types are supported")
    }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy