Skip to content

Commit

Permalink
should handle extend types when GraphQLSchema is the source (#238)
Browse files Browse the repository at this point in the history
* should handle extend types when GraphQLSchema is the source

* Merge with fields from object extensions

* should fail when a field is already defined and has a different type

* should preserve an extend keyword if there is no base

* Mistake

* Much nicer

* Update merge-schema.ts
  • Loading branch information
kamilkisiela committed Dec 24, 2018
1 parent 3168aef commit 69c5667
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 13 deletions.
57 changes: 44 additions & 13 deletions packages/epoxy/src/schema-mergers/merge-schema.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
import { buildASTSchema, printSchema, DefinitionNode, DocumentNode, GraphQLSchema, parse, print, Source, GraphQLObjectType, isSpecifiedScalarType, isIntrospectionType, GraphQLScalarType, printType } from 'graphql';
import {
buildASTSchema,
printSchema,
DefinitionNode,
DocumentNode,
GraphQLSchema,
parse,
print,
Source,
GraphQLObjectType,
isSpecifiedScalarType,
isIntrospectionType,
GraphQLScalarType,
printType,
ObjectTypeExtensionNode,
GraphQLNamedType,
} from 'graphql';
import { isGraphQLSchema, isSourceTypes, isStringTypes, isSchemaDefinition } from './utils';
import { MergedResultMap, mergeGraphQLNodes } from './merge-nodes';

interface Config {
useSchemaDefinition?: boolean;
}

export function mergeGraphQLSchemas(
types: Array<string | Source | DocumentNode | GraphQLSchema>,
config?: Partial<Config>,
): DocumentNode {
export function mergeGraphQLSchemas(types: Array<string | Source | DocumentNode | GraphQLSchema>, config?: Partial<Config>): DocumentNode {
return {
kind: 'Document',
definitions: mergeGraphQLTypes(types, {
Expand All @@ -29,9 +42,9 @@ function createSchemaDefinition(def: {
subscription: string | GraphQLObjectType | null;
}): string {
const schemaRoot: {
query?: string,
mutation?: string,
subscription?: string,
query?: string;
mutation?: string;
subscription?: string;
} = {};

if (def.query) {
Expand All @@ -55,10 +68,7 @@ function createSchemaDefinition(def: {
return undefined;
}

export function mergeGraphQLTypes(
types: Array<string | Source | DocumentNode | GraphQLSchema>,
config: Config,
): DefinitionNode[] {
export function mergeGraphQLTypes(types: Array<string | Source | DocumentNode | GraphQLSchema>, config: Config): DefinitionNode[] {
const allNodes: ReadonlyArray<DefinitionNode> = types
.map<DocumentNode>(type => {
if (isGraphQLSchema(type)) {
Expand All @@ -84,7 +94,13 @@ export function mergeGraphQLTypes(

return !isPredefinedScalar && !isIntrospection;
})
.map(type => (type.astNode ? print(type.astNode) : printType(type)))
.map(type => {
if (type.astNode) {
return print(type.extensionASTNodes ? extendDefinition(type) : type.astNode);
} else {
return printType(type);
}
})
.filter(e => e);
const directivesDeclaration = schema
.getDirectives()
Expand Down Expand Up @@ -145,3 +161,18 @@ export function mergeGraphQLTypes(

return [...Object.values(mergedNodes), parse(schemaDefinition).definitions[0]];
}

function extendDefinition(type: GraphQLNamedType): GraphQLNamedType['astNode'] {
switch (type.astNode.kind) {
case 'ObjectTypeDefinition':
return {
...type.astNode,
// add fields from object extension (`extend type Query { newField: String }`)
fields: type.astNode.fields.concat(
(type.extensionASTNodes as ReadonlyArray<ObjectTypeExtensionNode>).reduce((fields, node) => fields.concat(node.fields), []),
),
};
default:
return type.astNode;
}
}
73 changes: 73 additions & 0 deletions packages/epoxy/tests/merge-schema.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,79 @@ describe('Merge Schema', () => {
}
`));
});
it('should handle extend types when GraphQLSchema is the source', () => {
const schema = makeExecutableSchema({
typeDefs: [`
type Query {
foo: String
}
type User {
name: String
}
`, `
extend type Query {
bar: String
}
extend type User {
id: ID
}
`],
});
const merged = mergeGraphQLSchemas([schema]);
const printed = stripWhitespaces(print(merged));

expect(printed).toContain(stripWhitespaces(`
type Query {
foo: String
bar: String
}
`));
expect(printed).toContain(stripWhitespaces(`
type User {
name: String
id: ID
}
`));
});

it('should fail when a field is already defined and has a different type', () => {
expect(() => {
mergeGraphQLSchemas([`
type Query {
foo: String
}
`, `
extend type Query {
foo: Int
bar: String
}
`]);
}).toThrowError('Unable to merge GraphQL type');
});

it('should preserve an extend keyword if there is no base', () => {
const merged = mergeGraphQLSchemas([`
extend type Query {
foo: String
}
`, `
extend type Query {
bar: String
}
`]);

const printed = stripWhitespaces(print(merged));

expect(printed).toContain(stripWhitespaces(`
extend type Query {
foo: String
bar: String
}
`));
});

it('should handle extend inputs', () => {
const merged = mergeGraphQLSchemas([`
input TestInput {
Expand Down

0 comments on commit 69c5667

Please sign in to comment.