Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Experimental support for semantic-non-null #4192

Draft
wants to merge 18 commits into
base: 16.x.x
Choose a base branch
from
1 change: 1 addition & 0 deletions src/__tests__/starWarsIntrospection-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ describe('Star Wars Introspection Tests', () => {
{ name: '__TypeKind' },
{ name: '__Field' },
{ name: '__InputValue' },
{ name: '__TypeNullability' },
{ name: '__EnumValue' },
{ name: '__Directive' },
{ name: '__DirectiveLocation' },
Expand Down
2 changes: 2 additions & 0 deletions src/execution/__tests__/executor-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ describe('Execute: Handles basic execution tasks', () => {
'rootValue',
'operation',
'variableValues',
'errorPropagation',
);

const operation = document.definitions[0];
Expand All @@ -275,6 +276,7 @@ describe('Execute: Handles basic execution tasks', () => {
schema,
rootValue,
operation,
errorPropagation: true,
});

const field = operation.selectionSet.selections[0];
Expand Down
32 changes: 31 additions & 1 deletion src/execution/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
isListType,
isNonNullType,
isObjectType,
isSemanticNonNullType,
} from '../type/definition';
import {
SchemaMetaFieldDef,
Expand Down Expand Up @@ -115,6 +116,7 @@
typeResolver: GraphQLTypeResolver<any, any>;
subscribeFieldResolver: GraphQLFieldResolver<any, any>;
errors: Array<GraphQLError>;
errorPropagation: boolean;
}

/**
Expand Down Expand Up @@ -152,6 +154,12 @@
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
/**
* Set to `false` to disable error propagation. Experimental.
*
* @experimental
*/
errorPropagation?: boolean;
}

/**
Expand Down Expand Up @@ -286,6 +294,7 @@
fieldResolver,
typeResolver,
subscribeFieldResolver,
errorPropagation,
} = args;

let operation: OperationDefinitionNode | undefined;
Expand Down Expand Up @@ -347,6 +356,7 @@
typeResolver: typeResolver ?? defaultTypeResolver,
subscribeFieldResolver: subscribeFieldResolver ?? defaultFieldResolver,
errors: [],
errorPropagation: errorPropagation ?? true,
};
}

Expand Down Expand Up @@ -585,6 +595,7 @@
rootValue: exeContext.rootValue,
operation: exeContext.operation,
variableValues: exeContext.variableValues,
errorPropagation: exeContext.errorPropagation,
};
}

Expand All @@ -595,7 +606,7 @@
): null {
// If the field type is non-nullable, then it is resolved without any
// protection from errors, however it still properly locates the error.
if (isNonNullType(returnType)) {
if (exeContext.errorPropagation && isNonNullType(returnType)) {
throw error;
}

Expand Down Expand Up @@ -658,6 +669,25 @@
return completed;
}

// If field type is SemanticNonNull, complete for inner type, and throw field error
// if result is null.
if (isSemanticNonNullType(returnType)) {
const completed = completeValue(
exeContext,
returnType.ofType,
fieldNodes,
info,
path,
result,
);
if (completed === null) {
throw new Error(
`Cannot return null for semantic-non-nullable field ${info.parentType.name}.${info.fieldName}.`,
);
}
return completed;
}

Check warning on line 689 in src/execution/execute.ts

View check run for this annotation

Codecov / codecov/patch

src/execution/execute.ts#L675-L689

Added lines #L675 - L689 were not covered by tests

// If result value is null or undefined then return null.
if (result == null) {
return null;
Expand Down
8 changes: 8 additions & 0 deletions src/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ export interface GraphQLArgs {
operationName?: Maybe<string>;
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
/**
* Set to `false` to disable error propagation. Experimental.
*
* @experimental
*/
errorPropagation?: boolean;
}

export function graphql(args: GraphQLArgs): Promise<ExecutionResult> {
Expand Down Expand Up @@ -106,6 +112,7 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
operationName,
fieldResolver,
typeResolver,
errorPropagation,
} = args;

