我有一个看起来像这样的通用结构:
trait KeyType extends Product
case class DataBag[T <: KeyType](
key: T
someData: String,
someMoreData: String
)
以及具体实现:
case class NameKey(firstName: String, lastName: String) extends Product
type PersonData = DataBag[NameKey]
定义 Tapir 模式的好/简单/惯用的方法是什么,它会“扁平化”我的结构,并导致 json 看起来像这样
{
"firstName": "John",
"lastName": "Doe",
"someData": "foo",
"someMoreData": "bar"
}
我的目标是避免仅仅为了产生这个输出而定义另一个案例类。可以吗?
您必须定义两个值。首先,一个 json 编码器/解码器,使用您选择的 json 库(例如,在运行端点的服务器逻辑时,用于将高级类型的响应编码为字符串)。其次,您需要一个匹配的貘
Schema
,用于验证和生成文档。 (我们正在开发一个基于 uPickle 的实验性 pickler 模块,它将在一次运行中结合两个推导,但它仍然不完整)。
但要回答这个具体问题,您需要手动创建一些模式。这不是最漂亮的代码(可能模式可以更好地支持这些类型的操作,打赌吧:))。
首先,我们有数据类型:
trait KeyType extends Product
case class DataBag[T <: KeyType](
key: T,
someData: String,
someMoreData: String
)
case class NameKey(firstName: String, lastName: String) extends KeyType
type PersonData = DataBag[NameKey]
然后,我们通过派生空键的模式来捕获数据包中除键之外的所有字段的模式:
case class EmptyKey() extends KeyType
val dataBagWithEmptyKeySchema: Schema[DataBag[EmptyKey]] = {
implicit val emptyKeySchema: Schema[EmptyKey] = Schema.derived
Schema.derived
}
val dataBagFieldsWithoutKey: List[SchemaType.SProductField[DataBag[EmptyKey]]] =
dataBagWithEmptyKeySchema
.schemaType
.asInstanceOf[SchemaType.SProduct[DataBag[EmptyKey]]]
.fields
.filterNot(_.name.name == "key")
.asInstanceOf
之所以存在,是因为我们确信派生模式是一个乘积(而不是例如余积)。
然后,我们定义一个方法,该方法采用任意键及其模式。我们需要获取密钥的字段,“修复”它们的
.get
方法,以便它正确地取消引用 .key
,并与我们之前捕获的数据包字段结合起来:
def schemaForDataBag[T <: KeyType: Schema]: Schema[DataBag[T]] = {
val keyFields: List[SchemaType.SProductField[T]] =
implicitly[Schema[T]].schemaType match {
case SchemaType.SProduct(fields) => fields
case _ => throw new IllegalStateException("Key is not a product")
}
def fixField(f: SchemaType.SProductField[T]): SchemaType.SProductField[DataBag[T]] =
SchemaType.SProductField[DataBag[T], f.FieldType](f.name, f.schema, db => f.get(db.key))
val fixedKeyFields = keyFields.map(fixField)
val allFields: List[SchemaType.SProductField[DataBag[T]]] = fixedKeyFields ++
dataBagFieldsWithoutKey
.asInstanceOf[List[SchemaType.SProductField[DataBag[T]]]]
dataBagWithEmptyKeySchema
.as[DataBag[T]]
.copy(schemaType = SchemaType.SProduct(allFields))
}
最后,我们现在可以将其用于任何密钥模式;请注意,我们使用的是半自动推导,因此需要为每个组件定义隐式:
implicit val nameKeySchema: Schema[NameKey] = Schema.derived
implicit val personDataSchema: Schema[PersonData] = schemaForDataBag[NameKey]
println(personDataSchema)