スキーマ拡張
スキーマ拡張'oneOf' Input Object

'oneOf' Input Object

oneOf input objectは特定の種類のinput objectで、入力フィールドのうち正確に1つだけを入力として提供する必要があります。それ以外の場合、サーバーは検証エラーを返します。この動作により、GraphQLの入力にポリモーフィズムが導入され、よりすっきりとしたスキーマを設計できるようになります。

たとえば、アプリケーションでユーザーを取得するには、ユーザーIDやメールアドレスなど、異なるプロパティを使用することができます。これを行うには、通常、各プロパティに対して個別のフィールドを作成する必要があります。

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

oneOf input objectを使用することで、代わりに単一のフィールド user を持ち、UserByInput oneOf input objectを通じてすべてのプロパティを受け付けることができます。このとき、プロパティ(IDまたはメールアドレス)のうち1つだけが提供でき、かつ提供しなければならないことが保証されます。

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

(上記の @oneOf 構文は、Gato GraphQLのコンテキスト内でのドキュメント目的のみであることに注意してください。スキーマを生成するためにSDL(Schema Definition Language)を使用する必要はありません。プラグインは、Schema Configurationの入力を使用したPHPコードによってスキーマを生成します。)

クエリでは、プロパティのうち正確に1つの入力値を提供します。

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

inputに2つ(またはそれ以上)の値を提供した場合:

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

…サーバーはエラーを返します。

{
  "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におけるoneOf input objectの活用方法

プラグインがこの機能を使用するいくつかの場面と、GraphQLスキーマを拡張するために私たちも使用できる方法を見てみましょう。

異なるプロパティによる単一エンティティの選択

これは、フィールド user における input UserByInput に関して上記で示したクエリの一般的なケースです。

複数のプロパティ(IDやメールアドレス、IDやスラッグなど)によって一意に識別できる単一のエンティティ(単一の UserPostPostTag など)を取得する必要がある場合、すべての異なるプロパティをoneOf input objectに定義し、そのエンティティを取得するための異なるフィールドをすべて単一のフィールドに集約することができます。

ミューテーションで異なるデータセットを受け付ける

ミューテーションを行う際、入力として異なるデータセットを受け付けることがあります。異なるデータセットごとに別々のミューテーションフィールドを公開する代わりに、oneOf input objectを使用することで、単一のミューテーションフィールドがすべての可能性に対応できます。

たとえば、ミューテーション loginUser は、ユーザー名/パスワード、JWTトークン、アプリケーションパスワードなど、さまざまな方法でユーザーのログインをサポートできます。そのため、このミューテーションはoneOf Input Object LoginUserByInput を受け取ります。これは現在、WordPressの標準的なユーザー名/パスワード認証を受け付けますが、他の方法にも拡張できます。

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

メタ値のクエリ

WordPressでメタ値をクエリすることは複雑で、互いに競合する可能性のある入力の組み合わせが存在します。ドキュメントで説明されているように

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

ドキュメントでは、value は文字列または配列のいずれかであり、この値に応じて compare が受け付けられる値のセットが変わること(IN は配列のみ、LIKE は文字列のみなど)が説明されています。また、value は必須ですが、compareEXISTS を受け取る場合に限り value は不要となります。

異なる入力セットを分析すると、キーまたは値に適用される比較、および値の型に応じて、4つの可能な組み合わせがあることがわかります。

  • key
  • numericValue
  • stringValue
  • arrayValue

oneOf input object MetaQueryCompareByInput は、各inputが使用できる演算子を定義する異なるEnumの助けを借りて、これら4つの入力を処理します。numericValue でフィルタリングする場合は演算子 GREATER_THAN を、arrayValue でフィルタリングする場合は演算子 IN を、key でフィルタリングする場合は演算子 EXISTS を使用できます(この場合、value を提供する必要はありません)。

結果として得られるGraphQLスキーマ(SDLを使用)は次のとおりです。

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
}

このように、compareBy で使用するinputを選択することで、GraphQLによって全体的な入力データセットの正確性が検証されます。これで、メタキーが存在する投稿をフィルタリングする際に value を提供することができません。

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

あるユーザーが「いいね」した投稿をフィルタリングするには、input arrayValue を使用し、演算子 IN を選択します。

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

イントロスペクション:型が「oneOf」Input Objectであるかどうかの確認

イントロスペクションフィールド isOneOf を使用することで、型が「oneOf」Input Objectであるかどうかを確認できます。

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