// Validate Schema
Expand Down Expand Up @@ -138,5 +145,6 @@ function graphqlImpl(args: GraphQLArgs): PromiseOrValue<ExecutionResult> {
operationName,
fieldResolver,
typeResolver,
errorPropagation,
});
}
6 changes: 6 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export {
GraphQLInputObjectType,
GraphQLList,
GraphQLNonNull,
GraphQLSemanticNonNull,
// Standard GraphQL Scalars
specifiedScalarTypes,
GraphQLInt,
Expand All @@ -74,6 +75,7 @@ export {
__Schema,
__Directive,
__DirectiveLocation,
__TypeNullability,
__Type,
__Field,
__InputValue,
Expand All @@ -95,6 +97,7 @@ export {
isInputObjectType,
isListType,
isNonNullType,
isSemanticNonNullType,
isInputType,
isOutputType,
isLeafType,
Expand All @@ -120,6 +123,7 @@ export {
assertInputObjectType,
assertListType,
assertNonNullType,
assertSemanticNonNullType,
assertInputType,
assertOutputType,
assertLeafType,
Expand Down Expand Up @@ -287,6 +291,7 @@ export type {
NamedTypeNode,
ListTypeNode,
NonNullTypeNode,
SemanticNonNullTypeNode,
TypeSystemDefinitionNode,
SchemaDefinitionNode,
OperationTypeDefinitionNode,
Expand Down Expand Up @@ -480,6 +485,7 @@ export type {
IntrospectionNamedTypeRef,
IntrospectionListTypeRef,
IntrospectionNonNullTypeRef,
IntrospectionSemanticNonNullTypeRef,
IntrospectionField,
IntrospectionInputValue,
IntrospectionEnumValue,
Expand Down
17 changes: 17 additions & 0 deletions src/language/__tests__/parser-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,23 @@ describe('Parser', () => {
});
});

it('parses semantic-non-null types', () => {
const result = parseType('MyType*');
expectJSON(result).toDeepEqual({
kind: Kind.SEMANTIC_NON_NULL_TYPE,
loc: { start: 0, end: 7 },
type: {
kind: Kind.NAMED_TYPE,
loc: { start: 0, end: 6 },
name: {
kind: Kind.NAME,
loc: { start: 0, end: 6 },
value: 'MyType',
},
},
});
});

it('parses nested types', () => {
const result = parseType('[MyType!]');
expectJSON(result).toDeepEqual({
Expand Down
1 change: 1 addition & 0 deletions src/language/__tests__/predicates-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ describe('AST node predicates', () => {
'NamedType',
'ListType',
'NonNullType',
'SemanticNonNullType',
]);
});

Expand Down
14 changes: 13 additions & 1 deletion src/language/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export type ASTNode =
| NamedTypeNode
| ListTypeNode
| NonNullTypeNode
| SemanticNonNullTypeNode
| SchemaDefinitionNode
| OperationTypeDefinitionNode
| ScalarTypeDefinitionNode
Expand Down Expand Up @@ -235,6 +236,7 @@ export const QueryDocumentKeys: {
NamedType: ['name'],
ListType: ['type'],
NonNullType: ['type'],
SemanticNonNullType: ['type'],

SchemaDefinition: ['description', 'directives', 'operationTypes'],
OperationTypeDefinition: ['type'],
Expand Down Expand Up @@ -520,7 +522,11 @@ export interface ConstDirectiveNode {

/** Type Reference */

export type TypeNode = NamedTypeNode | ListTypeNode | NonNullTypeNode;
export type TypeNode =
| NamedTypeNode
| ListTypeNode
| NonNullTypeNode
| SemanticNonNullTypeNode;

export interface NamedTypeNode {
readonly kind: Kind.NAMED_TYPE;
Expand All @@ -540,6 +546,12 @@ export interface NonNullTypeNode {
readonly type: NamedTypeNode | ListTypeNode;
}

export interface SemanticNonNullTypeNode {
readonly kind: Kind.SEMANTIC_NON_NULL_TYPE;
readonly loc?: Location;
readonly type: NamedTypeNode | ListTypeNode;
}

/** Type System Definition */

export type TypeSystemDefinitionNode =
Expand Down
1 change: 1 addition & 0 deletions src/language/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export type {
NamedTypeNode,
ListTypeNode,
NonNullTypeNode,
SemanticNonNullTypeNode,
TypeSystemDefinitionNode,
SchemaDefinitionNode,
OperationTypeDefinitionNode,
Expand Down
1 change: 1 addition & 0 deletions src/language/kinds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ enum Kind {
NAMED_TYPE = 'NamedType',
LIST_TYPE = 'ListType',
NON_NULL_TYPE = 'NonNullType',
SEMANTIC_NON_NULL_TYPE = 'SemanticNonNullType',

/** Type System Definitions */
SCHEMA_DEFINITION = 'SchemaDefinition',
Expand Down
5 changes: 4 additions & 1 deletion src/language/lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export class Lexer {
export function isPunctuatorTokenKind(kind: TokenKind): boolean {
return (
kind === TokenKind.BANG ||
kind === TokenKind.ASTERISK ||
kind === TokenKind.DOLLAR ||
kind === TokenKind.AMP ||
kind === TokenKind.PAREN_L ||
Expand Down Expand Up @@ -246,7 +247,7 @@ function readNextToken(lexer: Lexer, start: number): Token {
// - FloatValue
// - StringValue
//
// Punctuator :: one of ! $ & ( ) ... : = @ [ ] { | }
// Punctuator :: one of ! $ & ( ) * ... : = @ [ ] { | }
case 0x0021: // !
return createToken(lexer, TokenKind.BANG, position, position + 1);
case 0x0024: // $
Expand All @@ -257,6 +258,8 @@ function readNextToken(lexer: Lexer, start: number): Token {
return createToken(lexer, TokenKind.PAREN_L, position, position + 1);
case 0x0029: // )
return createToken(lexer, TokenKind.PAREN_R, position, position + 1);
case 0x002a: // *
return createToken(lexer, TokenKind.ASTERISK, position, position + 1);
case 0x002e: // .
if (
body.charCodeAt(position + 1) === 0x002e &&
Expand Down
8 changes: 8 additions & 0 deletions src/language/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import type {
SchemaExtensionNode,
SelectionNode,
SelectionSetNode,
SemanticNonNullTypeNode,
StringValueNode,
Token,
TypeNode,
Expand Down Expand Up @@ -740,6 +741,7 @@ export class Parser {
* - NamedType
* - ListType
* - NonNullType
* - SemanticNonNullType
*/
parseTypeReference(): TypeNode {
const start = this._lexer.token;
Expand All @@ -761,6 +763,12 @@ export class Parser {
type,
});
}
if (this.expectOptionalToken(TokenKind.ASTERISK)) {
return this.node<SemanticNonNullTypeNode>(start, {
kind: Kind.SEMANTIC_NON_NULL_TYPE,
type,
});
}

return type;
}
Expand Down
3 changes: 2 additions & 1 deletion src/language/predicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ export function isTypeNode(node: ASTNode): node is TypeNode {
return (
node.kind === Kind.NAMED_TYPE ||
node.kind === Kind.LIST_TYPE ||
node.kind === Kind.NON_NULL_TYPE
node.kind === Kind.NON_NULL_TYPE ||
node.kind === Kind.SEMANTIC_NON_NULL_TYPE
);
}

Expand Down
1 change: 1 addition & 0 deletions src/language/printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ const printDocASTReducer: ASTReducer<string> = {
NamedType: { leave: ({ name }) => name },
ListType: { leave: ({ type }) => '[' + type + ']' },
NonNullType: { leave: ({ type }) => type + '!' },
SemanticNonNullType: { leave: ({ type }) => type + '*' },

// Type System Definitions

Expand Down
1 change: 1 addition & 0 deletions src/language/tokenKind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ enum TokenKind {
SOF = '<SOF>',
EOF = '<EOF>',
BANG = '!',
ASTERISK = '*',
DOLLAR = '$',
AMP = '&',
PAREN_L = '(',
Expand Down
Loading
Loading