Mở rộng schema
Mở rộng schemaInput Object 'oneOf'

Input Object 'oneOf'

Input object oneOf là một loại input object đặc biệt, trong đó chính xác một trong các trường input phải được cung cấp, nếu không server sẽ trả về lỗi xác thực. Hành vi này đưa tính đa hình vào các input trong GraphQL, giúp chúng ta thiết kế schema gọn gàng hơn.

Ví dụ, việc truy xuất một người dùng trong ứng dụng có thể thực hiện theo các thuộc tính khác nhau, chẳng hạn như ID người dùng hoặc email. Để làm điều này, thông thường chúng ta cần tạo một field riêng cho mỗi thuộc tính:

type Query {
  userByID(id: ID!): User
  userByEmail(email: String!): User
}

Nhờ input object oneOf, thay vào đó chúng ta có thể có một field duy nhất là user chấp nhận tất cả các thuộc tính thông qua input object oneOf UserByInput, biết rằng chỉ một trong các thuộc tính (ID hoặc email) có thể và phải được cung cấp:

type Query {
  user(by: UserByInput!): User
}
 
input UserByInput @oneOf {
  id: ID
  email: String
}

(Lưu ý rằng cú pháp @oneOf ở trên chỉ dùng cho mục đích tài liệu trong ngữ cảnh của Gato GraphQL, vì chúng ta không cần sử dụng SDL —Schema Definition Language— để tạo schema; plugin đã tạo schema thông qua mã PHP, sử dụng các input từ Cấu hình Schema.)

Trong query, chúng ta cung cấp giá trị input cho chính xác một trong các thuộc tính:

{
  tom: user(by: {
    id: 1
  }) {
    name
  }
 
  jerry: user(by: {
    email: "jerry@warnerbros.com"
  }) {
    name
  }
}

Nếu chúng ta cung cấp hai (hoặc nhiều hơn) giá trị cho input:

{
  user(by: {
    id: 1
    email: "jerry@warnerbros.com"
  }) {
    name
  }
}

... thì server sẽ trả về lỗi:

{
  "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 sử dụng input object oneOf như thế nào

Hãy xem một số tình huống mà plugin sử dụng tính năng này, và chúng ta cũng có thể dùng để mở rộng các schema GraphQL của mình.

Chọn một thực thể duy nhất theo các thuộc tính khác nhau

Đây là trường hợp tổng quát cho query được minh họa ở trên, liên quan đến input UserByInput trong field user.

Bất cứ khi nào chúng ta cần lấy một thực thể duy nhất (một User, Post, PostTag, v.v.) có thể được xác định duy nhất bằng nhiều hơn một thuộc tính (chẳng hạn theo ID hoặc email, ID hoặc slug, v.v.), chúng ta có thể định nghĩa tất cả các thuộc tính khác nhau vào một input object oneOf, và gom tất cả các field khác nhau để lấy thực thể đó vào một field duy nhất.

Chấp nhận các tập dữ liệu khác nhau trong mutation

Khi thực hiện mutation, chúng ta có thể chấp nhận các tập dữ liệu khác nhau làm input. Thay vì phải đưa ra các field mutation riêng biệt cho từng tập dữ liệu khác nhau, bằng cách sử dụng input object oneOf, một field mutation duy nhất có thể xử lý tất cả các khả năng.

Ví dụ, mutation loginUser có thể hỗ trợ đăng nhập người dùng bằng nhiều phương thức khác nhau: username/password, JWT token, application passwords, hoặc các phương thức khác. Đó là lý do mutation này nhận Input Object oneOf LoginUserByInput, hiện tại chấp nhận xác thực username/password chuẩn của WordPress, nhưng cũng có thể được mở rộng để hỗ trợ các phương thức khác:

type Mutation {
  loginUser(by: LoginUserByInput!): RootLoginUserMutationPayload!
}
 
input LoginUserByInput @oneOf {
  credentials: LoginCredentialsInput
}
 
input LoginCredentialsInput {
  usernameOrEmail: String!
  password: String!
}

Truy vấn các meta value

Truy vấn các meta value trong WordPress có thể phức tạp, với các tổ hợp input có thể xung đột với nhau, như được giải thích trong tài liệu của nó:

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 '='.

Tài liệu giải thích rằng value có thể là một chuỗi hoặc một mảng, và tùy thuộc vào giá trị này, compare có thể chấp nhận một tập giá trị hoặc một tập khác (chẳng hạn IN chỉ dành cho mảng, LIKE chỉ dành cho chuỗi). Ngoài ra, value là bắt buộc, nhưng chỉ khi compare không nhận EXISTS, trong trường hợp đó value hoàn toàn không cần thiết.

Phân tích các tập input khác nhau, chúng ta sẽ nhận ra rằng có 4 tổ hợp có thể xảy ra, tùy thuộc vào phép so sánh được áp dụng trên key hay value, và kiểu dữ liệu của value:

  • key
  • numericValue
  • stringValue
  • arrayValue

Input object oneOf MetaQueryCompareByInput xử lý 4 input này, được hỗ trợ bởi các Enum khác nhau định nghĩa các toán tử có thể mà mỗi input có thể sử dụng. Như vậy, khi lọc theo numericValue chúng ta có thể dùng toán tử GREATER_THAN, theo arrayValue có thể dùng toán tử IN, và theo key có thể dùng toán tử EXISTS (và không cần cung cấp value).

Schema GraphQL kết quả (sử dụng SDL) như sau:

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
}

Bằng cách này, khi chọn input nào để dùng dưới compareBy, tính đúng đắn của toàn bộ tập dữ liệu input sẽ được GraphQL xác thực. Bây giờ, khi lọc các bài viết mà một meta key nào đó tồn tại, chúng ta không thể cung cấp value:

{
  posts(filter: {
    metaQuery: {
      key: "_thumbnail_id",
      compareBy:{
        key: {
          operator: EXISTS
        }
      }
    }
  }) {
    id
    title
    metaValue(key: "_thumbnail_id")
  }
}

Để lọc các bài viết được "thích" bởi một người dùng nào đó, chúng ta sử dụng input arrayValue và chọn toán tử IN:

query FilterPostsLikedByUser($userID: ID!) {
  posts(filter: {
    metaQuery: {
      key: "liked_by_users",
      compareBy:{
        arrayValue: {
          value: $userID
          operator: IN
        }
      }
    }
  }) {
    id
    title
  }
}

Introspection: tìm hiểu xem một kiểu có phải là Input Object "oneOf" không

Chúng ta có thể tìm hiểu xem một kiểu có phải là Input Object "oneOf" hay không thông qua trường introspection isOneOf:

query IsOneOfInputObject {
  __schema {
    types {
      name
      isOneOf
    }
  }
}