Skip to content

Commit e1fb8bb

Browse files
committed
fix: merge directives in extensions
1 parent c59663d commit e1fb8bb

File tree

6 files changed

+167
-12
lines changed

6 files changed

+167
-12
lines changed

.changeset/fair-humans-flow.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@graphql-tools/schema': patch
3+
'@graphql-tools/utils': patch
4+
---
5+
6+
Merge directives in the extensions
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Merge Schemas should merge schemas with directives in extensions 1`] = `
4+
"schema @onSchema(name: "a") @onSchema(name: "b") {
5+
query: Query
6+
}
7+
8+
type Query @onType(name: "a") @onType(name: "b") {
9+
a: String @onField(name: "a") @onField(name: "b")
10+
}"
11+
`;

packages/schema/tests/merge-schemas.spec.ts

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
import { buildSchema, graphql, GraphQLScalarType, GraphQLSchema, Kind, print } from 'graphql';
1+
import {
2+
buildSchema,
3+
graphql,
4+
GraphQLObjectType,
5+
GraphQLScalarType,
6+
GraphQLSchema,
7+
GraphQLString,
8+
Kind,
9+
print,
10+
} from 'graphql';
211
import { makeExecutableSchema } from '@graphql-tools/schema';
312
import { assertSome, printSchemaWithDirectives } from '@graphql-tools/utils';
413
import { assertListValueNode } from '../../testing/assertion.js';
@@ -557,4 +566,77 @@ type City {
557566
}`.trim(),
558567
);
559568
});
569+
it('should merge schemas with directives in extensions', () => {
570+
const aSchema = new GraphQLSchema({
571+
extensions: {
572+
directives: {
573+
onSchema: {
574+
name: 'a',
575+
},
576+
},
577+
},
578+
query: new GraphQLObjectType({
579+
name: 'Query',
580+
extensions: {
581+
directives: {
582+
onType: {
583+
name: 'a',
584+
},
585+
},
586+
},
587+
fields: {
588+
a: {
589+
type: GraphQLString,
590+
resolve: () => 'a',
591+
extensions: {
592+
directives: {
593+
onField: {
594+
name: 'a',
595+
},
596+
},
597+
},
598+
},
599+
},
600+
}),
601+
});
602+
const bSchema = new GraphQLSchema({
603+
extensions: {
604+
directives: {
605+
onSchema: {
606+
name: 'b',
607+
},
608+
},
609+
},
610+
query: new GraphQLObjectType({
611+
name: 'Query',
612+
extensions: {
613+
directives: {
614+
onType: {
615+
name: 'b',
616+
},
617+
},
618+
},
619+
fields: {
620+
a: {
621+
type: GraphQLString,
622+
resolve: () => 'a',
623+
extensions: {
624+
directives: {
625+
onField: {
626+
name: 'b',
627+
},
628+
},
629+
},
630+
},
631+
},
632+
}),
633+
});
634+
const mergedSchema = mergeSchemas({
635+
schemas: [aSchema, bSchema],
636+
assumeValid: true,
637+
assumeValidSDL: true,
638+
});
639+
const printedSchema = printSchemaWithDirectives(mergedSchema);
640+
expect(printedSchema.trim()).toMatchSnapshot();
641+
});
560642
});

packages/utils/src/extractExtensionsFromSchema.ts

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,62 +8,98 @@ import {
88
SchemaExtensions,
99
} from './types.js';
1010

