Skip to content

Commit

Permalink
feature: Require label argument on @defer directive (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
calvincestari committed Sep 13, 2023
1 parent 29c2068 commit 9f65878
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,17 @@ describe("given schema", () => {
species: String!
friend: Animal!
}
type Dog implements Animal {
species: String!
friend: Animal!
}
`;

const schema: GraphQLSchema = loadSchemaFromSources([new Source(schemaSDL, "Test Schema", { line: 1, column: 1 })]);

// Compilation Tests

describe("query has inline fragment with @defer directive", () => {
const documentString: string = `
query Test($a: Boolean!) {
Expand Down Expand Up @@ -154,11 +161,13 @@ describe("given schema", () => {
});
});

// Validation Tests

describe("query has inline fragment with @defer directive and no type condition", () => {
const documentString: string = `
query Test {
allAnimals {
... @defer {
... @defer(label: "custom") {
species
}
}
Expand All @@ -169,10 +178,141 @@ describe("given schema", () => {
new Source(documentString, "Test Query", { line: 1, column: 1 })
);

it("should throw error", () => {
it("should fail validation", () => {
const validationErrors: readonly GraphQLError[] = validateDocument(schema, document, emptyValidationOptions)

expect(validationErrors.length).toEqual(1)
expect(validationErrors[0].message).toEqual(
"Apollo does not support deferred inline fragments without a type condition. Please add a type condition to this inline fragment."
)
});
});

describe("query has inline fragment with @defer directive and no label argument", () => {
const documentString: string = `
query Test {
allAnimals {
... on Dog @defer {
species
}
}
}
`;

const document: DocumentNode = parseOperationDocument(
new Source(documentString, "Test Query", { line: 1, column: 1 })
);

it("should fail validation", () => {
const validationErrors: readonly GraphQLError[] = validateDocument(schema, document, emptyValidationOptions)

expect(validationErrors.length).toEqual(1)
expect(validationErrors[0].message).toEqual(
"Apollo requires all @defer directives to use the 'label' argument. Please add a 'label' argument to the @defer directive on this inline fragment."
)
})
})

describe("query has fragment spread with @defer directive and no label argument", () => {
const documentString: string = `
query Test {
allAnimals {
...DogFragment @defer
}
}
fragment DogFragment on Dog {
species
}
`;

const document: DocumentNode = parseOperationDocument(
new Source(documentString, "Test Query", { line: 1, column: 1 })
);

it("should fail validation", () => {
const validationErrors: readonly GraphQLError[] = validateDocument(schema, document, emptyValidationOptions)

expect(validationErrors.length).toEqual(1)
expect(validationErrors[0].message).toEqual(
"Apollo requires all @defer directives to use the 'label' argument. Please add a 'label' argument to the @defer directive on this fragment spread."
)
})
})

describe("query has fragment spread, with @defer directive and if argument and no label argument on fragment inner type condition", () => {
const documentString: string = `
query Test {
allAnimals {
...AnimalFragment
}
}
fragment AnimalFragment on Animal {
... on Dog @defer(if: true) {
species
}
}
`;

const document: DocumentNode = parseOperationDocument(
new Source(documentString, "Test Query", { line: 1, column: 1 })
);

it("should fail validation", () => {
const validationErrors: readonly GraphQLError[] = validateDocument(schema, document, emptyValidationOptions)

expect(validationErrors.length).toEqual(1)
expect(validationErrors[0].message).toEqual(
"Apollo requires all @defer directives to use the 'label' argument. Please add a 'label' argument to the @defer directive on this inline fragment."
)
})
})

describe("query has inline fragment with @defer directive and label argument", () => {
const documentString: string = `
query Test {
allAnimals {
... on Dog @defer(label: "custom") {
species
}
}
}
`;

const document: DocumentNode = parseOperationDocument(
new Source(documentString, "Test Query", { line: 1, column: 1 })
);

it("should pass validation", () => {
const validationErrors: readonly GraphQLError[] = validateDocument(schema, document, emptyValidationOptions)

expect(validationErrors.length).toEqual(0)
})
})

describe("query has fragment spread with @defer directive and label argument", () => {
const documentString: string = `
query Test {
allAnimals {
...AnimalFragment @defer(label: "custom")
}
}
fragment AnimalFragment on Animal {
species
}
`;

const document: DocumentNode = parseOperationDocument(
new Source(documentString, "Test Query", { line: 1, column: 1 })
);

it("should pass validation", () => {
const validationErrors: readonly GraphQLError[] = validateDocument(schema, document, emptyValidationOptions)

expect(validationErrors.length).toEqual(0)
})
})

});
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
VariableDefinitionNode,
InlineFragmentNode,
GraphQLDeferDirective,
FragmentSpreadNode,
} from "graphql";

const specifiedRulesToBeRemoved: [ValidationRule] = [NoUnusedFragmentsRule];
Expand All @@ -32,6 +33,7 @@ export function defaultValidationRules(options: ValidationOptions): ValidationRu
NoAnonymousQueries,
NoTypenameAlias,
DeferredInlineFragmentNoTypeCondition,
DeferDirectiveMissingLabelArgument,
...(disallowedFieldNamesRule ? [disallowedFieldNamesRule] : []),
...(disallowedInputParameterNamesRule ? [disallowedInputParameterNamesRule] : []),
...specifiedRules.filter((rule) => !specifiedRulesToBeRemoved.includes(rule)),
Expand Down Expand Up @@ -87,6 +89,43 @@ export function DeferredInlineFragmentNoTypeCondition(context: ValidationContext
};
}

export function DeferDirectiveMissingLabelArgument(context: ValidationContext) {
function ValidateDeferDirectiveLabelArgument(node: InlineFragmentNode | FragmentSpreadNode, error: GraphQLError) {
if (node.directives) {
for (const directive of node.directives) {
if (directive.name.value == GraphQLDeferDirective.name && !(directive.arguments?.find(
function(element) {
return element.name.value == 'label'
}) ? true: false)
) {
context.reportError(error)
}
}
}
}

return {
InlineFragment(node: InlineFragmentNode) {
ValidateDeferDirectiveLabelArgument(
node,
new GraphQLError(
"Apollo requires all @defer directives to use the 'label' argument. Please add a 'label' argument to the @defer directive on this inline fragment.",
{ nodes: node }
)
)
},
FragmentSpread(node: FragmentSpreadNode) {
ValidateDeferDirectiveLabelArgument(
node,
new GraphQLError(
"Apollo requires all @defer directives to use the 'label' argument. Please add a 'label' argument to the @defer directive on this fragment spread.",
{ nodes: node }
)
)
}
}
}

function ApolloIOSDisallowedFieldNames(fieldNames?: Array<string>) {
if (fieldNames) {
return function ApolloIOSDisallowedFieldNamesValidationRule(context: ValidationContext) {
Expand Down

0 comments on commit 9f65878

Please sign in to comment.