Skip to content

Commit

Permalink
FIX: Uphold spec for non-validation names not beginning with __
Browse files Browse the repository at this point in the history
The spec describing introspection (http://facebook.github.io/graphql/#sec-Naming-conventions) restricts naming non-introspection related artifacts starting with __. This enforces that specification.
  • Loading branch information
leebyron committed Nov 22, 2016
1 parent 9d359b8 commit 379a308
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 17 deletions.
11 changes: 11 additions & 0 deletions src/type/__tests__/validation-test.js
Expand Up @@ -341,6 +341,17 @@ describe('Type System: Objects must have fields', () => {
);
});

it('rejects an Object type with reserved named fields', () => {
expect(
() => schemaWithFieldType(new GraphQLObjectType({
name: 'SomeObject',
fields: { __notPartOfIntrospection: { type: GraphQLString } }
}))
).to.throw(
'Name "__notPartOfIntrospection" must not begin with "__", which is reserved by GraphQL introspection.'
);
});

it('rejects an Object type with incorrectly typed fields', () => {
expect(
() => schemaWithFieldType(new GraphQLObjectType({
Expand Down
13 changes: 5 additions & 8 deletions src/type/definition.js
Expand Up @@ -291,7 +291,6 @@ export class GraphQLScalarType {
_scalarConfig: GraphQLScalarTypeConfig<*, *>;

constructor(config: GraphQLScalarTypeConfig<*, *>) {
invariant(config.name, 'Type must be named.');
assertValidName(config.name);
this.name = config.name;
this.description = config.description;
Expand Down Expand Up @@ -400,8 +399,7 @@ export class GraphQLObjectType {
_interfaces: Array<GraphQLInterfaceType>;

constructor(config: GraphQLObjectTypeConfig<*, *>) {
invariant(config.name, 'Type must be named.');
assertValidName(config.name);
assertValidName(config.name, config.isIntrospection);
this.name = config.name;
this.description = config.description;
if (config.isTypeOf) {
Expand Down Expand Up @@ -547,7 +545,8 @@ export type GraphQLObjectTypeConfig<TSource, TContext> = {
interfaces?: Thunk<?Array<GraphQLInterfaceType>>;
fields: Thunk<GraphQLFieldConfigMap<TSource, TContext>>;
isTypeOf?: ?GraphQLIsTypeOfFn<TSource, TContext>;
description?: ?string
description?: ?string;
isIntrospection?: boolean;
};

export type GraphQLTypeResolver<TSource, TContext> = (
Expand Down Expand Up @@ -656,7 +655,6 @@ export class GraphQLInterfaceType {
_fields: GraphQLFieldMap<*, *>;

constructor(config: GraphQLInterfaceTypeConfig<*, *>) {
invariant(config.name, 'Type must be named.');
assertValidName(config.name);
this.name = config.name;
this.description = config.description;
Expand Down Expand Up @@ -735,7 +733,6 @@ export class GraphQLUnionType {
_possibleTypeNames: {[typeName: string]: boolean};

constructor(config: GraphQLUnionTypeConfig<*, *>) {
invariant(config.name, 'Type must be named.');
assertValidName(config.name);
this.name = config.name;
this.description = config.description;
Expand Down Expand Up @@ -845,7 +842,7 @@ export class GraphQLEnumType/* <T> */ {

constructor(config: GraphQLEnumTypeConfig/* <T> */) {
this.name = config.name;
assertValidName(config.name);
assertValidName(config.name, config.isIntrospection);
this.description = config.description;
this._values = defineEnumValues(this, config.values);
this._enumConfig = config;
Expand Down Expand Up @@ -953,6 +950,7 @@ export type GraphQLEnumTypeConfig/* <T> */ = {
name: string;
values: GraphQLEnumValueConfigMap/* <T> */;
description?: ?string;
isIntrospection?: boolean;
};

export type GraphQLEnumValueConfigMap/* <T> */ = {
Expand Down Expand Up @@ -1003,7 +1001,6 @@ export class GraphQLInputObjectType {
_fields: GraphQLInputFieldMap;

constructor(config: GraphQLInputObjectTypeConfig) {
invariant(config.name, 'Type must be named.');
assertValidName(config.name);
this.name = config.name;
this.description = config.description;
Expand Down
8 changes: 8 additions & 0 deletions src/type/introspection.js
Expand Up @@ -28,6 +28,7 @@ import type { GraphQLField } from './definition';

export const __Schema = new GraphQLObjectType({
name: '__Schema',
isIntrospection: true,
description:
'A GraphQL Schema defines the capabilities of a GraphQL server. It ' +
'exposes all available types and directives on the server, as well as ' +
Expand Down Expand Up @@ -69,6 +70,7 @@ export const __Schema = new GraphQLObjectType({

export const __Directive = new GraphQLObjectType({
name: '__Directive',
isIntrospection: true,
description:
'A Directive provides a way to describe alternate runtime execution and ' +
'type validation behavior in a GraphQL document.' +
Expand Down Expand Up @@ -117,6 +119,7 @@ export const __Directive = new GraphQLObjectType({

export const __DirectiveLocation = new GraphQLEnumType({
name: '__DirectiveLocation',
isIntrospection: true,
description:
'A Directive can be adjacent to many parts of the GraphQL language, a ' +
'__DirectiveLocation describes one such possible adjacencies.',
Expand Down Expand Up @@ -198,6 +201,7 @@ export const __DirectiveLocation = new GraphQLEnumType({

export const __Type = new GraphQLObjectType({
name: '__Type',
isIntrospection: true,
description:
'The fundamental unit of any GraphQL Schema is the type. There are ' +
'many kinds of types in GraphQL as represented by the `__TypeKind` enum.' +
Expand Down Expand Up @@ -299,6 +303,7 @@ export const __Type = new GraphQLObjectType({

export const __Field = new GraphQLObjectType({
name: '__Field',
isIntrospection: true,
description:
'Object and Interface types are described by a list of Fields, each of ' +
'which has a name, potentially a list of arguments, and a return type.',
Expand All @@ -320,6 +325,7 @@ export const __Field = new GraphQLObjectType({

export const __InputValue = new GraphQLObjectType({
name: '__InputValue',
isIntrospection: true,
description:
'Arguments provided to Fields or Directives and the input fields of an ' +
'InputObject are represented as Input Values which describe their type ' +
Expand All @@ -342,6 +348,7 @@ export const __InputValue = new GraphQLObjectType({

export const __EnumValue = new GraphQLObjectType({
name: '__EnumValue',
isIntrospection: true,
description:
'One possible value for a given Enum. Enum values are unique values, not ' +
'a placeholder for a string or numeric value. However an Enum value is ' +
Expand Down Expand Up @@ -369,6 +376,7 @@ export const TypeKind = {

export const __TypeKind = new GraphQLEnumType({
name: '__TypeKind',
isIntrospection: true,
description: 'An enum describing what kind of type a given `__Type` is.',
values: {
SCALAR: {
Expand Down
32 changes: 23 additions & 9 deletions src/utilities/assertValidName.js
Expand Up @@ -8,15 +8,29 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/

import invariant from '../jsutils/invariant';


const NAME_RX = /^[_a-zA-Z][_a-zA-Z0-9]*$/;

// Helper to assert that provided names are valid.
export function assertValidName(name: string): void {
invariant(
NAME_RX.test(name),
`Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "${name}" does not.`
);
/**
* Upholds the spec rules about naming.
*/
export function assertValidName(
name: string,
isIntrospection?: boolean
): void {
if (!name || typeof name !== 'string') {
throw new Error(
`Must be named. Unexpected name: ${name}.`
);
}
if (!isIntrospection && name.slice(0, 2) === '__') {
throw new Error(
`Name "${name}" must not begin with "__", which is reserved by ` +
'GraphQL introspection.'
);
}
if (!NAME_RX.test(name)) {
throw new Error(
`Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but "${name}" does not.`
);
}
}

0 comments on commit 379a308

Please sign in to comment.