diff --git a/src/__tests__/resolve-info.test.ts b/src/__tests__/resolve-info.test.ts index 2d1b792..c647c62 100644 --- a/src/__tests__/resolve-info.test.ts +++ b/src/__tests__/resolve-info.test.ts @@ -188,21 +188,21 @@ describe("resolver info", () => { ); expect(result.errors).not.toBeDefined(); expect(inf.fieldExpansion).toMatchInlineSnapshot(` - Object { - "Foo": Object { - "a": Object { - Symbol(LeafFieldSymbol): true, - }, - "d": Object { - "Bar": Object { - "e": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - }, - }, - } - `); + Object { + "Foo": Object { + "a": Object { + Symbol(LeafFieldSymbol): true, + }, + "d": Object { + "Bar": Object { + "e": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + }, + }, + } + `); }); test("with fragments", async () => { @@ -230,20 +230,20 @@ describe("resolver info", () => { ); expect(inf.fieldExpansion).toMatchInlineSnapshot(` - Object { - "Foo": Object { - "a": Object { - Symbol(LeafFieldSymbol): true, - }, - "b": Object { - Symbol(LeafFieldSymbol): true, - }, - "c": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - } - `); + Object { + "Foo": Object { + "a": Object { + Symbol(LeafFieldSymbol): true, + }, + "b": Object { + Symbol(LeafFieldSymbol): true, + }, + "c": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + } + `); }); test("inline fragments", async () => { @@ -269,18 +269,18 @@ describe("resolver info", () => { ); expect(inf.fieldExpansion).toMatchInlineSnapshot(` - Object { - "Foo": Object { - "d": Object { - "Bar": Object { - "e": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - }, - }, - } - `); + Object { + "Foo": Object { + "d": Object { + "Bar": Object { + "e": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + }, + }, + } + `); }); test("aggregate multiple selections of the same field", async () => { @@ -304,20 +304,20 @@ describe("resolver info", () => { ); expect(inf.fieldExpansion).toMatchInlineSnapshot(` - Object { - "Foo": Object { - "a": Object { - Symbol(LeafFieldSymbol): true, - }, - "b": Object { - Symbol(LeafFieldSymbol): true, - }, - "c": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - } - `); + Object { + "Foo": Object { + "a": Object { + Symbol(LeafFieldSymbol): true, + }, + "b": Object { + Symbol(LeafFieldSymbol): true, + }, + "c": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + } + `); }); }); @@ -379,33 +379,33 @@ describe("resolver info", () => { expect(result.errors).not.toBeDefined(); expect(inf.fieldExpansion).toMatchInlineSnapshot(` - Object { - "Bar1": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "title": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Bar2": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "title": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "IBar": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "title": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - } - `); + Object { + "Bar1": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "title": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Bar2": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "title": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "IBar": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "title": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + } + `); }); test("fields per type", async () => { @@ -430,39 +430,39 @@ describe("resolver info", () => { ); expect(result.errors).not.toBeDefined(); expect(inf.fieldExpansion).toMatchInlineSnapshot(` - Object { - "Bar1": Object { - "b1": Object { - Symbol(LeafFieldSymbol): true, - }, - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "title": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Bar2": Object { - "b2": Object { - Symbol(LeafFieldSymbol): true, - }, - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "title": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "IBar": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "title": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - } - `); + Object { + "Bar1": Object { + "b1": Object { + Symbol(LeafFieldSymbol): true, + }, + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "title": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Bar2": Object { + "b2": Object { + Symbol(LeafFieldSymbol): true, + }, + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "title": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "IBar": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "title": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + } + `); }); test("fields per type - with fragments", async () => { @@ -498,39 +498,39 @@ describe("resolver info", () => { expect(result.errors).not.toBeDefined(); expect(inf.fieldExpansion).toMatchInlineSnapshot(` - Object { - "Bar1": Object { - "b1": Object { - Symbol(LeafFieldSymbol): true, - }, - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "title": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Bar2": Object { - "b2": Object { - Symbol(LeafFieldSymbol): true, - }, - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "title": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "IBar": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "title": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - } - `); + Object { + "Bar1": Object { + "b1": Object { + Symbol(LeafFieldSymbol): true, + }, + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "title": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Bar2": Object { + "b2": Object { + Symbol(LeafFieldSymbol): true, + }, + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "title": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "IBar": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "title": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + } + `); }); test("aggregate multiple selections of the same field", async () => { @@ -563,39 +563,39 @@ describe("resolver info", () => { expect(result.errors).not.toBeDefined(); expect(inf.fieldExpansion).toMatchInlineSnapshot(` - Object { - "Bar1": Object { - "b1": Object { - Symbol(LeafFieldSymbol): true, - }, - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "title": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Bar2": Object { - "b2": Object { - Symbol(LeafFieldSymbol): true, - }, - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "title": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "IBar": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "title": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - } - `); + Object { + "Bar1": Object { + "b1": Object { + Symbol(LeafFieldSymbol): true, + }, + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "title": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Bar2": Object { + "b2": Object { + Symbol(LeafFieldSymbol): true, + }, + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "title": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "IBar": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "title": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + } + `); }); }); @@ -653,19 +653,19 @@ describe("resolver info", () => { expect(result.errors).not.toBeDefined(); expect(inf.fieldExpansion).toMatchInlineSnapshot(` - Object { - "Bar": Object { - "bar": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Foo": Object { - "foo": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - } - `); + Object { + "Bar": Object { + "bar": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Foo": Object { + "foo": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + } + `); }); test("__typename", async () => { @@ -682,11 +682,11 @@ describe("resolver info", () => { ); expect(result.errors).not.toBeDefined(); expect(inf.fieldExpansion).toMatchInlineSnapshot(` - Object { - "Bar": Object {}, - "Foo": Object {}, - } - `); + Object { + "Bar": Object {}, + "Foo": Object {}, + } + `); }); test("unions with fragments", async () => { @@ -712,19 +712,19 @@ describe("resolver info", () => { expect(result.errors).not.toBeDefined(); expect(inf.fieldExpansion).toMatchInlineSnapshot(` - Object { - "Bar": Object { - "bar": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Foo": Object { - "foo": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - } - `); + Object { + "Bar": Object { + "bar": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Foo": Object { + "foo": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + } + `); }); test("aggregate multiple selections of the same field", async () => { @@ -754,19 +754,19 @@ describe("resolver info", () => { expect(result.errors).not.toBeDefined(); expect(inf.fieldExpansion).toMatchInlineSnapshot(` - Object { - "Bar": Object { - "bar": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Foo": Object { - "foo": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - } - `); + Object { + "Bar": Object { + "bar": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Foo": Object { + "foo": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + } + `); }); }); @@ -890,73 +890,64 @@ describe("resolver info", () => { expect(validationErrors.length).toBe(0); expect(result.errors).not.toBeDefined(); expect(infNode.fieldExpansion).toMatchInlineSnapshot(` - Object { - "Image": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "tags": Object { - "Tag": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "name": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - }, - "url": Object { - Symbol(LeafFieldSymbol): true, - }, - "width": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Media": Object { - "tags": Object { - "Tag": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "name": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - }, - "url": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Node": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Tag": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Video": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "tags": Object { - "Tag": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "name": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - }, - "url": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - } - `); + Object { + "Image": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "tags": Object { + "Tag": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + }, + "url": Object { + Symbol(LeafFieldSymbol): true, + }, + "width": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Media": Object { + "tags": Object { + "Tag": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + }, + "url": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Node": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Tag": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Video": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "tags": Object { + "Tag": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + }, + "url": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + } + `); }); test("elements", async () => { @@ -997,52 +988,13 @@ describe("resolver info", () => { expect(result.errors).not.toBeDefined(); expect(infElements.fieldExpansion).toMatchInlineSnapshot(` - Object { - "Div": Object { - "children": Object { - "Div": Object { - "children": Object { - "Div": Object {}, - "Image": Object { - "url": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Media": Object { - "url": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Node": Object {}, - "Video": Object { - "url": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - }, - }, - "Image": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Media": Object {}, - "Node": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Video": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - }, - }, + Object { + "Div": Object { + "children": Object { + "Div": Object { + "children": Object { + "Div": Object {}, "Image": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, "url": Object { Symbol(LeafFieldSymbol): true, }, @@ -1052,21 +1004,60 @@ describe("resolver info", () => { Symbol(LeafFieldSymbol): true, }, }, - "Node": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - }, + "Node": Object {}, "Video": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, "url": Object { Symbol(LeafFieldSymbol): true, }, }, - } - `); + }, + }, + "Image": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Media": Object {}, + "Node": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Video": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + }, + }, + "Image": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "url": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Media": Object { + "url": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Node": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Video": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "url": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + } + `); }); test("alias 1", async () => { @@ -1091,33 +1082,33 @@ describe("resolver info", () => { expect(validationErrors.length).toBe(0); expect(result.errors).not.toBeDefined(); expect(infNode.fieldExpansion).toMatchInlineSnapshot(` - Object { - "Image": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Media": Object {}, - "Node": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Tag": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "name": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Video": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - } - `); + Object { + "Image": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Media": Object {}, + "Node": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Tag": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "name": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Video": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + } + `); }); test("aliases and __typename should not be included in resolveInfo", async () => { @@ -1154,55 +1145,86 @@ describe("resolver info", () => { expect(validationErrors.length).toBe(0); expect(result.errors).not.toBeDefined(); expect(infNode.fieldExpansion).toMatchInlineSnapshot(` - Object { - "Image": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "tags": Object { - "Tag": Object { - "name": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - }, - }, - "Media": Object { - "tags": Object { - "Tag": Object { - "name": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - }, - }, - "Node": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Tag": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "name": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - "Video": Object { - "id": Object { - Symbol(LeafFieldSymbol): true, - }, - "tags": Object { - "Tag": Object { - "name": Object { - Symbol(LeafFieldSymbol): true, - }, - }, - }, - }, - } - `); + Object { + "Image": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "tags": Object { + "Tag": Object { + "name": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + }, + }, + "Media": Object { + "tags": Object { + "Tag": Object { + "name": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + }, + }, + "Node": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Tag": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "name": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + "Video": Object { + "id": Object { + Symbol(LeafFieldSymbol): true, + }, + "tags": Object { + "Tag": Object { + "name": Object { + Symbol(LeafFieldSymbol): true, + }, + }, + }, + }, + } + `); + }); + + test("shouldInclude function is there and it works", async () => { + const doc = parse(` + query ($var: Boolean!) { + node(id: "tag:1") { + ... on Tag @skip(if: $var) { + id + } + ... on Image { + width + } + } + } + `); + const rootValue = { root: "val" }; + + await executeQuery(schema, doc, rootValue, null, { + var: true + }); + const validationErrors = validate(schema, doc); + if (validationErrors.length > 0) { + console.error(validationErrors); + } + + expect(infNode.fieldExpansion.Tag.id.__shouldInclude).toBeDefined(); + expect(infNode.variableValues).toBeDefined(); + const idShouldInclude = infNode.fieldExpansion.Tag.id.__shouldInclude({ + variables: infNode.variableValues + }); + expect(idShouldInclude).toBe(false); }); }); }); diff --git a/src/resolve-info.ts b/src/resolve-info.ts index 2111e18..4b9cb2c 100644 --- a/src/resolve-info.ts +++ b/src/resolve-info.ts @@ -20,6 +20,8 @@ import { import memoize from "lodash.memoize"; import mergeWith from "lodash.mergewith"; import { memoize2, memoize4 } from "./memoize"; +import { JitFieldNode } from "./ast"; +import { CoercedVariableValues } from "./variables"; // TODO(boopathi): Use negated types to express // Enrichments = { [key in (string & not keyof GraphQLResolveInfo)]: T[key] } @@ -35,19 +37,32 @@ export interface ResolveInfoEnricherInput { parentType: GraphQLObjectType; returnType: GraphQLOutputType; fieldName: string; - fieldNodes: FieldNode[]; + fieldNodes: JitFieldNode[]; } -export interface FieldExpansion { +export type ShouldIncludeVariables = { + variables: CoercedVariableValues; +}; + +export interface ShouldIncludeExtension { + __shouldInclude: (variables: ShouldIncludeVariables) => boolean; +} + +export type FieldExpansion = ShouldIncludeExtension & { // The possible return types that the field can return // It includes all the types in the Schema that intersect with the actual return type // eslint-disable-next-line no-use-before-define [returnType: string]: TypeExpansion; +}; + +type RootFieldExpansion = { + // eslint-disable-next-line no-use-before-define + [returnType: string]: TypeExpansion; } const LeafFieldSymbol = Symbol("LeafFieldSymbol"); -export interface LeafField { +export interface LeafField extends ShouldIncludeExtension { [LeafFieldSymbol]: true; } @@ -57,11 +72,27 @@ export interface TypeExpansion { [fieldName: string]: FieldExpansion | LeafField; } -function createLeafField(props: T): T & LeafField { - return { - [LeafFieldSymbol]: true, - ...props - }; +function createLeafField( + props: T, + shouldInclude: (variables: ShouldIncludeVariables) => boolean +): T & LeafField { + const leaf = Object.create( + { + __shouldInclude: shouldInclude + }, + {} + ); + + Object.assign(leaf, props); + + Object.defineProperty(leaf, LeafFieldSymbol, { + writable: true, + configurable: true, + enumerable: true, + value: true + }); + + return leaf; } export function isLeafField(obj: LeafField | FieldExpansion): obj is LeafField { @@ -154,7 +185,7 @@ export function createResolveInfoThunk( export function fieldExpansionEnricher(input: ResolveInfoEnricherInput) { const { schema, fragments, returnType, fieldNodes } = input; - const fieldExpansion: FieldExpansion | LeafField = {}; + const fieldExpansion: RootFieldExpansion = {}; for (const fieldNode of fieldNodes) { deepMerge( @@ -163,6 +194,10 @@ export function fieldExpansionEnricher(input: ResolveInfoEnricherInput) { ); } + // this is remaining from deepMerge. + // delete - because you can't skip resolution of root + delete fieldExpansion.__shouldInclude + return { fieldExpansion }; @@ -182,18 +217,31 @@ const memoizedExpandFieldNode = memoize4(expandFieldNode); function expandFieldNode( schema: GraphQLSchema, fragments: FragmentsType, - node: FieldNode, + node: JitFieldNode, fieldType: GraphQLOutputType ): FieldExpansion | LeafField { + const shouldInclude = (variables: ShouldIncludeVariables): boolean => { + const expr = (node.__internalShouldInclude as string); + // eslint-disable-next-line no-new-func + const fn = new Function(`__context`, `return ${expr}`); + + return fn(variables); + }; + if (node.selectionSet == null) { - return createLeafField({}); + return createLeafField({}, shouldInclude); } // there is a selectionSet which makes the fieldType a CompositeType const typ = memoizedResolveEndType(fieldType) as GraphQLCompositeType; const possibleTypes = memoizedGetPossibleTypes(schema, typ); - const fieldExpansion: FieldExpansion = {}; + const fieldExpansion: FieldExpansion = Object.create( + { + __shouldInclude: shouldInclude + }, + {} + ); for (const possibleType of possibleTypes) { if (!isUnionType(possibleType)) { fieldExpansion[possibleType.name] = memoizedExpandFieldNodeType(