Skip to content

Commit 2727502

Browse files
authored
Merge pull request #1 from XiNiHa/feat/transform-directives
2 parents b674bc5 + e8d8d57 commit 2727502

File tree

1 file changed

+82
-9
lines changed

1 file changed

+82
-9
lines changed

src/cli.ts

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
import {
22
buildSchema,
3+
GraphQLFieldConfig,
34
GraphQLFieldConfigMap,
4-
GraphQLFieldMap,
55
GraphQLInterfaceType,
66
GraphQLList,
77
GraphQLNamedType,
88
GraphQLNonNull,
99
GraphQLObjectType,
10+
GraphQLOutputType,
1011
GraphQLSchema,
1112
GraphQLSemanticNonNull,
1213
GraphQLType,
1314
GraphQLUnionType,
15+
Kind,
1416
printSchema,
1517
validateSchema,
1618
} from "graphql";
1719
import type { Maybe } from "graphql/jsutils/Maybe";
18-
import { ObjMap } from "graphql/jsutils/ObjMap";
1920
import { readFile, writeFile } from "node:fs/promises";
2021
import { parseArgs } from "node:util";
2122

@@ -59,6 +60,7 @@ export async function main(toStrict = false) {
5960
types: config.types
6061
.filter((t) => !t.name.startsWith("__"))
6162
.map((t) => convertType(t)),
63+
directives: config.directives.filter((d) => d.name !== "semanticNonNull"),
6264
});
6365

6466
const newSdl = printSchema(derivedSchema);
@@ -72,13 +74,16 @@ function makeConvertType(toStrict: boolean) {
7274
function convertFields(fields: GraphQLFieldConfigMap<any, any>) {
7375
return () => {
7476
return Object.fromEntries(
75-
Object.entries(fields).map(([fieldName, spec]) => [
76-
fieldName,
77-
{
78-
...spec,
79-
type: convertType(spec.type),
80-
},
81-
]),
77+
Object.entries(fields).map(([fieldName, inSpec]) => {
78+
const spec = applySemanticNonNullDirective(inSpec);
79+
return [
80+
fieldName,
81+
{
82+
...spec,
83+
type: convertType(spec.type),
84+
},
85+
];
86+
}),
8287
) as any;
8388
};
8489
}
@@ -161,3 +166,71 @@ function makeConvertType(toStrict: boolean) {
161166

162167
return convertType;
163168
}
169+
170+
/**
171+
* Takes a GraphQL field config and checks to see if the `@semanticNonNull`
172+
* directive was applied; if so, converts to a field config using explicit
173+
* GraphQLSemanticNonNull wrapper types instead.
174+
*
175+
* @see {@url https://www.apollographql.com/docs/kotlin/advanced/nullability/#semanticnonnull}
176+
*/
177+
function applySemanticNonNullDirective(
178+
spec: GraphQLFieldConfig<any, any, any>,
179+
): GraphQLFieldConfig<any, any, any> {
180+
const directive = spec.astNode?.directives?.find(
181+
(d) => d.name.value === "semanticNonNull",
182+
);
183+
if (!directive) {
184+
return spec;
185+
}
186+
const levelsArg = directive.arguments?.find((a) => a.name.value === "levels");
187+
const levels =
188+
levelsArg?.value?.kind === Kind.LIST
189+
? levelsArg.value.values
190+
.filter((v) => v.kind === Kind.INT)
191+
.map((v) => Number(v.value))
192+
: [0];
193+
function recurse(type: GraphQLOutputType, level: number): GraphQLOutputType {
194+
if (type instanceof GraphQLSemanticNonNull) {
195+
// Strip semantic-non-null types; this should never happen but if someone
196+
// uses both semantic-non-null and the `@semanticNonNull` directive, we
197+
// want the directive to win (I guess?)
198+
return recurse(type.ofType, level);
199+
} else if (type instanceof GraphQLNonNull) {
200+
const inner = recurse(type.ofType, level);
201+
if (levels.includes(level)) {
202+
// Semantic non-null from `inner` replaces our GrpahQLNonNull wrapper
203+
return inner;
204+
} else {
205+
// Keep non-null wrapper; no semantic-non-null was added to `inner`
206+
return new GraphQLNonNull(inner);
207+
}
208+
} else if (type instanceof GraphQLList) {
209+
const inner = new GraphQLList(recurse(type.ofType, level + 1));
210+
if (levels.includes(level)) {
211+
return new GraphQLSemanticNonNull(inner);
212+
} else {
213+
return inner;
214+
}
215+
} else {
216+
if (levels.includes(level)) {
217+
return new GraphQLSemanticNonNull(type);
218+
} else {
219+
return type;
220+
}
221+
}
222+
}
223+
224+
return {
225+
...spec,
226+
type: recurse(spec.type, 0),
227+
astNode: spec.astNode
228+
? {
229+
...spec.astNode,
230+
directives: spec.astNode.directives?.filter(
231+
(d) => d.name.value !== "semanticNonNull",
232+
),
233+
}
234+
: undefined,
235+
};
236+
}

0 commit comments

Comments
 (0)