Skip to content

Commit b4f2955

Browse files
alloyleebyron
authored andcommitted
More allowed legacy names (#1235)
* Allow legacy names when building AST schema. * Always store allowed legacy names on schema. * Allow additional legacy names when extending a schema. * Standardise schema validation options. * Allow legacy names in client schemas. * Add test coverage for schema validation options. * Simplify value merge * Add test * Trailing space * Recycling this value means we need to be read-only strict
1 parent 14fde90 commit b4f2955

File tree

8 files changed

+165
-35
lines changed

8 files changed

+165
-35
lines changed

src/type/__tests__/schema-test.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,66 @@ describe('Type System: Schema', () => {
9595
expect(Schema.getTypeMap()).to.include.key('WrappedDirInput');
9696
});
9797
});
98+
99+
describe('Validity', () => {
100+
describe('when not assumed valid', () => {
101+
it('configures the schema to still needing validation', () => {
102+
expect(
103+
new GraphQLSchema({
104+
assumeValid: false,
105+
}).__validationErrors,
106+
).to.equal(undefined);
107+
});
108+
109+
it('configures the schema for allowed legacy names', () => {
110+
expect(
111+
new GraphQLSchema({
112+
allowedLegacyNames: ['__badName'],
113+
}).__allowedLegacyNames,
114+
).to.deep.equal(['__badName']);
115+
});
116+
117+
it('checks the configuration for mistakes', () => {
118+
expect(() => new GraphQLSchema(() => null)).to.throw();
119+
expect(() => new GraphQLSchema({ types: {} })).to.throw();
120+
expect(() => new GraphQLSchema({ directives: {} })).to.throw();
121+
expect(() => new GraphQLSchema({ allowedLegacyNames: {} })).to.throw();
122+
});
123+
});
124+
125+
describe('when assumed valid', () => {
126+
it('configures the schema to have no errors', () => {
127+
expect(
128+
new GraphQLSchema({
129+
assumeValid: true,
130+
}).__validationErrors,
131+
).to.deep.equal([]);
132+
});
133+
134+
it('still configures the schema for allowed legacy names', () => {
135+
expect(
136+
new GraphQLSchema({
137+
assumeValid: true,
138+
allowedLegacyNames: ['__badName'],
139+
}).__allowedLegacyNames,
140+
).to.deep.equal(['__badName']);
141+
});
142+
143+
it('does not check the configuration for mistakes', () => {
144+
expect(() => {
145+
const config = () => null;
146+
config.assumeValid = true;
147+
return new GraphQLSchema(config);
148+
}).to.not.throw();
149+
expect(() => {
150+
return new GraphQLSchema({
151+
assumeValid: true,
152+
types: {},
153+
directives: { reduce: () => [] },
154+
allowedLegacyNames: {},
155+
});
156+
}).to.not.throw();
157+
});
158+
});
159+
});
98160
});

src/type/schema.js

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,9 @@ export class GraphQLSchema {
111111
'"allowedLegacyNames" must be Array if provided but got: ' +
112112
`${String(config.allowedLegacyNames)}.`,
113113
);
114-
this.__allowedLegacyNames = config.allowedLegacyNames;
115114
}
116115

