Skip to content

Commit

Permalink
fix: merge directives in extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan committed Nov 21, 2023
1 parent c59663d commit e1fb8bb
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 12 deletions.
6 changes: 6 additions & 0 deletions .changeset/fair-humans-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@graphql-tools/schema': patch
'@graphql-tools/utils': patch
---

Merge directives in the extensions
11 changes: 11 additions & 0 deletions packages/schema/tests/__snapshots__/merge-schemas.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -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")
}"
`;
84 changes: 83 additions & 1 deletion packages/schema/tests/merge-schemas.spec.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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();
});
});
58 changes: 47 additions & 11 deletions packages/utils/src/extractExtensionsFromSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any, any>).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;
},
Expand Down
14 changes: 14 additions & 0 deletions packages/utils/src/mergeDeep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,24 @@ export function mergeDeep<S extends any[]>(
} 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;
Expand Down
6 changes: 6 additions & 0 deletions packages/utils/tests/mergeDeep.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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] });
});
});

0 comments on commit e1fb8bb

Please sign in to comment.