Input Object 'oneOf'
Input object oneOf to szczególny rodzaj input object, w którym dokładnie jedno z pól wejściowych musi zostać podane jako input, w przeciwnym razie serwer zwraca błąd walidacji. To zachowanie wprowadza polimorfizm dla inputów w GraphQL, umożliwiając projektowanie czystszych schematów.
Na przykład pobieranie użytkownika w naszej aplikacji może odbywać się według różnych właściwości, takich jak ID użytkownika lub e-mail. Aby to zrobić, normalnie musielibyśmy utworzyć oddzielne pole dla każdej właściwości:
type Query {
userByID(id: ID!): User
userByEmail(email: String!): User
}Dzięki input object oneOf możemy zamiast tego mieć jedno pole user, które akceptuje wszystkie właściwości za pośrednictwem input object oneOf UserByInput, wiedząc, że tylko jedna z właściwości (ID lub e-mail) może i musi zostać podana:
type Query {
user(by: UserByInput!): User
}
input UserByInput @oneOf {
id: ID
email: String
}(Zwróć uwagę, że składnia @oneOf powyżej służy wyłącznie do celów dokumentacyjnych w kontekście Gato GraphQL, ponieważ nie musimy używać SDL —Schema Definition Language— do generowania schematu; plugin już generuje schemat za pomocą kodu PHP, korzystając z inputów z Konfiguracji Schematu.)
W zapytaniu podajemy wartość input dla dokładnie jednej z właściwości:
{
tom: user(by: {
id: 1
}) {
name
}
jerry: user(by: {
email: "jerry@warnerbros.com"
}) {
name
}
}Jeśli podamy dwie (lub więcej) wartości do inputu:
{
user(by: {
id: 1
email: "jerry@warnerbros.com"
}) {
name
}
}... serwer zwróci błąd:
{
"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
}
}Jak Gato GraphQL wykorzystuje input objects oneOf
Przyjrzyjmy się kilku sytuacjom, w których plugin korzysta z tej funkcji i które możemy również wykorzystać do rozszerzenia naszych schematów GraphQL.
Wybieranie pojedynczej encji według różnych właściwości
Jest to ogólny przypadek zapytania zademonstrowanego powyżej, dotyczący inputu UserByInput w polu user.
Zawsze gdy musimy pobrać pojedynczą encję (pojedynczy User, Post, PostTag itd.), która może być jednoznacznie zidentyfikowana przez więcej niż jedną właściwość (np. przez ID lub e-mail, ID lub slug itd.), możemy zdefiniować wszystkie różne właściwości w input object oneOf i zbiegać wszystkie różne pola do pobrania tej encji w jedno pole.
Akceptowanie różnych zestawów danych w mutacjach
Podczas wykonywania mutacji możemy akceptować różne zestawy danych jako inputy. Zamiast udostępniać różne pola mutacji dla każdego odrębnego zestawu danych, używając input object oneOf, jedno pole mutacji może obsłużyć wszystkie możliwości.
Na przykład mutacja loginUser może obsługiwać logowanie użytkowników różnymi metodami: nazwa użytkownika/hasło, token JWT, application passwords lub inne. Dlatego ta mutacja przyjmuje Input Object oneOf LoginUserByInput, który aktualnie akceptuje standardową walidację nazwa użytkownika/hasło WordPress, ale może być również rozszerzony o inne metody:
type Mutation {
loginUser(by: LoginUserByInput!): RootLoginUserMutationPayload!
}
input LoginUserByInput @oneOf {
credentials: LoginCredentialsInput
}
input LoginCredentialsInput {
usernameOrEmail: String!
password: String!
}Odpytywanie meta values
Odpytywanie meta values w WordPress może być złożone, z kombinacjami inputów, które mogą ze sobą kolidować, jak wyjaśniono w jego dokumentacji:
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 '='.
Dokumentacja wyjaśnia, że value może być ciągiem znaków lub tablicą, i w zależności od tej wartości compare może akceptować jeden zestaw wartości lub inny (np. IN tylko dla tablic, LIKE tylko dla ciągów znaków). Ponadto value jest wymagane, ale tylko jeśli compare nie otrzymuje wartości EXISTS, w którym to przypadku value nie jest potrzebne.
Analizując różne zestawy inputów, odkryjemy, że istnieją 4 możliwe kombinacje, w zależności od porównania stosowanego na kluczu lub wartości oraz od typu wartości:
keynumericValuestringValuearrayValue
Input object oneOf MetaQueryCompareByInput obsługuje te 4 inputy, wspomagany przez różne Enum definiujące możliwe operatory, których może użyć każdy input. W ten sposób, filtrując według numericValue, możemy użyć operatora GREATER_THAN, według arrayValue możemy użyć operatora IN, a według key możemy użyć operatora EXISTS (bez potrzeby podawania value).
Wynikowy schemat GraphQL (używając SDL) wygląda następująco:
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
}W ten sposób, wybierając, którego inputu użyć w compareBy, poprawność całego zestawu danych wejściowych będzie walidowana przez GraphQL. Teraz, przy filtrowaniu postów, w których istnieje określona meta key, nie możemy podać value:
{
posts(filter: {
metaQuery: {
key: "_thumbnail_id",
compareBy:{
key: {
operator: EXISTS
}
}
}
}) {
id
title
metaValue(key: "_thumbnail_id")
}
}Aby filtrować posty "polubione" przez jakiegoś użytkownika, używamy inputu arrayValue i wybieramy operator IN:
query FilterPostsLikedByUser($userID: ID!) {
posts(filter: {
metaQuery: {
key: "liked_by_users",
compareBy:{
arrayValue: {
value: $userID
operator: IN
}
}
}
}) {
id
title
}
}Introspekcja: sprawdzanie, czy typ jest Input Object "oneOf"
Możemy sprawdzić, czy typ jest Input Object "oneOf" za pomocą pola introspekcji isOneOf:
query IsOneOfInputObject {
__schema {
types {
name
isOneOf
}
}
}