From e1fb8bb85b2c075ca37d510d5ffd56031d8898da Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Tue, 21 Nov 2023 20:01:00 +0300 Subject: [PATCH] fix: merge directives in extensions --- .changeset/fair-humans-flow.md | 6 ++ .../__snapshots__/merge-schemas.spec.ts.snap | 11 +++ packages/schema/tests/merge-schemas.spec.ts | 84 ++++++++++++++++++- .../utils/src/extractExtensionsFromSchema.ts | 58 ++++++++++--- packages/utils/src/mergeDeep.ts | 14 ++++ packages/utils/tests/mergeDeep.test.ts | 6 ++ 6 files changed, 167 insertions(+), 12 deletions(-) create mode 100644 .changeset/fair-humans-flow.md create mode 100644 packages/schema/tests/__snapshots__/merge-schemas.spec.ts.snap diff --git a/.changeset/fair-humans-flow.md b/.changeset/fair-humans-flow.md new file mode 100644 index 00000000000..6a8b33346c2 --- /dev/null +++ b/.changeset/fair-humans-flow.md @@ -0,0 +1,6 @@ +--- +'@graphql-tools/schema': patch +'@graphql-tools/utils': patch +--- + +Merge directives in the extensions diff --git a/packages/schema/tests/__snapshots__/merge-schemas.spec.ts.snap b/packages/schema/tests/__snapshots__/merge-schemas.spec.ts.snap new file mode 100644 index 00000000000..19b94254288 --- /dev/null +++ b/packages/schema/tests/__snapshots__/merge-schemas.spec.ts.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Merge Schemas should merge schemas with directives in extensions 1`] = ` +"schema @onSchema(name: "a") @onSchema(name: "b") { + query: Query +} + +type Query @onType(name: "a") @onType(name: "b") { + a: String @onField(name: "a") @onField(name: "b") +}" +`; diff --git a/packages/schema/tests/merge-schemas.spec.ts b/packages/schema/tests/merge-schemas.spec.ts index 201843c1573..024c763284d 100644 --- a/packages/schema/tests/merge-schemas.spec.ts +++ b/packages/schema/tests/merge-schemas.spec.ts @@ -1,4 +1,13 @@ -import { buildSchema, graphql, GraphQLScalarType, GraphQLSchema, Kind, print } from 'graphql'; +import { + buildSchema, + graphql, + GraphQLObjectType, + GraphQLScalarType, + GraphQLSchema, + GraphQLString, + Kind, + print, +} from 'graphql'; import { makeExecutableSchema } from '@graphql-tools/schema'; import { assertSome, printSchemaWithDirectives } from '@graphql-tools/utils'; import { assertListValueNode } from '../../testing/assertion.js'; @@ -557,4 +566,77 @@ type City { }`.trim(), ); }); + it('should merge schemas with directives in extensions', () => { + const aSchema = new GraphQLSchema({ + extensions: { + directives: { + onSchema: { + name: 'a', + }, + }, + }, + query: new GraphQLObjectType({ + name: 'Query', + extensions: { + directives: { + onType: { + name: 'a', + }, + }, + }, + fields: { + a: { + type: GraphQLString, + resolve: () => 'a', + extensions: { + directives: { + onField: { + name: 'a', + }, + }, + }, + }, + }, + }), + }); + const bSchema = new GraphQLSchema({ + extensions: { + directives: { + onSchema: { + name: 'b', + }, + }, + }, + query: new GraphQLObjectType({ + name: 'Query', + extensions: { + directives: { + onType: { + name: 'b', + }, + }, + }, + fields: { + a: { + type: GraphQLString, + resolve: () => 'a', + extensions: { + directives: { + onField: { + name: 'b', + }, + }, + }, + }, + }, + }), + }); + const mergedSchema = mergeSchemas({ + schemas: [aSchema, bSchema], + assumeValid: true, + assumeValidSDL: true, + }); + const printedSchema = printSchemaWithDirectives(mergedSchema); + expect(printedSchema.trim()).toMatchSnapshot(); + }); }); diff --git a/packages/utils/src/extractExtensionsFromSchema.ts b/packages/utils/src/extractExtensionsFromSchema.ts index 720f175e73d..897a815ef80 100644 --- a/packages/utils/src/extractExtensionsFromSchema.ts +++ b/packages/utils/src/extractExtensionsFromSchema.ts @@ -8,62 +8,98 @@ import { SchemaExtensions, } from './types.js'; +function handleDirectiveExtensions(extensions: any = {}) { + const finalExtensions: any = { + ...extensions, + }; + const directives = finalExtensions.directives; + if (directives != null) { + for (const directiveName in directives) { + const directiveObj = directives[directiveName]; + if (!Array.isArray(directiveObj)) { + directives[directiveName] = [directiveObj]; + } + } + } + return finalExtensions; +} + export function extractExtensionsFromSchema(schema: GraphQLSchema): SchemaExtensions { const result: SchemaExtensions = { - schemaExtensions: schema.extensions || {}, + schemaExtensions: handleDirectiveExtensions(schema.extensions), types: {}, }; mapSchema(schema, { [MapperKind.OBJECT_TYPE]: type => { - result.types[type.name] = { fields: {}, type: 'object', extensions: type.extensions || {} }; + result.types[type.name] = { + fields: {}, + type: 'object', + extensions: handleDirectiveExtensions(type.extensions), + }; return type; }, [MapperKind.INTERFACE_TYPE]: type => { result.types[type.name] = { fields: {}, type: 'interface', - extensions: type.extensions || {}, + extensions: handleDirectiveExtensions(type.extensions), }; return type; }, [MapperKind.FIELD]: (field, fieldName, typeName) => { (result.types[typeName] as ObjectTypeExtensions).fields[fieldName] = { arguments: {}, - extensions: field.extensions || {}, + extensions: handleDirectiveExtensions(field.extensions), }; const args = (field as GraphQLFieldConfig).args; if (args != null) { for (const argName in args) { (result.types[typeName] as ObjectTypeExtensions).fields[fieldName].arguments[argName] = - args[argName].extensions || {}; + handleDirectiveExtensions(args[argName].extensions); } } return field; }, [MapperKind.ENUM_TYPE]: type => { - result.types[type.name] = { values: {}, type: 'enum', extensions: type.extensions || {} }; + result.types[type.name] = { + values: {}, + type: 'enum', + extensions: handleDirectiveExtensions(type.extensions), + }; return type; }, [MapperKind.ENUM_VALUE]: (value, typeName, _schema, valueName) => { - (result.types[typeName] as EnumTypeExtensions).values[valueName] = value.extensions || {}; + (result.types[typeName] as EnumTypeExtensions).values[valueName] = handleDirectiveExtensions( + value.extensions, + ); return value; }, [MapperKind.SCALAR_TYPE]: type => { - result.types[type.name] = { type: 'scalar', extensions: type.extensions || {} }; + result.types[type.name] = { + type: 'scalar', + extensions: handleDirectiveExtensions(type.extensions), + }; return type; }, [MapperKind.UNION_TYPE]: type => { - result.types[type.name] = { type: 'union', extensions: type.extensions || {} }; + result.types[type.name] = { + type: 'union', + extensions: handleDirectiveExtensions(type.extensions), + }; return type; }, [MapperKind.INPUT_OBJECT_TYPE]: type => { - result.types[type.name] = { fields: {}, type: 'input', extensions: type.extensions || {} }; + result.types[type.name] = { + fields: {}, + type: 'input', + extensions: handleDirectiveExtensions(type.extensions), + }; return type; }, [MapperKind.INPUT_OBJECT_FIELD]: (field, fieldName, typeName) => { (result.types[typeName] as InputTypeExtensions).fields[fieldName] = { - extensions: field.extensions || {}, + extensions: handleDirectiveExtensions(field.extensions), }; return field; }, diff --git a/packages/utils/src/mergeDeep.ts b/packages/utils/src/mergeDeep.ts index 533e2f88471..503a0ddfa02 100644 --- a/packages/utils/src/mergeDeep.ts +++ b/packages/utils/src/mergeDeep.ts @@ -37,10 +37,24 @@ export function mergeDeep( } else { output[key] = mergeDeep([output[key], source[key]] as S, respectPrototype); } + } else if (Array.isArray(output[key])) { + if (Array.isArray(source[key])) { + output[key].push(...source[key]); + } else { + output[key].push(source[key]); + } } else { Object.assign(output, { [key]: source[key] }); } } + } else if (Array.isArray(target)) { + if (Array.isArray(source)) { + target.push(...source); + } else { + target.push(source); + } + } else if (Array.isArray(source)) { + return [target, ...source]; } } return output; diff --git a/packages/utils/tests/mergeDeep.test.ts b/packages/utils/tests/mergeDeep.test.ts index 3eb0f434783..7ba850106f1 100644 --- a/packages/utils/tests/mergeDeep.test.ts +++ b/packages/utils/tests/mergeDeep.test.ts @@ -52,4 +52,10 @@ describe('mergeDeep', () => { expect(merged.one.b()).toEqual('b'); expect(merged.a).toBeUndefined(); }); + + it('merges arrays', () => { + const x = { a: [1, 2] }; + const y = { a: [3, 4] }; + expect(mergeDeep([x, y])).toEqual({ a: [1, 2, 3, 4] }); + }); });