Skip to content

Commit d508613

Browse files
Superd22MichalLytek
andcommitted
feat(directives): add support for directives on interface types (#744)
Co-authored-by: Michał Lytek <[email protected]>
1 parent 8d8bbae commit d508613

File tree

6 files changed

+97
-11
lines changed

6 files changed

+97
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Features
66
- **Breaking Change**: `AuthChecker` type is now "function or class" - update to `AuthCheckerFn` if the function form is needed in the code
77
- support class-based auth checker, which allows for dependency injection
8+
- allow defining directives for interface types and theirs fields, with inheritance for object types fields (#744)
89

910
## v1.1.1
1011
### Fixes

docs/directives.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Basically, we declare the usage of directives just like in SDL, with the `@` syn
3232
@Directive('@deprecated(reason: "Use newField")')
3333
```
3434

35-
Currently, you can use the directives only on object types, input types and their fields or fields resolvers, as well as queries, mutations and subscriptions.
35+
Currently, you can use the directives only on object types, input types, interface types and their fields or fields resolvers, as well as queries, mutations and subscriptions. Other locations like scalars, enums, unions or arguments are not yet supported.
3636

3737
So the `@Directive` decorator can be placed over the class property/method or over the type class itself, depending on the needs and the placements supported by the implementation:
3838

src/schema/definition-node.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
parseValue,
1010
DocumentNode,
1111
parse,
12+
InterfaceTypeDefinitionNode,
1213
} from "graphql";
1314

1415
import { InvalidDirectiveError } from "../errors";
@@ -104,6 +105,25 @@ export function getInputValueDefinitionNode(
104105
};
105106
}
106107

108+
export function getInterfaceTypeDefinitionNode(
109+
name: string,
110+
directiveMetadata?: DirectiveMetadata[],
111+
): InterfaceTypeDefinitionNode | undefined {
112+
if (!directiveMetadata || !directiveMetadata.length) {
113+
return;
114+
}
115+
116+
return {
117+
kind: "InterfaceTypeDefinition",
118+
name: {
119+
kind: "Name",
120+
// FIXME: use proper AST representation
121+
value: name,
122+
},
123+
directives: directiveMetadata.map(getDirectiveNode),
124+
};
125+
}
126+
107127
export function getDirectiveNode(directive: DirectiveMetadata): DirectiveNode {
108128
const { nameOrDefinition, args } = directive;
109129

src/schema/schema-generator.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import {
5252
getFieldDefinitionNode,
5353
getInputObjectTypeDefinitionNode,
5454
getInputValueDefinitionNode,
55+
getInterfaceTypeDefinitionNode,
5556
getObjectTypeDefinitionNode,
5657
} from "./definition-node";
5758
import { ObjectClassMetadata } from "../metadata/definitions/object-class-metdata";
@@ -392,6 +393,7 @@ export abstract class SchemaGenerator {
392393
type: new GraphQLInterfaceType({
393394
name: interfaceType.name,
394395
description: interfaceType.description,
396+
astNode: getInterfaceTypeDefinitionNode(interfaceType.name, interfaceType.directives),
395397
interfaces: () => {
396398
let interfaces = (interfaceType.interfaceClasses || []).map<GraphQLInterfaceType>(
397399
interfaceClass =>
@@ -431,19 +433,21 @@ export abstract class SchemaGenerator {
431433
(resolver.resolverClassMetadata === undefined ||
432434
resolver.resolverClassMetadata.isAbstract === false),
433435
);
436+
const type = this.getGraphQLOutputType(
437+
field.target,
438+
field.name,
439+
field.getType(),
440+
field.typeOptions,
441+
);
434442
fieldsMap[field.schemaName] = {
435-
type: this.getGraphQLOutputType(
436-
field.target,
437-
field.name,
438-
field.getType(),
439-
field.typeOptions,
440-
),
443+
type,
441444
args: this.generateHandlerArgs(field.target, field.name, field.params!),
442445
resolve: fieldResolverMetadata
443446
? createAdvancedFieldResolver(fieldResolverMetadata)
444447
: createBasicFieldResolver(field),
445448
description: field.description,
446449
deprecationReason: field.deprecationReason,
450+
astNode: getFieldDefinitionNode(field.name, type, field.directives),
447451
extensions: {
448452
complexity: field.complexity,
449453
...field.extensions,

tests/functional/directives.ts

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
// tslint:disable:member-ordering
22
import "reflect-metadata";
3-
4-
import { GraphQLSchema, graphql, GraphQLInputObjectType, printSchema } from "graphql";
3+
import {
4+
GraphQLSchema,
5+
graphql,
6+
GraphQLInputObjectType,
7+
GraphQLInterfaceType,
8+
GraphQLObjectType,
9+
} from "graphql";
510
import {
611
Field,
712
InputType,
@@ -14,6 +19,7 @@ import {
1419
Mutation,
1520
FieldResolver,
1621
Subscription,
22+
InterfaceType,
1723
} from "../../src";
1824
import { getMetadataStorage } from "../../src/metadata/getMetadataStorage";
1925
import { SchemaDirectiveVisitor } from "graphql-tools";
@@ -108,6 +114,17 @@ describe("Directives", () => {
108114
}
109115
}
110116

117+
@InterfaceType()
118+
@Directive("foo")
119+
abstract class DirectiveOnInterface {
120+
@Field()
121+
@Directive("bar")
122+
withDirective: string;
123+
}
124+
125+
@ObjectType({ implements: DirectiveOnInterface })
126+
class ObjectImplement extends DirectiveOnInterface {}
127+
111128
@Resolver()
112129
class SampleResolver {
113130
@Query(() => SampleObjectType)
@@ -225,8 +242,21 @@ describe("Directives", () => {
225242
}
226243
}
227244

245+
@Resolver(() => ObjectImplement)
246+
class ObjectImplementResolver {
247+
@Query(() => ObjectImplement)
248+
objectImplentingInterface(): ObjectImplement {
249+
return new ObjectImplement();
250+
}
251+
}
252+
228253
schema = await buildSchema({
229-
resolvers: [SampleResolver, SampleObjectTypeResolver, SubSampleResolver],
254+
resolvers: [
255+
SampleResolver,
256+
SampleObjectTypeResolver,
257+
SubSampleResolver,
258+
ObjectImplementResolver,
259+
],
230260
validate: false,
231261
});
232262

@@ -483,6 +513,31 @@ describe("Directives", () => {
483513
});
484514
});
485515
});
516+
517+
describe("Interface", () => {
518+
it("adds directive to interface", () => {
519+
const interfaceType = schema.getType("DirectiveOnInterface") as GraphQLInterfaceType;
520+
521+
expect(interfaceType).toHaveProperty("astNode");
522+
assertValidDirective(interfaceType.astNode, "foo");
523+
});
524+
525+
it("adds field directives to interface fields", async () => {
526+
const fields = (schema.getType("DirectiveOnInterface") as GraphQLInterfaceType).getFields();
527+
528+
expect(fields).toHaveProperty("withDirective");
529+
expect(fields.withDirective).toHaveProperty("astNode");
530+
assertValidDirective(fields.withDirective.astNode, "bar");
531+
});
532+
533+
it("adds inherited field directives to object type fields while extending interface type class", async () => {
534+
const fields = (schema.getType("ObjectImplement") as GraphQLObjectType).getFields();
535+
536+
expect(fields).toHaveProperty("withDirective");
537+
expect(fields.withDirective).toHaveProperty("astNode");
538+
assertValidDirective(fields.withDirective.astNode, "bar");
539+
});
540+
});
486541
});
487542

488543
describe("errors", () => {

tests/helpers/directives/assertValidDirective.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@ import {
22
FieldDefinitionNode,
33
InputObjectTypeDefinitionNode,
44
InputValueDefinitionNode,
5+
InterfaceTypeDefinitionNode,
56
parseValue,
67
} from "graphql";
78

89
import { Maybe } from "../../../src/interfaces/Maybe";
910

1011
export function assertValidDirective(
11-
astNode: Maybe<FieldDefinitionNode | InputObjectTypeDefinitionNode | InputValueDefinitionNode>,
12+
astNode: Maybe<
13+
| FieldDefinitionNode
14+
| InputObjectTypeDefinitionNode
15+
| InputValueDefinitionNode
16+
| InterfaceTypeDefinitionNode
17+
>,
1218
name: string,
1319
args?: { [key: string]: string },
1420
): void {

0 commit comments

Comments
 (0)