1

我想根据不同的类型(包括精炼的Scala)提供案例类的json模式(在编译时)

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()}""")
  }

}

我希望能够对所有无形的自然进行模式匹配:

typeOf[Size[_]]代替typeOf[Size[_0]]

但我有一个编译错误:

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

我想得到一个 int 的大小

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

用法 :

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}}"""

    }  
  }

4

1 回答 1

1

尝试

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()))
  }

但是考虑一下是否可以在类型类而不是原始宏中编码逻辑是有意义的。

于 2019-09-03T11:02:03.753 回答