有没有人知道如何使用Shapeless使这个测试工作。
package net.jtownson.swakka.jsonschema
import org.scalatest.FlatSpec
import org.scalatest.Matchers._
class OptionalFieldSpec extends FlatSpec {
case class A(i: Int, j: Option[Int])
"an extractor of some kind" should "get the (non)optional fields from a case class" in {
extractNonOptionalFieldNames[A] shouldBe List("i")
extractOptionalFieldNames[A] shouldBe List("j")
}
def extractNonOptionalFieldNames[T <: Product](/* implicit typeclass instances? */): List[String] = ???
def extractOptionalFieldNames[T <: Product]: List[String] = ???
}
我没有 A 或其泛型等效项的运行时实例,因为我正在努力为案例类 A 创建独立于任何给定实例的 JsonSchema。架构具有必填字段,该字段是非可选字段的列表。例如
{
"type" -> "object",
"required" -> ["i"],
"properties" -> {
"i" -> {
"type" -> "integer",
"format" -> "int32"
}
}
}
像这样:
trait FieldNameExtractor[T] extends Serializable {
import shapeless.ops.hlist.{RightFolder, ToTraversable}
import shapeless.ops.record.Keys
import shapeless.{HList, HNil, LabelledGeneric, Poly2}
/**
* Extracts filtered field names for type [[T]],
* given a polymorphic function that acts as the type filter
*/
def extract[L <: HList, R <: HList, O <: HList](op: Poly2)(
implicit lgen: LabelledGeneric.Aux[T, L],
folder: RightFolder.Aux[L, HNil.type, op.type, R],
keys: Keys.Aux[R, O],
traversable: ToTraversable.Aux[O, List, Symbol]
): List[String] = {
val result = keys().to[List]
result.map(_.name)
}
}
object FieldNameExtractor {
def apply[T] = new FieldNameExtractor[T] {}
}
用法:
import org.scalatest.FlatSpec
import org.scalatest.Matchers._
class Test extends FlatSpec {
/* type filters */
import shapeless.{HList, Poly2}
import shapeless.labelled.KeyTag, shapeless.tag.Tagged
type FilterO[A, T] = Option[A] with KeyTag[Symbol with Tagged[T], Option[A]]
trait Ignore extends Poly2 {
implicit def default[A, L <: HList] = at[A, L]((_, l) => l)
}
trait Accept extends Poly2 {
implicit def default[A, L <: HList] = at[A, L](_ :: _)
}
object allOptions extends Ignore {
implicit def option[A, T, L <: HList] = at[FilterO[A, T], L](_ :: _)
}
object noOptions extends Accept {
implicit def option[A, T, L <: HList] = at[FilterO[A, T], L]((_, l) => l)
}
"an extractor of some kind" should "get the (non)optional fields from a case class" in {
case class A(i: Int, j: Option[Int], k: String)
val fne = FieldNameExtractor[A]
fne.extract(noOptions) shouldBe List("i", "k") // extractNonOptionalFieldNames
fne.extract(allOptions) shouldBe List("j") // extractOptionalFieldNames
}
}
以下是使用类型类的一种方法:
import shapeless._
import shapeless.labelled.FieldType
trait OptionExtractor[A] {
type B <: HList
}
trait LowPriorityOptionExtractor {
implicit def hconsExtractor[K, V, T <: HList](implicit
extractor: OptionExtractor[T]):
OptionExtractor.Aux[FieldType[K, V] :: T, extractor.B] = new OptionExtractor[FieldType[K, V] :: T] {
type B = extractor.B
}
}
object OptionExtractor extends LowPriorityOptionExtractor {
type Aux[A, B0 <: HList] = OptionExtractor[A] {type B = B0}
def apply[A](implicit extractor: OptionExtractor[A]): OptionExtractor.Aux[A, extractor.B] = extractor
implicit val hnilOptionExtractor: OptionExtractor.Aux[HNil, HNil] = new OptionExtractor[HNil] {
type B = HNil
}
implicit def hconsOptionExtractor[K, V, T <: HList](implicit extractor: OptionExtractor[T]):
OptionExtractor.Aux[FieldType[K, Option[V]] :: T, K :: extractor.B] = new OptionExtractor[FieldType[K, Option[V]] :: T] {
type B = K :: extractor.B
}
}
有几件事可能需要解释:
- 既然你提到你没有
A
的运行时实例.您希望返回的类型级别表示形式是什么?在此解决方案中,我只是为可选证人返回了HList
见证人。我认为List[String]
表示是不够的,因为过滤掉非可选值与什么都不做具有相同的类型。 - 类型类具有优先级,因此筛选选项与反向优先级相同。
它可以像这样使用:
case class A(i: Int, j: Option[Int], k: Option[Long])
val x = LabelledGeneric[A]
type filteredType = OptionExtractor[x.Repr]
//type B = Symbol with shapeless.tag.Tagged[String("j")] :: Symbol with shapeless.tag.Tagged[String("k")] :: shapeless.HNil