Pattern matching on scala Refined size

375 views Asked by At

I want to provide a json schema (at compile time) of a case class, based on the differents types (Scala refined included)

object JsonSchema {

  def jsonSchema[T]: String = macro impl[T]

  def impl[T: c.WeakTypeTag](c: scala.reflect.macros.whitebox.Context): c.Expr[String] = {
    import c.universe._

    val r = weakTypeOf[T].decls.collect {
      case m: MethodSymbol if m.isCaseAccessor =>
        val typeArgs = m.info match {
          case NullaryMethodType(v) => v.typeArgs
        }

        val supportedStringFormat = List("IPv4", "IPv6", "Uri")


        typeArgs match {
          case _type :: _predicate :: Nil if _type =:= typeOf[String] && supportedStringFormat.contains(_predicate.typeSymbol.name.toString())  => Json.obj(m.name.decodedName.toString -> Json.obj("type" -> "string", "format" -> _predicate.typeSymbol.name.toString().toLowerCase()))
          case _type :: _predicate :: Nil if _type =:= typeOf[String] && _predicate =:= typeOf[NonEmpty] => Json.obj(m.name.decodedName.toString -> Json.obj("type" -> "string", "minLength" -> 1))
          case _type :: _predicate :: Nil if _type =:= typeOf[String] && _predicate =:= typeOf[Size[_0]] => {

            val size   = _predicate.typeArgs match {
              case h :: _ if h <:< typeOf[Nat._0] => 0
            }
            Json.obj(m.name.decodedName.toString->  Json.obj("type" -> "string", "minLength" -> size))
          }
          case _type :: _ if _type =:= typeOf[String]  => Json.obj(m.name.decodedName.toString ->  Json.obj("type" -> "string"))
          case _type :: _predicate :: Nil if _type =:= typeOf[Int] && _predicate =:= typeOf[Positive] =>  Json.obj(m.name.decodedName.toString ->Json.obj("type" -> "int", "minValue" -> 1))
          case _type :: _predicate :: Nil if _type =:= typeOf[List[String]] && _predicate =:= typeOf[NonEmpty] =>  Json.obj(m.name.decodedName.toString-> Json.obj("type" -> "array", "minLength" -> 1))
          case List() => Json.obj(m.name.decodedName.toString ->Json.obj("type"-> m.info.typeSymbol.name.decodedName.toString.toLowerCase()))
          case other => Json.obj("other"-> other.map(_.toString()).mkString)

        }
    }

    val json = r.reduce(_ ++ _)
    c.Expr[String](q"""${json.toString()}""")
  }

}

I want to be able to pattern match for all shapeless natural :

typeOf[Size[_]] instead of typeOf[Size[_0]]

But i have a compile error :

 No TypeTag available for eu.timepit.refined.collection.Size[_]
[error]           case _type :: _predicate :: Nil if _type =:= typeOf[String] && _predicate =:= typeOf[Size[_]] => {

and i want to get an int for size

val size   = _predicate.typeArgs match {
             case h :: _ if h <:< typeOf[Nat._0] => 0
}

Usage :

case class StringWithMinSize22(k: String Refined MinSize[_22])

"String with min size 22" must {
    "return a schema with min size" in {
      JsonSchema.jsonSchema[StringWithMinSize22] mustBe """{"k":{"type":"string","minLength":22}}"""

    }  
  }

1

There are 1 answers

2
Dmytro Mitin On BEST ANSWER

Try

val sizeTC = typeOf[eu.timepit.refined.collection.Size[_]]

val r = weakTypeOf[T].decls.collect {

  // ...

  case _type :: _predicate :: Nil if _type =:= typeOf[String] && _predicate <:< sizeTC => {
    val sizeTyp = _predicate.dealias.typeArgs.head.typeArgs.head
    val toIntTree = c.inferImplicitValue(c.typecheck(tq"_root_.shapeless.ops.nat.ToInt[$sizeTyp]", mode = c.TYPEmode).tpe, silent = false)
    val toInt = c.eval(c.Expr(c.untypecheck(toIntTree.duplicate)))

    Json.obj(m.name.decodedName.toString ->  Json.obj("type" -> "string", "minLength" -> toInt.asInstanceOf[ToInt[_]].apply()))
  }

But it would make sense to think if you can encode your logic in a type class rather than raw macros.