'oneOf' Input Object
oneOf input object 是一种特殊类型的 input object,其中的输入字段中必须且仅能提供一个值,否则服务器将返回验证错误。这种行为为 GraphQL 的输入引入了多态性,使我们能够设计出更简洁的 schema。
例如,在应用中通过不同属性(如用户 ID 或电子邮件)查询用户时,通常需要为每个属性分别创建一个字段:
type Query {
userByID(id: ID!): User
userByEmail(email: String!): User
}借助 oneOf input object,我们可以改用单一字段 user,通过 UserByInput oneOf input object 接受所有属性,同时保证属性(ID 或电子邮件)中有且仅有一个被提供:
type Query {
user(by: UserByInput!): User
}
input UserByInput @oneOf {
id: ID
email: String
}(请注意,上面的 @oneOf 语法仅用于 Gato GraphQL 上下文中的文档说明,我们无需使用 SDL(Schema Definition Language)来生成 schema;插件已通过 PHP 代码,利用 Schema Configuration 中的输入来生成 schema。)
在 query 中,我们为属性中的某一个提供精确的输入值:
{
tom: user(by: {
id: 1
}) {
name
}
jerry: user(by: {
email: "jerry@warnerbros.com"
}) {
name
}
}如果我们为输入提供两个(或更多)值:
{
user(by: {
id: 1
email: "jerry@warnerbros.com"
}) {
name
}
}……服务器将返回错误:
{
"errors": [
{
"message": "The oneOf input object 'UserByInput' must be provided exactly one value, but 2 have been provided",
"extensions": {
"type": "Query",
"field": "user(by:{id:1,email:\"jerry@warnerbros.com\"})",
"argument": "by"
}
}
],
"data": {
"user": null
}
}Gato GraphQL 如何使用 oneOf input object
让我们来看看插件使用此功能的几种场景,以及我们可以借助哪些方式扩展自己的 GraphQL schema。
通过不同属性选取单一实体
这是上述 query 的通用案例,涉及字段 user 中的 input UserByInput。
当我们需要获取可通过多个属性(如 ID 或电子邮件、ID 或 slug 等)唯一标识的单一实体(单个 User、Post、PostTag 等)时,可以将所有不同的属性定义到一个 oneOf input object 中,并将用于获取该实体的不同字段合并为单一字段。
在 mutation 中接受不同的数据集
执行 mutation 时,我们可能需要接受不同的数据集作为输入。与其为每个不同的数据集分别暴露 mutation 字段,不如使用 oneOf input object,用单一的 mutation 字段涵盖所有可能性。
例如,mutation loginUser 可以支持多种用户登录方式:用户名/密码、JWT token、应用程序密码等。因此,该 mutation 接受 oneOf Input Object LoginUserByInput,目前支持 WordPress 标准的用户名/密码验证,未来也可扩展到其他方式:
type Mutation {
loginUser(by: LoginUserByInput!): RootLoginUserMutationPayload!
}
input LoginUserByInput @oneOf {
credentials: LoginCredentialsInput
}
input LoginCredentialsInput {
usernameOrEmail: String!
password: String!
}查询 meta 值
在 WordPress 中查询 meta 值可能较为复杂,涉及多种可能相互冲突的输入组合,正如其文档所述:
The following arguments can be passed in a key=>value paired array.
- meta_query (array) – Contains one or more arrays with the following keys:
- key (string) – Custom field key.
- value (string|array) – Custom field value. It can be an array only when compare is 'IN', 'NOT IN', 'BETWEEN', or 'NOT BETWEEN'. You don't have to specify a value when using the 'EXISTS' or 'NOT EXISTS' comparisons in WordPress 3.9 and up. (Note: Due to bug #23268, value was required for NOT EXISTS comparisons to work correctly prior to 3.9. You had to supply some string for the value parameter. An empty string or NULL will NOT work. However, any other string will do the trick and will NOT show up in your SQL when using NOT EXISTS. Need inspiration? How about 'bug #23268'.)
- compare (string) – Operator to test. Possible values are '=', '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN', 'EXISTS' (only in WP >= 3.5), and 'NOT EXISTS' (also only in WP >= 3.5). Values 'REGEXP', 'NOT REGEXP' and 'RLIKE' were added in WordPress 3.7. Default value is '='.
文档说明 value 可以是字符串或数组,依据此值,compare 所接受的值集合也会不同(例如 IN 仅适用于数组,LIKE 仅适用于字符串)。此外,value 是必填的,但当 compare 接收 EXISTS 时,value 则不再需要。
分析不同的输入集合,我们会发现根据对 key 或 value 应用的比较类型及值的类型,共有 4 种可能的组合:
keynumericValuestringValuearrayValue
oneOf input object MetaQueryCompareByInput 借助定义各 input 可用运算符的不同 Enum,处理这 4 种输入。使用 numericValue 过滤时可使用运算符 GREATER_THAN,使用 arrayValue 过滤时可使用运算符 IN,使用 key 过滤时可使用运算符 EXISTS(此时无需提供 value)。
生成的 GraphQL schema(使用 SDL)如下:
type Query {
posts(filter: PostsFilterInput): [Post!]!
}
input PostsFilterInput {
metaQuery: [PostMetaQueryInput!]
}
input PostMetaQueryInput {
compareBy: MetaQueryCompareByInput!
key: String!
}
type MetaQueryCompareByInput @oneOf {
"""
Compare against the meta key
"""
key: MetaQueryCompareByKeyInput
"""
Compare against an array meta value
"""
array: ValueMetaQueryCompareByArrayValueInput
"""
Compare against a numeric meta value
"""
numeric: ValueMetaQueryCompareByNumericValueInput
"""
Compare against a string meta value
"""
string: ValueMetaQueryCompareByStringValueInput
}
input MetaQueryCompareByKeyInput {
operator: MetaQueryCompareByKeyOperatorEnum!
}
enum MetaQueryCompareByKeyOperatorEnum {
EXISTS
NOT_EXISTS
}
input ValueMetaQueryCompareByArrayValueInput {
operator: MetaQueryCompareByArrayValueOperatorEnum!
value: [AnyBuiltInScalar!]!
}
# AnyBuiltInScalar: Int, Float, String or Bool
scalar AnyBuiltInScalar
enum MetaQueryCompareByArrayValueOperatorEnum {
BETWEEN
IN
NOT_BETWEEN
NOT_IN
}
input ValueMetaQueryCompareByNumericValueInput {
operator: MetaQueryCompareByNumericValueOperatorEnum!
value: Numeric!
}
enum MetaQueryCompareByNumericValueOperatorEnum {
EQUALS
GREATER_THAN
GREATER_THAN_OR_EQUAL
LESS_THAN
LESS_THAN_OR_EQUAL
NOT_EQUALS
}
# Numeric: Float or Int
scalar Numeric
input ValueMetaQueryCompareByStringValueInput {
operator: MetaQueryCompareByStringValueOperatorEnum!
value: String!
}
enum MetaQueryCompareByStringValueOperatorEnum {
EQUALS
LIKE
NOT_EQUALS
NOT_LIKE
NOT_REGEXP
REGEXP
RLIKE
}这样,通过在 compareBy 中选择使用哪个 input,GraphQL 将验证整体输入数据集的正确性。现在,在过滤某个 meta key 存在的文章时,我们无法提供 value:
{
posts(filter: {
metaQuery: {
key: "_thumbnail_id",
compareBy:{
key: {
operator: EXISTS
}
}
}
}) {
id
title
metaValue(key: "_thumbnail_id")
}
}要过滤某用户"点赞"的文章,我们使用 input arrayValue 并选择运算符 IN:
query FilterPostsLikedByUser($userID: ID!) {
posts(filter: {
metaQuery: {
key: "liked_by_users",
compareBy:{
arrayValue: {
value: $userID
operator: IN
}
}
}
}) {
id
title
}
}自省:判断一个类型是否为「oneOf」Input Object
我们可以通过自省字段 isOneOf 来判断某个类型是否为「oneOf」Input Object:
query IsOneOfInputObject {
__schema {
types {
name
isOneOf
}
}
}