Input Object 'oneOf'
Input object oneOf adalah jenis input object tertentu, di mana tepat satu dari field input harus disediakan sebagai input, atau jika tidak server akan mengembalikan error validasi. Perilaku ini memperkenalkan polimorfisme untuk input dalam GraphQL, memungkinkan kita merancang skema yang lebih rapi.
Misalnya, mengambil pengguna dalam aplikasi kita dapat dilakukan berdasarkan properti yang berbeda, seperti ID pengguna atau email. Untuk melakukan ini, biasanya kita perlu membuat field terpisah untuk setiap properti:
type Query {
userByID(id: ID!): User
userByEmail(email: String!): User
}Berkat input object oneOf, kita dapat memiliki satu field user yang menerima semua properti melalui input object oneOf UserByInput, dengan mengetahui bahwa hanya satu properti (baik ID atau email) yang dapat dan harus disediakan:
type Query {
user(by: UserByInput!): User
}
input UserByInput @oneOf {
id: ID
email: String
}(Perlu diperhatikan bahwa sintaks @oneOf di atas hanya untuk tujuan dokumentasi dalam konteks Gato GraphQL, karena kita tidak perlu menggunakan SDL โSchema Definition Languageโ untuk menghasilkan skema; plugin sudah menghasilkan skema melalui kode PHP, menggunakan input dari Konfigurasi Skema.)
Dalam query, kita menyediakan nilai input untuk tepat satu properti:
{
tom: user(by: {
id: 1
}) {
name
}
jerry: user(by: {
email: "jerry@warnerbros.com"
}) {
name
}
}Jika kita menyediakan dua (atau lebih) nilai ke input:
{
user(by: {
id: 1
email: "jerry@warnerbros.com"
}) {
name
}
}... maka server akan mengembalikan error:
{
"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
}
}Bagaimana Gato GraphQL menggunakan input object oneOf
Mari kita lihat beberapa situasi di mana plugin memanfaatkan fitur ini, dan yang juga dapat kita gunakan untuk memperluas skema GraphQL kita.
Memilih satu entitas berdasarkan properti yang berbeda
Ini adalah kasus umum untuk query yang didemonstrasikan di atas, terkait input UserByInput pada field user.
Setiap kali kita perlu mengambil satu entitas (satu User, Post, PostTag, dll.) yang dapat diidentifikasi secara unik oleh lebih dari satu properti (seperti berdasarkan ID atau email, ID atau slug, dll.), maka kita dapat mendefinisikan semua properti yang berbeda ke dalam satu input object oneOf, dan menggabungkan semua field yang berbeda untuk mengambil entitas tersebut menjadi satu field.
Menerima kumpulan data yang berbeda dalam mutasi
Saat melakukan mutasi, kita dapat menerima kumpulan data yang berbeda sebagai input. Alih-alih mengekspos field mutasi yang berbeda untuk setiap kumpulan data yang berbeda, dengan menggunakan input object oneOf, satu field mutasi dapat menangani semua kemungkinan.
Misalnya, mutasi loginUser dapat mendukung login pengguna dengan sejumlah metode yang berbeda: username/password, token JWT, application passwords, atau lainnya. Itulah mengapa mutasi ini menerima Input Object oneOf LoginUserByInput, yang saat ini menerima validasi WordPress standar username/password, tetapi juga dapat diperluas ke metode lain:
type Mutation {
loginUser(by: LoginUserByInput!): RootLoginUserMutationPayload!
}
input LoginUserByInput @oneOf {
credentials: LoginCredentialsInput
}
input LoginCredentialsInput {
usernameOrEmail: String!
password: String!
}Melakukan query pada meta value
Melakukan query pada meta value di WordPress bisa rumit, dengan kombinasi input yang dapat saling bertentangan, seperti yang dijelaskan dalam dokumentasinya:
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 '='.
Dokumentasi menjelaskan bahwa value dapat berupa string atau array, dan bergantung pada nilai ini, maka compare dapat menerima satu kumpulan nilai atau yang lain (seperti IN hanya untuk array, LIKE hanya untuk string). Selain itu, value bersifat wajib, tetapi hanya jika compare tidak menerima EXISTS, dalam hal ini value sama sekali tidak diperlukan.
Menganalisis kumpulan input yang berbeda, kita akan menemukan bahwa ada 4 kombinasi yang mungkin, tergantung pada perbandingan yang diterapkan pada kunci atau nilai, dan tipe nilai:
keynumericValuestringValuearrayValue
Input object oneOf MetaQueryCompareByInput menangani 4 input ini, dibantu oleh berbagai Enum yang mendefinisikan operator yang mungkin digunakan oleh setiap input. Kemudian, dengan memfilter berdasarkan numericValue kita dapat menggunakan operator GREATER_THAN, berdasarkan arrayValue kita dapat menggunakan operator IN, dan berdasarkan key kita dapat menggunakan operator EXISTS (dan tidak perlu menyediakan value).
Skema GraphQL yang dihasilkan (menggunakan SDL) adalah sebagai berikut:
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
}Dengan cara ini, dengan memilih input apa yang digunakan di bawah compareBy, kebenaran keseluruhan kumpulan data input akan divalidasi oleh GraphQL. Sekarang, saat memfilter post di mana beberapa meta key ada, kita tidak dapat menyediakan value:
{
posts(filter: {
metaQuery: {
key: "_thumbnail_id",
compareBy:{
key: {
operator: EXISTS
}
}
}
}) {
id
title
metaValue(key: "_thumbnail_id")
}
}Untuk memfilter post yang "disukai" oleh pengguna tertentu, kita menggunakan input arrayValue, dan memilih operator IN:
query FilterPostsLikedByUser($userID: ID!) {
posts(filter: {
metaQuery: {
key: "liked_by_users",
compareBy:{
arrayValue: {
value: $userID
operator: IN
}
}
}
}) {
id
title
}
}Introspeksi: mengetahui apakah suatu tipe adalah Input Object "oneOf"
Kita dapat mengetahui apakah suatu tipe adalah Input Object "oneOf" melalui field introspeksi isOneOf:
query IsOneOfInputObject {
__schema {
types {
name
isOneOf
}
}
}