Input Object 'oneOf'
O input object oneOf é um tipo particular de input object, em que exatamente um dos campos de entrada deve ser fornecido como input, caso contrário o servidor retorna um erro de validação. Esse comportamento introduz polimorfismo para inputs em GraphQL, permitindo projetar schemas mais limpos.
Por exemplo, recuperar um usuário em nossa aplicação pode ser feito por diferentes propriedades, como o ID do usuário ou o e-mail. Para fazer isso, normalmente precisaríamos criar um campo separado para cada propriedade:
type Query {
userByID(id: ID!): User
userByEmail(email: String!): User
}Graças ao input object oneOf, podemos ter um único campo user que aceita todas as propriedades via um input object oneOf UserByInput, sabendo que apenas uma das propriedades (o ID ou o e-mail) pode e deve ser fornecida:
type Query {
user(by: UserByInput!): User
}
input UserByInput @oneOf {
id: ID
email: String
}(Observe que a sintaxe @oneOf acima é apenas para fins de documentação no contexto do Gato GraphQL, pois não precisamos usar SDL —Schema Definition Language— para gerar o schema; o plugin já gera o schema via código PHP, usando os inputs da Configuração do Schema.)
Na query, fornecemos o valor de input para exatamente uma das propriedades:
{
tom: user(by: {
id: 1
}) {
name
}
jerry: user(by: {
email: "jerry@warnerbros.com"
}) {
name
}
}Se fornecermos dois (ou mais) valores ao input:
{
user(by: {
id: 1
email: "jerry@warnerbros.com"
}) {
name
}
}... então o servidor retornará um erro:
{
"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
}
}Como o Gato GraphQL utiliza os input objects oneOf
Vamos ver algumas situações em que o plugin faz uso desse recurso, e que também podemos usar para estender nossos schemas GraphQL.
Selecionar uma única entidade por diferentes propriedades
Este é o caso geral da query demonstrada acima, referente ao input UserByInput no campo user.
Sempre que precisarmos recuperar uma única entidade (um único User, Post, PostTag, etc.) que pode ser identificada de forma única por mais de uma propriedade (como por ID ou e-mail, ID ou slug, etc.), podemos definir todas as diferentes propriedades em um input object oneOf, e convergir todos os diferentes campos para recuperar essa entidade em um único campo.
Aceitar diferentes conjuntos de dados em mutations
Ao fazer uma mutation, podemos aceitar diferentes conjuntos de dados como inputs. Em vez de expor diferentes campos de mutation para cada conjunto de dados distinto, usando um input object oneOf, um único campo de mutation pode lidar com todas as possibilidades.
Por exemplo, a mutation loginUser pode suportar o login de usuários por diferentes métodos: nome de usuário/senha, token JWT, application passwords ou outros. É por isso que essa mutation recebe o Input Object oneOf LoginUserByInput, que atualmente aceita a validação padrão de nome de usuário/senha do WordPress, mas também pode ser expandida para outros métodos:
type Mutation {
loginUser(by: LoginUserByInput!): RootLoginUserMutationPayload!
}
input LoginUserByInput @oneOf {
credentials: LoginCredentialsInput
}
input LoginCredentialsInput {
usernameOrEmail: String!
password: String!
}Consultar meta values
Consultar meta values no WordPress pode ser complexo, com combinações de inputs que podem entrar em conflito entre si, conforme explicado em sua documentação:
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 '='.
A documentação explica que value pode ser uma string ou um array, e dependendo desse valor, compare pode aceitar um conjunto de valores ou outro (como IN apenas para arrays, LIKE apenas para strings). Além disso, value é obrigatório, mas somente se compare não receber EXISTS, caso em que value não é necessário.
Analisando os diferentes conjuntos de inputs, descobriremos que existem 4 combinações possíveis, dependendo da comparação aplicada na chave ou no valor, e do tipo do valor:
keynumericValuestringValuearrayValue
O input object oneOf MetaQueryCompareByInput lida com esses 4 inputs, auxiliado por diferentes Enums que definem os possíveis operadores que cada input pode usar. Assim, filtrando por numericValue podemos usar o operador GREATER_THAN, por arrayValue podemos usar o operador IN, e por key podemos usar o operador EXISTS (sem necessidade de fornecer um value).
O schema GraphQL resultante (usando SDL) é este:
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
}Dessa forma, ao escolher qual input usar em compareBy, a correção do conjunto geral de dados de input será validada pelo GraphQL. Agora, ao filtrar posts em que determinada meta key existe, não podemos fornecer um value:
{
posts(filter: {
metaQuery: {
key: "_thumbnail_id",
compareBy:{
key: {
operator: EXISTS
}
}
}
}) {
id
title
metaValue(key: "_thumbnail_id")
}
}Para filtrar posts "curtidos" por algum usuário, usamos o input arrayValue e selecionamos o operador IN:
query FilterPostsLikedByUser($userID: ID!) {
posts(filter: {
metaQuery: {
key: "liked_by_users",
compareBy:{
arrayValue: {
value: $userID
operator: IN
}
}
}
}) {
id
title
}
}Introspecção: descobrir se um tipo é um Input Object "oneOf"
Podemos descobrir se um tipo é um Input Object "oneOf" via o campo de introspecção isOneOf:
query IsOneOfInputObject {
__schema {
types {
name
isOneOf
}
}
}