11+
function handleDirectiveExtensions(extensions: any = {}) {
12+
const finalExtensions: any = {
13+
...extensions,
14+
};
15+
const directives = finalExtensions.directives;
16+
if (directives != null) {
17+
for (const directiveName in directives) {
18+
const directiveObj = directives[directiveName];
19+
if (!Array.isArray(directiveObj)) {
20+
directives[directiveName] = [directiveObj];
21+
}
22+
}
23+
}
24+
return finalExtensions;
25+
}
26+
1127
export function extractExtensionsFromSchema(schema: GraphQLSchema): SchemaExtensions {
1228
const result: SchemaExtensions = {
13-
schemaExtensions: schema.extensions || {},
29+
schemaExtensions: handleDirectiveExtensions(schema.extensions),
1430
types: {},
1531
};
1632

1733
mapSchema(schema, {
1834
[MapperKind.OBJECT_TYPE]: type => {
19-
result.types[type.name] = { fields: {}, type: 'object', extensions: type.extensions || {} };
35+
result.types[type.name] = {
36+
fields: {},
37+
type: 'object',
38+
extensions: handleDirectiveExtensions(type.extensions),
39+
};
2040
return type;
2141
},
2242
[MapperKind.INTERFACE_TYPE]: type => {
2343
result.types[type.name] = {
2444
fields: {},
2545
type: 'interface',
26-
extensions: type.extensions || {},
46+
extensions: handleDirectiveExtensions(type.extensions),
2747
};
2848
return type;
2949
},
3050
[MapperKind.FIELD]: (field, fieldName, typeName) => {
3151
(result.types[typeName] as ObjectTypeExtensions).fields[fieldName] = {
3252
arguments: {},
33-
extensions: field.extensions || {},
53+
extensions: handleDirectiveExtensions(field.extensions),
3454
};
3555
const args = (field as GraphQLFieldConfig<any, any>).args;
3656
if (args != null) {
3757
for (const argName in args) {
3858
(result.types[typeName] as ObjectTypeExtensions).fields[fieldName].arguments[argName] =
39-
args[argName].extensions || {};
59+
handleDirectiveExtensions(args[argName].extensions);
4060
}
4161
}
4262
return field;
4363
},
4464
[MapperKind.ENUM_TYPE]: type => {
45-
result.types[type.name] = { values: {}, type: 'enum', extensions: type.extensions || {} };
65+
result.types[type.name] = {
66+
values: {},
67+
type: 'enum',
68+
extensions: handleDirectiveExtensions(type.extensions),
69+
};
4670
return type;
4771
},
4872
[MapperKind.ENUM_VALUE]: (value, typeName, _schema, valueName) => {
49-
(result.types[typeName] as EnumTypeExtensions).values[valueName] = value.extensions || {};
73+
(result.types[typeName] as EnumTypeExtensions).values[valueName] = handleDirectiveExtensions(
74+
value.extensions,
75+
);
5076
return value;
5177
},
5278
[MapperKind.SCALAR_TYPE]: type => {
53-
result.types[type.name] = { type: 'scalar', extensions: type.extensions || {} };
79+
result.types[type.name] = {
80+
type: 'scalar',
81+
extensions: handleDirectiveExtensions(type.extensions),
82+
};
5483
return type;
5584
},
5685
[MapperKind.UNION_TYPE]: type => {
57-
result.types[type.name] = { type: 'union', extensions: type.extensions || {} };
86+
result.types[type.name] = {
87+
type: 'union',
88+
extensions: handleDirectiveExtensions(type.extensions),
89+
};
5890
return type;
5991
},
6092
[MapperKind.INPUT_OBJECT_TYPE]: type => {
61-
result.types[type.name] = { fields: {}, type: 'input', extensions: type.extensions || {} };
93+
result.types[type.name] = {
94+
fields: {},
95+
type: 'input',
96+
extensions: handleDirectiveExtensions(type.extensions),
97+
};
6298
return type;
6399
},
64100
[MapperKind.INPUT_OBJECT_FIELD]: (field, fieldName, typeName) => {
65101
(result.types[typeName] as InputTypeExtensions).fields[fieldName] = {
66-
extensions: field.extensions || {},
102+
extensions: handleDirectiveExtensions(field.extensions),
67103
};
68104
return field;
69105
},

packages/utils/src/mergeDeep.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,24 @@ export function mergeDeep<S extends any[]>(
3737
} else {
3838
output[key] = mergeDeep([output[key], source[key]] as S, respectPrototype);
3939
}
40+
} else if (Array.isArray(output[key])) {
41+
if (Array.isArray(source[key])) {
42+
output[key].push(...source[key]);
43+
} else {
44+
output[key].push(source[key]);
45+
}
4046
} else {
4147
Object.assign(output, { [key]: source[key] });
4248
}
4349
}
50+
} else if (Array.isArray(target)) {
51+
if (Array.isArray(source)) {
52+
target.push(...source);
53+
} else {
54+
target.push(source);
55+
}
56+
} else if (Array.isArray(source)) {
57+
return [target, ...source];
4458
}
4559
}
4660
return output;

packages/utils/tests/mergeDeep.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,10 @@ describe('mergeDeep', () => {
5252
expect(merged.one.b()).toEqual('b');
5353
expect(merged.a).toBeUndefined();
5454
});
55+
56+
it('merges arrays', () => {
57+
const x = { a: [1, 2] };
58+
const y = { a: [3, 4] };
59+
expect(mergeDeep([x, y])).toEqual({ a: [1, 2, 3, 4] });
60+
});
5561
});

0 commit comments

Comments
 (0)