Input Object 'oneOf'
L'input object oneOf è un particolare tipo di input object, in cui esattamente uno dei campi di input deve essere fornito come input, altrimenti il server restituisce un errore di validazione. Questo comportamento introduce il polimorfismo per gli input in GraphQL, permettendoci di progettare schemi più puliti.
Ad esempio, recuperare un utente nella nostra applicazione potrebbe avvenire tramite diverse proprietà, come l'ID utente o l'e-mail. Per farlo, normalmente avremmo bisogno di creare un campo separato per ogni proprietà:
type Query {
userByID(id: ID!): User
userByEmail(email: String!): User
}Grazie all'input object oneOf, possiamo invece avere un unico campo user che accetta tutte le proprietà tramite un input object oneOf UserByInput, sapendo che solo una delle proprietà (o l'ID, o l'e-mail) può e deve essere fornita:
type Query {
user(by: UserByInput!): User
}
input UserByInput @oneOf {
id: ID
email: String
}(Nota che la sintassi @oneOf qui sopra è solo a scopo di documentazione nel contesto di Gato GraphQL, poiché non abbiamo bisogno di usare SDL —Schema Definition Language— per generare lo schema; il plugin genera già lo schema tramite codice PHP, utilizzando gli input della Configurazione dello Schema.)
Nella query, forniamo il valore di input per esattamente una delle proprietà:
{
tom: user(by: {
id: 1
}) {
name
}
jerry: user(by: {
email: "jerry@warnerbros.com"
}) {
name
}
}Se forniamo due (o più) valori all'input:
{
user(by: {
id: 1
email: "jerry@warnerbros.com"
}) {
name
}
}... allora il server restituirà un errore:
{
"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
}
}Come Gato GraphQL utilizza gli input object oneOf
Vediamo alcune situazioni in cui il plugin fa uso di questa funzionalità, e che possiamo anche utilizzare per estendere i nostri schemi GraphQL.
Selezionare una singola entità tramite diverse proprietà
Questo è il caso generale della query dimostrata sopra, riguardante l'input UserByInput nel campo user.
Ogni volta che abbiamo bisogno di recuperare una singola entità (un singolo User, Post, PostTag, ecc.) che può essere identificata in modo univoco da più di una proprietà (come per ID o e-mail, ID o slug, ecc.), possiamo definire tutte le diverse proprietà in un input object oneOf, e far convergere tutti i diversi campi che recuperano quell'entità in un unico campo.
Accettare diversi insiemi di dati nelle mutation
Durante una mutation, possiamo accettare diversi insiemi di dati come input. Invece di esporre diversi campi di mutation per ogni insieme di dati distinto, utilizzando un input object oneOf, un unico campo di mutation può coprire tutte le possibilità.
Ad esempio, la mutation loginUser può supportare l'accesso degli utenti tramite diversi metodi: nome utente/password, token JWT, application password o altri. Ecco perché questa mutation riceve l'Input Object oneOf LoginUserByInput, che attualmente accetta la validazione standard nome utente/password di WordPress, ma può anche essere esteso ad altri metodi:
type Mutation {
loginUser(by: LoginUserByInput!): RootLoginUserMutationPayload!
}
input LoginUserByInput @oneOf {
credentials: LoginCredentialsInput
}
input LoginCredentialsInput {
usernameOrEmail: String!
password: String!
}Interrogare i meta value
Interrogare i meta value in WordPress può essere complesso, con combinazioni di input che possono entrare in conflitto tra loro, come spiegato nella sua documentazione:
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 '='.
La documentazione spiega che value può essere una stringa o un array, e a seconda di questo valore, compare può accettare un insieme di valori o un altro (come IN solo per gli array, LIKE solo per le stringhe). Inoltre, value è obbligatorio, ma solo se compare non riceve EXISTS, nel qual caso value non è affatto necessario.
Analizzando i diversi insiemi di input, scopriremo che esistono 4 combinazioni possibili, a seconda del confronto applicato sulla chiave o sul valore, e del tipo di valore:
keynumericValuestringValuearrayValue
L'input object oneOf MetaQueryCompareByInput gestisce questi 4 input, coadiuvato da diversi Enum che definiscono i possibili operatori che ogni input può utilizzare. Quindi, filtrando per numericValue possiamo utilizzare l'operatore GREATER_THAN, per arrayValue possiamo utilizzare l'operatore IN, e per key possiamo utilizzare l'operatore EXISTS (e non c'è bisogno di fornire un value).
Lo schema GraphQL risultante (utilizzando SDL) è il seguente:
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
}In questo modo, scegliendo quale input utilizzare sotto compareBy, la correttezza dell'intero insieme di dati di input sarà validata da GraphQL. Ora, quando filtriamo i post per cui esiste una determinata meta key, non possiamo fornire un value:
{
posts(filter: {
metaQuery: {
key: "_thumbnail_id",
compareBy:{
key: {
operator: EXISTS
}
}
}
}) {
id
title
metaValue(key: "_thumbnail_id")
}
}Per filtrare i post "apprezzati" da un utente utilizziamo l'input arrayValue e selezioniamo l'operatore IN:
query FilterPostsLikedByUser($userID: ID!) {
posts(filter: {
metaQuery: {
key: "liked_by_users",
compareBy:{
arrayValue: {
value: $userID
operator: IN
}
}
}
}) {
id
title
}
}Introspezione: scoprire se un tipo è un Input Object "oneOf"
Possiamo scoprire se un tipo è un Input Object "oneOf" tramite il campo di introspezione isOneOf:
query IsOneOfInputObject {
__schema {
types {
name
isOneOf
}
}
}