116+
this.__allowedLegacyNames = config.allowedLegacyNames;
117117
this._queryType = config.query;
118118
this._mutationType = config.mutation;
119119
this._subscriptionType = config.subscription;
@@ -229,14 +229,16 @@ export class GraphQLSchema {
229229

230230
type TypeMap = ObjMap<GraphQLNamedType>;
231231

232-
export type GraphQLSchemaConfig = {
233-
query?: ?GraphQLObjectType,
234-
mutation?: ?GraphQLObjectType,
235-
subscription?: ?GraphQLObjectType,
236-
types?: ?Array<GraphQLNamedType>,
237-
directives?: ?Array<GraphQLDirective>,
238-
astNode?: ?SchemaDefinitionNode,
232+
export type GraphQLSchemaValidationOptions = {|
233+
/**
234+
* When building a schema from a GraphQL service's introspection result, it
235+
* might be safe to assume the schema is valid. Set to true to assume the
236+
* produced schema is valid.
237+
*
238+
* Default: false
239+
*/
239240
assumeValid?: boolean,
241+
240242
/**
241243
* If provided, the schema will consider fields or types with names included
242244
* in this list valid, even if they do not adhere to the specification's
@@ -245,7 +247,17 @@ export type GraphQLSchemaConfig = {
245247
* This option is provided to ease adoption and may be removed in a future
246248
* major release.
247249
*/
248-
allowedLegacyNames?: ?Array<string>,
250+
allowedLegacyNames?: ?$ReadOnlyArray<string>,
251+
|};
252+
253+
export type GraphQLSchemaConfig = {
254+
query?: ?GraphQLObjectType,
255+
mutation?: ?GraphQLObjectType,
256+
subscription?: ?GraphQLObjectType,
257+
types?: ?Array<GraphQLNamedType>,
258+
directives?: ?Array<GraphQLDirective>,
259+
astNode?: ?SchemaDefinitionNode,
260+
...GraphQLSchemaValidationOptions,
249261
};
250262

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

src/utilities/__tests__/buildASTSchema-test.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,17 @@ describe('Schema Builder', () => {
764764
const errors = validateSchema(schema);
765765
expect(errors.length).to.be.above(0);
766766
});
767+
768+
it('Accepts legacy names', () => {
769+
const doc = parse(dedent`
770+
type Query {
771+
__badName: String
772+
}
773+
`);
774+
const schema = buildASTSchema(doc, { allowedLegacyNames: ['__badName'] });
775+
const errors = validateSchema(schema);
776+
expect(errors.length).to.equal(0);
777+
});
767778
});
768779

769780
describe('Failures', () => {

src/utilities/__tests__/buildClientSchema-test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,34 @@ describe('Type System: build schema from introspection', () => {
669669
expect(secondIntrospection.data).to.containSubset(newIntrospection);
670670
});
671671

672+
it('builds a schema with legacy names', () => {
673+
const introspection = {
674+
__schema: {
675+
queryType: {
676+
name: 'Query',
677+
},
678+
types: [
679+
{
680+
name: 'Query',
681+
kind: 'OBJECT',
682+
fields: [
683+
{
684+
name: '__badName',
685+
args: [],
686+
type: { name: 'String' },
687+
},
688+
],
689+
interfaces: [],
690+
},
691+
],
692+
},
693+
};
694+
const schema = buildClientSchema(introspection, {
695+
allowedLegacyNames: ['__badName'],
696+
});
697+
expect(schema.__allowedLegacyNames).to.deep.equal(['__badName']);
698+
});
699+
672700
it('builds a schema aware of deprecation', async () => {
673701
const schema = new GraphQLSchema({
674702
query: new GraphQLObjectType({

src/utilities/__tests__/extendSchema-test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,30 @@ describe('extendSchema', () => {
818818
expect(schema.__allowedLegacyNames).to.deep.equal(['__badName']);
819819
});
820820

821+
it('adds to the configuration of the original schema object', () => {
822+
const testSchemaWithLegacyNames = new GraphQLSchema({
823+
query: new GraphQLObjectType({
824+
name: 'Query',
825+
fields: () => ({
826+
__badName: { type: GraphQLString },
827+
}),
828+
}),
829+
allowedLegacyNames: ['__badName'],
830+
});
831+
const ast = parse(`
832+
extend type Query {
833+
__anotherBadName: String
834+
}
835+
`);
836+
const schema = extendSchema(testSchemaWithLegacyNames, ast, {
837+
allowedLegacyNames: ['__anotherBadName'],
838+
});
839+
expect(schema.__allowedLegacyNames).to.deep.equal([
840+
'__badName',
841+
'__anotherBadName',
842+
]);
843+
});
844+
821845
describe('does not allow extending a non-object type', () => {
822846
it('not an object', () => {
823847
const ast = parse(`

src/utilities/buildASTSchema.js

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ import { introspectionTypes } from '../type/introspection';
6464
import { specifiedScalarTypes } from '../type/scalars';
6565

6666
import { GraphQLSchema } from '../type/schema';
67+
import type { GraphQLSchemaValidationOptions } from '../type/schema';
6768

6869
import type {
6970
GraphQLType,
@@ -72,14 +73,7 @@ import type {
7273
} from '../type/definition';
7374

7475
type Options = {|
75-
/**
76-
* When building a schema from a GraphQL service's introspection result, it
77-
* might be safe to assume the schema is valid. Set to true to assume the
78-
* produced schema is valid.
79-
*
80-
* Default: false
81-
*/
82-
assumeValid?: boolean,
76+
...GraphQLSchemaValidationOptions,
8377

8478
/**
8579
* Descriptions are defined as preceding string literals, however an older
@@ -227,6 +221,7 @@ export function buildASTSchema(
227221
directives,
228222
astNode: schemaDef,
229223
assumeValid: options && options.assumeValid,
224+
allowedLegacyNames: options && options.allowedLegacyNames,
230225
});
231226

232227
function getOperationTypes(schema: SchemaDefinitionNode) {

src/utilities/buildClientSchema.js

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,10 @@ import type {
6060
IntrospectionNamedTypeRef,
6161
} from './introspectionQuery';
6262

63+
import type { GraphQLSchemaValidationOptions } from '../type/schema';
64+
6365
type Options = {|
64-
/**
65-
* When building a schema from a GraphQL service's introspection result, it
66-
* might be safe to assume the schema is valid. Set to true to assume the
67-
* produced schema is valid.
68-
*
69-
* Default: false
70-
*/
71-
assumeValid?: boolean,
66+
...GraphQLSchemaValidationOptions,
7267
|};
7368

7469
/**
@@ -414,5 +409,6 @@ export function buildClientSchema(
414409
types,
415410
directives,
416411
assumeValid: options && options.assumeValid,
412+
allowedLegacyNames: options && options.allowedLegacyNames,
417413
});
418414
}

src/utilities/extendSchema.js

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { ASTDefinitionBuilder } from './buildASTSchema';
1313
import { GraphQLError } from '../error/GraphQLError';
1414
import { isSchema, GraphQLSchema } from '../type/schema';
1515

16+
import type { GraphQLSchemaValidationOptions } from '../type/schema';
17+
1618
import {
1719
isObjectType,
1820
isInterfaceType,
@@ -38,14 +40,7 @@ import type {
3840
} from '../language/ast';
3941

4042
type Options = {|
41-
/**
42-
* When extending a schema with a known valid extension, it might be safe to
43-
* assume the schema is valid. Set to true to assume the produced schema
44-
* is valid.
45-
*
46-
* Default: false
47-
*/
48-
assumeValid?: boolean,
43+
...GraphQLSchemaValidationOptions,
4944

5045
/**
5146
* Descriptions are defined as preceding string literals, however an older
@@ -245,6 +240,14 @@ export function extendSchema(
245240
types.push(definitionBuilder.buildType(typeName));
246241
});
247242

243+
// Support both original legacy names and extended legacy names.
244+
const schemaAllowedLegacyNames = schema.__allowedLegacyNames;
245+
const extendAllowedLegacyNames = options && options.allowedLegacyNames;
246+
const allowedLegacyNames =
247+
schemaAllowedLegacyNames && extendAllowedLegacyNames
248+
? schemaAllowedLegacyNames.concat(extendAllowedLegacyNames)
249+
: schemaAllowedLegacyNames || extendAllowedLegacyNames;
250+
248251
// Then produce and return a Schema with these types.
249252
return new GraphQLSchema({
250253
query: queryType,
@@ -253,8 +256,7 @@ export function extendSchema(
253256
types,
254257
directives: getMergedDirectives(),
255258
astNode: schema.astNode,
256-
allowedLegacyNames:
257-
schema.__allowedLegacyNames && schema.__allowedLegacyNames.slice(),
259+
allowedLegacyNames,
258260
});
259261

260262
function appendExtensionToTypeExtensions(

0 commit comments

Comments
 (0)