From 3e99cfe7467be9ccbe0ae178b6b054090a105271 Mon Sep 17 00:00:00 2001 From: Joseph Savona Date: Sat, 10 Oct 2015 11:57:55 -0700 Subject: [PATCH] disallow non-root `node(id:...)` fields --- .../babel-relay-plugin/lib/RelayQLPrinter.js | 10 ++++ .../babel-relay-plugin/src/RelayQLPrinter.js | 18 ++++++++ .../src/__fixtures__/nonRootNodeField.fixture | 17 +++++++ .../src/__tests__/testschema.rfc.graphql | 5 ++ .../src/__tests__/testschema.rfc.json | 46 +++++++++++++++++++ 5 files changed, 96 insertions(+) create mode 100644 scripts/babel-relay-plugin/src/__fixtures__/nonRootNodeField.fixture diff --git a/scripts/babel-relay-plugin/lib/RelayQLPrinter.js b/scripts/babel-relay-plugin/lib/RelayQLPrinter.js index b7dd991cd847d..695c8a746b6f4 100644 --- a/scripts/babel-relay-plugin/lib/RelayQLPrinter.js +++ b/scripts/babel-relay-plugin/lib/RelayQLPrinter.js @@ -35,6 +35,7 @@ var RelayQLFragmentSpread = _require.RelayQLFragmentSpread; var RelayQLInlineFragment = _require.RelayQLInlineFragment; var RelayQLMutation = _require.RelayQLMutation; var RelayQLQuery = _require.RelayQLQuery; +var RelayQLType = _require.RelayQLType; var invariant = require('./invariant'); var t = require('babel-core/lib/types'); @@ -206,6 +207,8 @@ var RelayQLPrinter = (function () { requisiteFields.id = true; } + validateField(field, parent.getType()); + // TODO: Generalize to non-`Node` types. if (fieldType.alwaysImplements('Node')) { metadata.rootCall = 'node'; @@ -344,6 +347,13 @@ var RelayQLPrinter = (function () { return RelayQLPrinter; })(); +function validateField(field, parentType) { + if (field.getName() === 'node') { + var args = field.getArguments(); + invariant(args.length !== 1 || args[0].getName() !== 'id', 'You defined a `node(id: %s)` field on type `%s`, but Relay requires ' + 'the `node` field to be defined on the root type. See the Object ' + 'Identification Guide: \n' + 'http://facebook.github.io/relay/docs/graphql-object-identification.html', args[0] && args[0].getType().getName({ modifiers: true }), parentType.getName({ modifiers: false })); + } +} + function validateConnectionField(field) { invariant(!field.hasArgument('first') || !field.hasArgument('before'), 'Connection arguments `%s(before: , first: )` are ' + 'not supported. Use `(first: )`, ' + '`(after: , first: )`, or ' + '`(before: , last: )`.', field.getName()); invariant(!field.hasArgument('last') || !field.hasArgument('after'), 'Connection arguments `%s(after: , last: )` are ' + 'not supported. Use `(last: )`, ' + '`(before: , last: )`, or ' + '`(after: , first: )`.', field.getName()); diff --git a/scripts/babel-relay-plugin/src/RelayQLPrinter.js b/scripts/babel-relay-plugin/src/RelayQLPrinter.js index d69bdc2695e27..8cba529a65cc1 100644 --- a/scripts/babel-relay-plugin/src/RelayQLPrinter.js +++ b/scripts/babel-relay-plugin/src/RelayQLPrinter.js @@ -23,6 +23,7 @@ const { RelayQLInlineFragment, RelayQLMutation, RelayQLQuery, + RelayQLType } = require('./RelayQLAST'); const invariant = require('./invariant'); @@ -305,6 +306,8 @@ class RelayQLPrinter { requisiteFields.id = true; } + validateField(field, parent.getType()); + // TODO: Generalize to non-`Node` types. if (fieldType.alwaysImplements('Node')) { metadata.rootCall = 'node'; @@ -493,6 +496,21 @@ class RelayQLPrinter { } } +function validateField(field: RelayQLField, parentType: RelayQLType): void { + if (field.getName() === 'node') { + var args = field.getArguments(); + invariant( + args.length !== 1 || args[0].getName() !== 'id', + 'You defined a `node(id: %s)` field on type `%s`, but Relay requires ' + + 'the `node` field to be defined on the root type. See the Object ' + + 'Identification Guide: \n' + + 'http://facebook.github.io/relay/docs/graphql-object-identification.html', + args[0] && args[0].getType().getName({modifiers: true}), + parentType.getName({modifiers: false}) + ); + } +} + function validateConnectionField(field: RelayQLField): void { invariant( !field.hasArgument('first') || !field.hasArgument('before'), diff --git a/scripts/babel-relay-plugin/src/__fixtures__/nonRootNodeField.fixture b/scripts/babel-relay-plugin/src/__fixtures__/nonRootNodeField.fixture new file mode 100644 index 0000000000000..4b93a99dd418a --- /dev/null +++ b/scripts/babel-relay-plugin/src/__fixtures__/nonRootNodeField.fixture @@ -0,0 +1,17 @@ +Input: +var Relay = require('Relay'); +var fragment = Relay.QL` + fragment on InvalidType { + node(id: 123) { + ... on User { + name + } + } + } +`; + +Output: +var Relay = require('Relay'); +var fragment = (function () { + throw new Error('GraphQL validation/transform error ``You defined a `node(id: Int)` field on type `InvalidType`, but Relay requires the `node` field to be defined on the root type. See the Object Identification Guide: \nhttp://facebook.github.io/relay/docs/graphql-object-identification.html`` in file `nonRootNodeField.fixture`.'); +})(); \ No newline at end of file diff --git a/scripts/babel-relay-plugin/src/__tests__/testschema.rfc.graphql b/scripts/babel-relay-plugin/src/__tests__/testschema.rfc.graphql index b86073d973526..13a8a15fca340 100644 --- a/scripts/babel-relay-plugin/src/__tests__/testschema.rfc.graphql +++ b/scripts/babel-relay-plugin/src/__tests__/testschema.rfc.graphql @@ -4,6 +4,11 @@ type Root { media(id: Int): Media viewer: Viewer search(query: [SearchInput!]): [SearchResult] + _invalid: InvalidType +} + +type InvalidType { + node(id: Int): Node } type SearchResult { diff --git a/scripts/babel-relay-plugin/src/__tests__/testschema.rfc.json b/scripts/babel-relay-plugin/src/__tests__/testschema.rfc.json index e58060e1202aa..07a2b359ee681 100644 --- a/scripts/babel-relay-plugin/src/__tests__/testschema.rfc.json +++ b/scripts/babel-relay-plugin/src/__tests__/testschema.rfc.json @@ -136,6 +136,18 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "_invalid", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "InvalidType", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -1273,6 +1285,40 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "InvalidType", + "description": null, + "fields": [ + { + "name": "node", + "description": null, + "args": [ + { + "name": "id", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "INTERFACE", + "name": "Node", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "OBJECT", "name": "Mutation",