Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added flag for mergeTypes to merge all types #118

Merged
merged 6 commits into from
Feb 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 28 additions & 14 deletions src/merge_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import print from './utilities/astPrinter';
import { isObjectTypeDefinition, isObjectSchemaDefinition } from './utilities/astHelpers';
import { makeSchema, mergeableTypes } from './utilities/makeSchema';

const _isMergeableTypeDefinition = def =>
isObjectTypeDefinition(def) && mergeableTypes.includes(def.name.value);
const _isMergeableTypeDefinition = (def, all) =>
isObjectTypeDefinition(def) && (mergeableTypes.includes(def.name.value) || all);

const _isNonMergeableTypeDefinition = def => !_isMergeableTypeDefinition(def);
const _isNonMergeableTypeDefinition = (def, all) => !_isMergeableTypeDefinition(def, all);

const _makeCommentNode = value => ({ kind: 'Comment', value });

Expand All @@ -31,9 +31,9 @@ const _addCommentsToAST = (nodes, flatten = true) => {
return astWithComments;
};

const _makeRestDefinitions = defs =>
const _makeRestDefinitions = (defs, all = false) =>
defs
.filter(def => _isNonMergeableTypeDefinition(def) && !isObjectSchemaDefinition(def))
.filter(def => _isNonMergeableTypeDefinition(def, all) && !isObjectSchemaDefinition(def))
.map((def) => {
if (isObjectTypeDefinition(def)) {
return {
Expand All @@ -45,10 +45,26 @@ const _makeRestDefinitions = defs =>
return def;
});

const _makeMergedDefinitions = (defs) => {
const _makeMergedFieldDefinitions = (merged, candidate) => _addCommentsToAST(candidate.fields)
.reduce((fields, field) => {
const original = merged.fields.find(base => base.name && typeof base.name.value !== 'undefined' &&
field.name && typeof field.name.value !== 'undefined' &&
base.name.value === field.name.value);
if (!original) {
fields.push(field);
} else if (field.type.name.value !== original.type.name.value) {
throw new Error(
`Conflicting types for ${merged.name.value}.${field.name.value}: ` +
`${field.type.name.value} != ${original.type.name.value}`,
);
}
return fields;
}, merged.fields);

const _makeMergedDefinitions = (defs, all = false) => {
// TODO: This function can be cleaner!
const groupedMergableDefinitions = defs
.filter(_isMergeableTypeDefinition)
.filter(def => _isMergeableTypeDefinition(def, all))
.reduce(
(mergableDefs, def) => {
const name = def.name.value;
Expand All @@ -67,10 +83,7 @@ const _makeMergedDefinitions = (defs) => {
...mergableDefs,
[name]: {
...mergableDefs[name],
fields: [
...mergableDefs[name].fields,
..._addCommentsToAST(def.fields),
],
fields: _makeMergedFieldDefinitions(mergableDefs[name], def),
},
};
}, {
Expand All @@ -92,7 +105,7 @@ const _makeDocumentWithDefinitions = definitions => ({

const printDefinitions = defs => print(_makeDocumentWithDefinitions(defs));

const mergeTypes = (types) => {
const mergeTypes = (types, options = { all: false }) => {
const allDefs = types
.map((type) => {
if (typeof type === 'string') {
Expand All @@ -103,8 +116,9 @@ const mergeTypes = (types) => {
.map(ast => ast.definitions)
.reduce((defs, newDef) => [...defs, ...newDef], []);

const mergedDefs = _makeMergedDefinitions(allDefs);
const rest = _addCommentsToAST(_makeRestDefinitions(allDefs), false).map(printDefinitions);
const mergedDefs = _makeMergedDefinitions(allDefs, options.all);
const rest = _addCommentsToAST(_makeRestDefinitions(allDefs, options.all), false)
.map(printDefinitions);
const schemaDefs = allDefs.filter(isObjectSchemaDefinition);
const schema = printDefinitions([makeSchema(mergedDefs, schemaDefs), ...mergedDefs]);

Expand Down
10 changes: 10 additions & 0 deletions test/graphql/other/custom_type/conflicting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default `
type Custom {
id: ID!
age: Int
}
type Custom {
name: String
age: String
}
`;
9 changes: 9 additions & 0 deletions test/graphql/other/custom_type/disjoint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default `
type Custom {
id: ID!
}
type Custom {
name: String
age: Int
}
`;
10 changes: 10 additions & 0 deletions test/graphql/other/custom_type/matching.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default `
type Custom {
id: ID!
age: Int
}
type Custom {
name: String
age: Int
}
`;
14 changes: 14 additions & 0 deletions test/graphql/other/query_type/conflicting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export default `
type Client {
id: ID!
name: String
age: Int
}
type Query {
getClient(id: ID!): Client
}
type Query {
getClient(id: ID!): Boolean
deleteClient(id: ID!): Client
}
`;
13 changes: 13 additions & 0 deletions test/graphql/other/query_type/disjoint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default `
type Client {
id: ID!
name: String
age: Int
}
type Query {
getClient(id: ID!): Client
}
type Query {
deleteClient(id: ID!): Client
}
`;
15 changes: 15 additions & 0 deletions test/graphql/other/query_type/matching.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default `
type Client {
id: ID!
name: String
age: Int
}
type Query {
getClient(id: ID!): Client
deleteClient(id: ID!): Client
}
type Query {
getClient(id: ID!): Client
deleteClient(id: ID!): Client
}
`;
81 changes: 80 additions & 1 deletion test/merge_types.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ import vendorType from './graphql/types/vendor_type';
import personEntityType from './graphql/types/person_entity_type';
import personSearchType from './graphql/types/person_search_type';
import customType from './graphql/other/custom_type';
import disjointCustomTypes from './graphql/other/custom_type/disjoint';
import matchingCustomTypes from './graphql/other/custom_type/matching';
import conflictingCustomTypes from './graphql/other/custom_type/conflicting';

import simpleQueryType from './graphql/other/simple_query_type';
import disjointQueryTypes from './graphql/other/query_type/disjoint';
import matchingQueryTypes from './graphql/other/query_type/matching';
import conflictingQueryTypes from './graphql/other/query_type/conflicting';

const normalizeWhitespace = str => str.replace(/\s+/g, ' ').trim();

Expand Down Expand Up @@ -115,8 +122,43 @@ describe('mergeTypes', () => {
});
});

describe('when query type is present twice', () => {
it('merges disjoint query types', () => {
const types = [disjointQueryTypes];
const mergedTypes = mergeTypes(types);
const expectedSchemaType = normalizeWhitespace(`
type Query {
getClient(id: ID!): Client
deleteClient(id: ID!): Client
}
`);
const separateTypes = normalizeWhitespace(mergedTypes);
expect(separateTypes).toContain(expectedSchemaType);
});

it('merges query types with matching definitions', () => {
const types = [matchingQueryTypes];
const mergedTypes = mergeTypes(types);
const expectedSchemaType = normalizeWhitespace(`
type Query {
getClient(id: ID!): Client
deleteClient(id: ID!): Client
}
`);
const separateTypes = normalizeWhitespace(mergedTypes);
expect(separateTypes).toContain(expectedSchemaType);
});

it('throws on query types with conflicting definitions', () => {
const types = [conflictingQueryTypes];
expect(() => {
mergeTypes(types);
}).toThrow(expect.any(Error));
});
});

describe('when only single custom type is passed', () => {
it('includes customType', () => {
it('includes custom type', () => {
const types = [customType];
const mergedTypes = mergeTypes(types);
const expectedCustomType = normalizeWhitespace(`
Expand Down Expand Up @@ -181,6 +223,43 @@ describe('mergeTypes', () => {
});
});

describe('when custom type is present twice', () => {
it('merges disjoint custom types', () => {
const types = [disjointCustomTypes];
const mergedTypes = mergeTypes(types, { all: true });
const expectedCustomType = normalizeWhitespace(`
type Custom {
id: ID!
name: String
age: Int
}
`);
const separateTypes = normalizeWhitespace(mergedTypes);
expect(separateTypes).toContain(expectedCustomType);
});

it('merges custom types with matching definitions', () => {
const types = [matchingCustomTypes];
const mergedTypes = mergeTypes(types, { all: true });
const expectedCustomType = normalizeWhitespace(`
type Custom {
id: ID!
age: Int
name: String
}
`);
const separateTypes = normalizeWhitespace(mergedTypes);
expect(separateTypes).toContain(expectedCustomType);
});

it('throws on custom types with conflicting definitions', () => {
const types = [conflictingCustomTypes];
expect(() => {
mergeTypes(types, { all: true });
}).toThrow(expect.any(Error));
});
});

it('includes schemaType', () => {
const types = [clientType, productType];
const mergedTypes = mergeTypes(types);
Expand Down