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

ba.sake.sharaf.routing.pathParams.scala Maven / Gradle / Ivy

There is a newer version: 0.8.0
Show newest version
package ba.sake.sharaf
package routing

import java.util.UUID
import scala.deriving.*
import scala.quoted.*
import scala.util.Try

object param:
  def unapply[T](str: String)(using fp: FromPathParam[T]): Option[T] =
    fp.parse(str)

// typeclass for converting a path parameter to T
trait FromPathParam[T]:
  def parse(str: String): Option[T]

object FromPathParam {
  given FromPathParam[Int] with {
    def parse(str: String): Option[Int] = str.toIntOption
  }
  given FromPathParam[Long] with {
    def parse(str: String): Option[Long] = str.toLongOption
  }
  given FromPathParam[UUID] with {
    def parse(str: String): Option[UUID] = Try(UUID.fromString(str)).toOption
  }

  /* macro derivation */
  inline def derived[T]: FromPathParam[T] = ${ derivedMacro[T] }

  private def derivedMacro[T: Type](using Quotes): Expr[FromPathParam[T]] = {
    import quotes.reflect.*

    val mirror: Expr[Mirror.Of[T]] = Expr.summon[Mirror.Of[T]].getOrElse {
      report.errorAndAbort(
        s"Cannot derive FromPathParam[${Type.show[T]}] automatically because ${Type.show[T]} is not an ADT"
      )
    }

    mirror match
      case '{
            $m: Mirror.ProductOf[T]
          } =>
        report.errorAndAbort(
          s"Cannot derive FromPathParam[${Type.show[T]}] automatically because product types are not supported"
        )

      case '{
            type label <: Tuple;
            $m: Mirror.SumOf[T] { type MirroredElemLabels = `label` }
          } =>
        val isSingleCasesEnum = isSingletonCasesEnum[T]
        if !isSingleCasesEnum then
          report.errorAndAbort(
            s"Cannot derive FromPathParam[${Type.show[T]}] automatically because ${Type.show[T]} is not a singleton-cases enum"
          )

        val companion = TypeRepr.of[T].typeSymbol.companionModule.termRef
        val valueOfSelect = Select.unique(Ident(companion), "valueOf").symbol
        '{
          new FromPathParam[T] {
            override def parse(str: String): Option[T] =
              ${
                val labelQuote = 'str
                val tryBlock =
                  Block(Nil, Apply(Select(Ident(companion), valueOfSelect), List(labelQuote.asTerm))).asExprOf[T]
                '{
                  try {
                    Option($tryBlock)
                  } catch {
                    case e: IllegalArgumentException =>
                      None
                  }
                }
              }
          }
        }

      case hmm => report.errorAndAbort("Not supported")
  }

  private def isSingletonCasesEnum[T: Type](using Quotes): Boolean =
    import quotes.reflect.*
    val ts = TypeRepr.of[T].typeSymbol
    ts.flags.is(Flags.Enum) && ts.companionClass.methodMember("values").nonEmpty

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy