Skip to content

Commit

Permalink
[directive plugin] improve ast representation of directive args
Browse files Browse the repository at this point in the history
  • Loading branch information
hayes committed Nov 30, 2024
1 parent a57c047 commit ea9981f
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 31 deletions.
5 changes: 5 additions & 0 deletions .changeset/fuzzy-moons-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pothos/plugin-prisma": minor
---

Support prisma 6.0
5 changes: 5 additions & 0 deletions .changeset/pink-gifts-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pothos/plugin-directives": minor
---

Improve ast representation of direvtive arguments
82 changes: 54 additions & 28 deletions packages/plugin-directives/src/mock-ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default function mockAst(schema: GraphQLSchema) {
schema.extensionASTNodes = [
{
kind: Kind.SCHEMA_EXTENSION,
directives: directiveNodes(schema.extensions?.directives as DirectiveList),
directives: directiveNodes(schema.extensions?.directives as DirectiveList, null, schema),
operationTypes: (
[
{
Expand Down Expand Up @@ -78,48 +78,48 @@ export default function mockAst(schema: GraphQLSchema) {
name: { kind: Kind.NAME, value: typeName },
description: type.description ? { kind: Kind.STRING, value: type.description } : undefined,
interfaces: type.getInterfaces().map((iface) => typeNode(iface) as NamedTypeNode),
fields: fieldNodes(type.getFields()),
directives: directiveNodes(type.extensions?.directives as DirectiveList),
fields: fieldNodes(type.getFields(), schema),
directives: directiveNodes(type.extensions?.directives as DirectiveList, null, schema),
};
} else if (type instanceof GraphQLInterfaceType) {
type.astNode = {
kind: Kind.INTERFACE_TYPE_DEFINITION,
name: { kind: Kind.NAME, value: typeName },
description: type.description ? { kind: Kind.STRING, value: type.description } : undefined,
interfaces: type.getInterfaces().map((iface) => typeNode(iface) as NamedTypeNode),
fields: fieldNodes(type.getFields()),
directives: directiveNodes(type.extensions?.directives as DirectiveList),
fields: fieldNodes(type.getFields(), schema),
directives: directiveNodes(type.extensions?.directives as DirectiveList, null, schema),
};
} else if (type instanceof GraphQLUnionType) {
type.astNode = {
kind: Kind.UNION_TYPE_DEFINITION,
name: { kind: Kind.NAME, value: typeName },
description: type.description ? { kind: Kind.STRING, value: type.description } : undefined,
types: type.getTypes().map((iface) => typeNode(iface) as NamedTypeNode),
directives: directiveNodes(type.extensions?.directives as DirectiveList),
directives: directiveNodes(type.extensions?.directives as DirectiveList, null, schema),
};
} else if (type instanceof GraphQLEnumType) {
type.astNode = {
kind: Kind.ENUM_TYPE_DEFINITION,
name: { kind: Kind.NAME, value: typeName },
description: type.description ? { kind: Kind.STRING, value: type.description } : undefined,
values: enumValueNodes(type.getValues()),
directives: directiveNodes(type.extensions?.directives as DirectiveList),
values: enumValueNodes(type.getValues(), schema),
directives: directiveNodes(type.extensions?.directives as DirectiveList, null, schema),
};
} else if (type instanceof GraphQLScalarType) {
type.astNode = {
kind: Kind.SCALAR_TYPE_DEFINITION,
name: { kind: Kind.NAME, value: typeName },
description: type.description ? { kind: Kind.STRING, value: type.description } : undefined,
directives: directiveNodes(type.extensions?.directives as DirectiveList),
directives: directiveNodes(type.extensions?.directives as DirectiveList, null, schema),
};
} else if (type instanceof GraphQLInputObjectType) {
type.astNode = {
kind: Kind.INPUT_OBJECT_TYPE_DEFINITION,
name: { kind: Kind.NAME, value: typeName },
description: type.description ? { kind: Kind.STRING, value: type.description } : undefined,
fields: inputFieldNodes(type.getFields()),
directives: directiveNodes(type.extensions?.directives as DirectiveList),
fields: inputFieldNodes(type.getFields(), schema),
directives: directiveNodes(type.extensions?.directives as DirectiveList, null, schema),
};
}
}
Expand All @@ -140,13 +140,17 @@ function typeNode(type: GraphQLType): TypeNode {
return { kind: Kind.NAMED_TYPE, name: { kind: Kind.NAME, value: type.name } };
}

function valueNode(value: unknown): ValueNode {
function valueNode(value: unknown, arg?: GraphQLArgument): ValueNode {
if (value == null) {
return { kind: Kind.NULL };
}

if (arg) {
return astFromValue(value, arg.type) as ValueNode;
}

if (Array.isArray(value)) {
return { kind: Kind.LIST, values: value.map(valueNode) };
return { kind: Kind.LIST, values: value.map((val) => valueNode(val)) };
}

switch (typeof value) {
Expand All @@ -166,7 +170,8 @@ function valueNode(value: unknown): ValueNode {

function directiveNodes(
directives: DirectiveList | Record<string, object> | undefined,
deprecationReason?: string | null,
deprecationReason: string | null,
schema: GraphQLSchema,
): readonly ConstDirectiveNode[] {
if (!directives) {
return [];
Expand Down Expand Up @@ -195,8 +200,10 @@ function directiveNodes(
});
}

return directiveList.map(
(directive): DirectiveNode => ({
return directiveList.map((directive): DirectiveNode => {
const directiveDef = schema.getDirective(directive.name);
directiveDef?.args.find((arg) => arg.name);
return {
kind: Kind.DIRECTIVE,
name: { kind: Kind.NAME, value: directive.name },
arguments:
Expand All @@ -205,34 +212,44 @@ function directiveNodes(
(argName): ArgumentNode => ({
kind: Kind.ARGUMENT,
name: { kind: Kind.NAME, value: argName },
value: valueNode((directive.args as Record<string, unknown>)[argName]),
value: valueNode(
(directive.args as Record<string, unknown>)[argName],
directiveDef?.args.find((arg) => arg.name === argName),
),
}),
),
}),
) as readonly ConstDirectiveNode[];
};
}) as readonly ConstDirectiveNode[];
}

function fieldNodes(fields: GraphQLFieldMap<unknown, unknown>): FieldDefinitionNode[] {
function fieldNodes(
fields: GraphQLFieldMap<unknown, unknown>,
schema: GraphQLSchema,
): FieldDefinitionNode[] {
return Object.keys(fields).map((fieldName) => {
const field: GraphQLField<unknown, unknown> = fields[fieldName];

field.astNode = {
kind: Kind.FIELD_DEFINITION,
description: field.description ? { kind: Kind.STRING, value: field.description } : undefined,
name: { kind: Kind.NAME, value: fieldName },
arguments: argumentNodes(field.args),
arguments: argumentNodes(field.args, schema),
type: typeNode(field.type),
directives: directiveNodes(
field.extensions?.directives as DirectiveList,
field.deprecationReason,
field.deprecationReason ?? null,
schema,
),
};

return field.astNode!;
});
}

function inputFieldNodes(fields: GraphQLInputFieldMap): InputValueDefinitionNode[] {
function inputFieldNodes(
fields: GraphQLInputFieldMap,
schema: GraphQLSchema,
): InputValueDefinitionNode[] {
return Object.keys(fields).map((fieldName) => {
const field: GraphQLInputField = fields[fieldName];

Expand All @@ -246,15 +263,19 @@ function inputFieldNodes(fields: GraphQLInputFieldMap): InputValueDefinitionNode
defaultValue: field.defaultValue === undefined ? undefined : defaultValueNode,
directives: directiveNodes(
field.extensions?.directives as DirectiveList,
field.deprecationReason,
field.deprecationReason ?? null,
schema,
),
};

return field.astNode!;
});
}

function argumentNodes(args: readonly GraphQLArgument[]): InputValueDefinitionNode[] {
function argumentNodes(
args: readonly GraphQLArgument[],
schema: GraphQLSchema,
): InputValueDefinitionNode[] {
return args.map((arg): InputValueDefinitionNode => {
const defaultValueNode = astFromValue(arg.defaultValue, arg.type) as ConstValueNode;

Expand All @@ -266,23 +287,28 @@ function argumentNodes(args: readonly GraphQLArgument[]): InputValueDefinitionNo
defaultValue: arg.defaultValue === undefined ? undefined : defaultValueNode,
directives: directiveNodes(
arg.extensions?.directives as DirectiveList,
arg.deprecationReason,
arg.deprecationReason ?? null,
schema,
),
};

return arg.astNode;
});
}

function enumValueNodes(values: readonly GraphQLEnumValue[]): readonly EnumValueDefinitionNode[] {
function enumValueNodes(
values: readonly GraphQLEnumValue[],
schema: GraphQLSchema,
): readonly EnumValueDefinitionNode[] {
return values.map((value): EnumValueDefinitionNode => {
value.astNode = {
kind: Kind.ENUM_VALUE_DEFINITION,
description: value.description ? { kind: Kind.STRING, value: value.description } : undefined,
name: { kind: Kind.NAME, value: value.name },
directives: directiveNodes(
value.extensions?.directives as DirectiveList,
value.deprecationReason,
value.deprecationReason ?? null,
schema,
),
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`extends example schema generates expected schema 1`] = `
"scalar Date
""""Marks a field as cacheable"""
directive @cacheControl(
"""Inherit max age from parent"""
inheritMaxAge: Boolean
"""The maximum age of the cache in seconds"""
maxAge: Int
"""The scope for the cache"""
scope: CacheControlScope = PRIVATE
) on FIELD_DEFINITION | INTERFACE | OBJECT | UNION
enum CacheControlScope {
PRIVATE
PUBLIC
}
scalar Date
enum EN {
ONE
Expand Down Expand Up @@ -35,8 +52,72 @@ type Obj {
}
type Query {
cacheControlPrivate: String
cacheControlPublic: String
test(arg1: String, myInput: MyInput, myOtherInput: MyOtherInput = {}): String
}
union UN = Obj"
`;

exports[`extends example schema generates expected schema with directives 1`] = `
"schema {
query: Query
}
"""Marks a field as cacheable"""
directive @cacheControl(
"""Inherit max age from parent"""
inheritMaxAge: Boolean
"""The maximum age of the cache in seconds"""
maxAge: Int
"""The scope for the cache"""
scope: CacheControlScope = PRIVATE
) on FIELD_DEFINITION | INTERFACE | OBJECT | UNION
enum CacheControlScope {
PRIVATE
PUBLIC
}
scalar Date @s(foo: 123)
enum EN @e(foo: 123) {
ONE @ev(foo: 123)
TWO
}
interface IF @i(foo: 123) {
field: String
}
input In @io(foo: 123) {
test: String @if(foo: 123)
}
input MyInput {
booleanWithDefault: Boolean = false
enumWithDefault: EN = TWO
id: ID!
idWithDefault: ID = 123
ids: [ID!]!
idsWithDefault: [ID!] = [123, 456]
stringWithDefault: String = "default string"
}
input MyOtherInput {
booleanWithDefault: Boolean = false
}
type Obj @o(foo: 123) {
field: String
}
type Query @o(foo: 123) @rateLimit(limit: 1, duration: 5) {
cacheControlPrivate: String @cacheControl(scope: PRIVATE, maxAge: 100, inheritMaxAge: true)
cacheControlPublic: String @cacheControl(scope: PUBLIC, maxAge: 100, inheritMaxAge: true)
test(arg1: String @a(foo: 123), myInput: MyInput, myOtherInput: MyOtherInput = {}): String @f(foo: 123)
}
union UN @u(foo: 123) = Obj"
`;
8 changes: 8 additions & 0 deletions packages/plugin-directives/tests/example/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ type DirectiveTypes = {
duration: number;
};
};
cacheControl: {
locations: 'FIELD_DEFINITION' | 'OBJECT' | 'INTERFACE' | 'UNION';
args: {
scope?: 'PRIVATE' | 'PUBLIC';
maxAge?: number;
inheritMaxAge?: boolean;
};
};
s: {
locations: 'SCALAR';
args: { foo: number };
Expand Down
Loading

0 comments on commit ea9981f

Please sign in to comment.