Skip to content

Commit dd3be40

Browse files
committed
Allow use of legacy names for schema validation
This offers an escape hatch for schema that are defined with legacy field names that were valid in older versions of the spec and no longer are via an explicit white-list. Fixes #1184
1 parent 87c1bc3 commit dd3be40

File tree

3 files changed

+67
-3
lines changed

3 files changed

+67
-3
lines changed

src/type/__tests__/validation-test.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,43 @@ describe('Type System: Objects must have fields', () => {
407407
},
408408
]);
409409
});
410+
411+
it('accepts an Object type with explicitly allowed legacy named fields', () => {
412+
const schemaBad = new GraphQLSchema({
413+
query: new GraphQLObjectType({
414+
name: 'Query',
415+
fields: { __badName: { type: GraphQLString } },
416+
}),
417+
});
418+
const schemaOk = new GraphQLSchema({
419+
query: new GraphQLObjectType({
420+
name: 'Query',
421+
fields: { __badName: { type: GraphQLString } },
422+
}),
423+
allowedLegacyNames: ['__badName'],
424+
});
425+
expect(validateSchema(schemaBad)).to.containSubset([
426+
{
427+
message:
428+
'Name "__badName" must not begin with "__", which is reserved by ' +
429+
'GraphQL introspection.',
430+
},
431+
]);
432+
expect(validateSchema(schemaOk)).to.deep.equal([]);
433+
});
434+
435+
it('throws with bad value for explicitly allowed legacy names', () => {
436+
expect(
437+
() =>
438+
new GraphQLSchema({
439+
query: new GraphQLObjectType({
440+
name: 'Query',
441+
fields: { __badName: { type: GraphQLString } },
442+
}),
443+
allowedLegacyNames: true,
444+
}),
445+
).to.throw('"allowedLegacyNames" must be Array if provided but got: true.');
446+
});
410447
});
411448

412449
describe('Type System: Fields args must be properly named', () => {

src/type/schema.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ export class GraphQLSchema {
8181
_possibleTypeMap: ?ObjMap<ObjMap<boolean>>;
8282
// Used as a cache for validateSchema().
8383
__validationErrors: ?$ReadOnlyArray<GraphQLError>;
84+
// Referenced by validateSchema().
85+
__allowedLegacyNames: ?$ReadOnlyArray<string>;
8486

8587
constructor(config: GraphQLSchemaConfig): void {
8688
// If this schema was built from a source known to be valid, then it may be
@@ -103,6 +105,12 @@ export class GraphQLSchema {
103105
'"directives" must be Array if provided but got: ' +
104106
`${String(config.directives)}.`,
105107
);
108+
invariant(
109+
!config.allowedLegacyNames || Array.isArray(config.allowedLegacyNames),
110+
'"allowedLegacyNames" must be Array if provided but got: ' +
111+
`${String(config.allowedLegacyNames)}.`,
112+
);
113+
this.__allowedLegacyNames = config.allowedLegacyNames;
106114
}
107115

108116
this._queryType = config.query;
@@ -228,6 +236,15 @@ type GraphQLSchemaConfig = {
228236
directives?: ?Array<GraphQLDirective>,
229237
astNode?: ?SchemaDefinitionNode,
230238
assumeValid?: boolean,
239+
/**
240+
* If provided, the schema will consider fields or types with names included
241+
* in this list valid, even if they do not adhere to the specification's
242+
* schema validation rules.
243+
*
244+
* This option is provided to ease adoption and may be removed in a future
245+
* major release.
246+
*/
247+
allowedLegacyNames?: ?Array<string>,
231248
};
232249

233250
function typeMapReducer(map: TypeMap, type: ?GraphQLType): TypeMap {

src/type/validate.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,17 @@ function validateName(
215215
context: SchemaValidationContext,
216216
node: { +name: string, +astNode: ?ASTNode },
217217
): void {
218+
// If a schema explicitly allows some legacy name which is no longer valid,
219+
// allow it to be assumed valid.
220+
if (
221+
context.schema.__allowedLegacyNames &&
222+
context.schema.__allowedLegacyNames.indexOf(node.name) !== -1
223+
) {
224+
return;
225+
}
218226
// Ensure names are valid, however introspection types opt out.
219227
const error = isValidNameError(node.name, node.astNode || undefined);
220-
if (error && !isIntrospectionType((node: any))) {
228+
if (error) {
221229
context.addError(error);
222230
}
223231
}
@@ -236,8 +244,10 @@ function validateTypes(context: SchemaValidationContext): void {
236244
return;
237245
}
238246

239-
// Ensure they are named correctly.
240-
validateName(context, type);
247+
// Ensure it is named correctly (excluding introspection types).
248+
if (!isIntrospectionType(type)) {
249+
validateName(context, type);
250+
}
241251

242252
if (isObjectType(type)) {
243253
// Ensure fields are valid

0 commit comments

Comments
 (0)