Skip to content

Commit 534b243

Browse files
committed
Add __Type.oneOf
This exposes a new boolean `oneOf` field on `__Type` that indicates if a type is `oneOf`. We implement by checking if the AST node for the object contains a `@oneOf` directive.
1 parent 99189d9 commit 534b243

File tree

5 files changed

+119
-0
lines changed

5 files changed

+119
-0
lines changed

src/type/__tests__/introspection-test.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,17 @@ describe('Introspection', () => {
372372
isDeprecated: false,
373373
deprecationReason: null,
374374
},
375+
{
376+
name: 'oneOf',
377+
args: [],
378+
type: {
379+
kind: 'SCALAR',
380+
name: 'Boolean',
381+
ofType: null,
382+
},
383+
isDeprecated: false,
384+
deprecationReason: null,
385+
},
375386
],
376387
inputFields: null,
377388
interfaces: [],
@@ -1525,6 +1536,84 @@ describe('Introspection', () => {
15251536
});
15261537
});
15271538

1539+
it('identifies oneOf for objects', () => {
1540+
const schema = buildSchema(`
1541+
type SomeObject @oneOf {
1542+
a: String
1543+
}
1544+
1545+
type AnotherObject {
1546+
a: String
1547+
b: String
1548+
}
1549+
1550+
type Query {
1551+
someField: String
1552+
}
1553+
`);
1554+
1555+
const source = `
1556+
{
1557+
a: __type(name: "SomeObject") {
1558+
oneOf
1559+
}
1560+
b: __type(name: "AnotherObject") {
1561+
oneOf
1562+
}
1563+
}
1564+
`;
1565+
1566+
expect(graphqlSync({ schema, source })).to.deep.equal({
1567+
data: {
1568+
a: {
1569+
oneOf: true,
1570+
},
1571+
b: {
1572+
oneOf: false,
1573+
},
1574+
},
1575+
});
1576+
});
1577+
1578+
it('identifies oneOf for input objects', () => {
1579+
const schema = buildSchema(`
1580+
input SomeInputObject @oneOf {
1581+
a: String
1582+
}
1583+
1584+
input AnotherInputObject {
1585+
a: String
1586+
b: String
1587+
}
1588+
1589+
type Query {
1590+
someField(someArg: SomeInputObject): String
1591+
}
1592+
`);
1593+
1594+
const source = `
1595+
{
1596+
a: __type(name: "SomeInputObject") {
1597+
oneOf
1598+
}
1599+
b: __type(name: "AnotherInputObject") {
1600+
oneOf
1601+
}
1602+
}
1603+
`;
1604+
1605+
expect(graphqlSync({ schema, source })).to.deep.equal({
1606+
data: {
1607+
a: {
1608+
oneOf: true,
1609+
},
1610+
b: {
1611+
oneOf: false,
1612+
},
1613+
},
1614+
});
1615+
});
1616+
15281617
it('fails as expected on the __type root field without an arg', () => {
15291618
const schema = buildSchema(`
15301619
type Query {

src/type/definition.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,7 @@ export class GraphQLObjectType<TSource = any, TContext = any> {
763763
extensions: Readonly<GraphQLObjectTypeExtensions<TSource, TContext>>;
764764
astNode: Maybe<ObjectTypeDefinitionNode>;
765765
extensionASTNodes: ReadonlyArray<ObjectTypeExtensionNode>;
766+
isOneOf: boolean;
766767

767768
private _fields: ThunkObjMap<GraphQLField<TSource, TContext>>;
768769
private _interfaces: ThunkReadonlyArray<GraphQLInterfaceType>;
@@ -774,6 +775,7 @@ export class GraphQLObjectType<TSource = any, TContext = any> {
774775
this.extensions = toObjMap(config.extensions);
775776
this.astNode = config.astNode;
776777
this.extensionASTNodes = config.extensionASTNodes ?? [];
778+
this.isOneOf = config.isOneOf ?? false;
777779

778780
this._fields = () => defineFieldMap(config);
779781
this._interfaces = () => defineInterfaces(config);
@@ -942,6 +944,7 @@ export interface GraphQLObjectTypeConfig<TSource, TContext> {
942944
extensions?: Maybe<Readonly<GraphQLObjectTypeExtensions<TSource, TContext>>>;
943945
astNode?: Maybe<ObjectTypeDefinitionNode>;
944946
extensionASTNodes?: Maybe<ReadonlyArray<ObjectTypeExtensionNode>>;
947+
isOneOf?: boolean;
945948
}
946949

947950
interface GraphQLObjectTypeNormalizedConfig<TSource, TContext>
@@ -1611,6 +1614,7 @@ export class GraphQLInputObjectType {
16111614
extensions: Readonly<GraphQLInputObjectTypeExtensions>;
16121615
astNode: Maybe<InputObjectTypeDefinitionNode>;
16131616
extensionASTNodes: ReadonlyArray<InputObjectTypeExtensionNode>;
1617+
isOneOf: boolean;
16141618

16151619
private _fields: ThunkObjMap<GraphQLInputField>;
16161620

@@ -1620,6 +1624,7 @@ export class GraphQLInputObjectType {
16201624
this.extensions = toObjMap(config.extensions);
16211625
this.astNode = config.astNode;
16221626
this.extensionASTNodes = config.extensionASTNodes ?? [];
1627+
this.isOneOf = config.isOneOf ?? false;
16231628

16241629
this._fields = defineInputFieldMap.bind(undefined, config);
16251630
}
@@ -1652,6 +1657,7 @@ export class GraphQLInputObjectType {
16521657
extensions: this.extensions,
16531658
astNode: this.astNode,
16541659
extensionASTNodes: this.extensionASTNodes,
1660+
isOneOf: this.isOneOf,
16551661
};
16561662
}
16571663

@@ -1697,6 +1703,7 @@ export interface GraphQLInputObjectTypeConfig {
16971703
extensions?: Maybe<Readonly<GraphQLInputObjectTypeExtensions>>;
16981704
astNode?: Maybe<InputObjectTypeDefinitionNode>;
16991705
extensionASTNodes?: Maybe<ReadonlyArray<InputObjectTypeExtensionNode>>;
1706+
isOneOf?: boolean;
17001707
}
17011708

17021709
interface GraphQLInputObjectTypeNormalizedConfig

src/type/introspection.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,16 @@ export const __Type: GraphQLObjectType = new GraphQLObjectType({
323323
type: __Type,
324324
resolve: (type) => ('ofType' in type ? type.ofType : undefined),
325325
},
326+
oneOf: {
327+
type: GraphQLBoolean,
328+
resolve: (type) => {
329+
if (isInputObjectType(type) || isObjectType(type)) {
330+
return type.isOneOf;
331+
}
332+
333+
return null;
334+
},
335+
},
326336
} as GraphQLFieldConfigMap<GraphQLType, unknown>),
327337
});
328338

src/utilities/__tests__/printSchema-test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,7 @@ describe('Type System Printer', () => {
703703
enumValues(includeDeprecated: Boolean = false): [__EnumValue!]
704704
inputFields(includeDeprecated: Boolean = false): [__InputValue!]
705705
ofType: __Type
706+
oneOf: Boolean
706707
}
707708
708709
"""An enum describing what kind of type a given \`__Type\` is."""

src/utilities/extendSchema.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import {
6666
import {
6767
GraphQLDeprecatedDirective,
6868
GraphQLDirective,
69+
GraphQLOneOfDirective,
6970
GraphQLSpecifiedByDirective,
7071
} from '../type/directives';
7172
import { introspectionTypes, isIntrospectionType } from '../type/introspection';
@@ -592,6 +593,7 @@ export function extendSchemaImpl(
592593
fields: () => buildFieldMap(allNodes),
593594
astNode,
594595
extensionASTNodes,
596+
isOneOf: isOneOf(astNode),
595597
});
596598
}
597599
case Kind.INTERFACE_TYPE_DEFINITION: {
@@ -646,6 +648,7 @@ export function extendSchemaImpl(
646648
fields: () => buildInputFieldMap(allNodes),
647649
astNode,
648650
extensionASTNodes,
651+
isOneOf: isOneOf(astNode),
649652
});
650653
}
651654
}
@@ -682,3 +685,12 @@ function getSpecifiedByURL(
682685
// @ts-expect-error validated by `getDirectiveValues`
683686
return specifiedBy?.url;
684687
}
688+
689+
/**
690+
* Given an input object node, returns if the node should be OneOf.
691+
*/
692+
function isOneOf(
693+
node: InputObjectTypeDefinitionNode | ObjectTypeDefinitionNode,
694+
): boolean {
695+
return Boolean(getDirectiveValues(GraphQLOneOfDirective, node));
696+
}

0 commit comments

Comments
 (0)