Skip to content

Commit

Permalink
fix(merge): now merge correctly uses nested type instances from the…
Browse files Browse the repository at this point in the history
… new schema (not old one)
  • Loading branch information
nodkz committed Sep 21, 2020
1 parent 36fa150 commit 03b1d39
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 45 deletions.
6 changes: 2 additions & 4 deletions src/EnumTypeComposer.js
Original file line number Diff line number Diff line change
Expand Up @@ -449,10 +449,6 @@ export class EnumTypeComposer<TContext> {
tc = EnumTypeComposer.createTemp(type, this.schemaComposer);
} else if (type instanceof EnumTypeComposer) {
tc = type;
}

if (tc) {
this.addFields(tc.getFields());
} else {
throw new Error(
`Cannot merge ${inspect(
Expand All @@ -461,6 +457,8 @@ export class EnumTypeComposer<TContext> {
);
}

this.addFields(tc.getFields());

return this;
}

Expand Down
17 changes: 12 additions & 5 deletions src/InputTypeComposer.js
Original file line number Diff line number Diff line change
Expand Up @@ -650,15 +650,11 @@ export class InputTypeComposer<TContext> {
}

merge(type: GraphQLInputObjectType | InputTypeComposer<any>): InputTypeComposer<TContext> {
let tc: ?InputTypeComposer<any>;
let tc;
if (type instanceof GraphQLInputObjectType) {
tc = InputTypeComposer.createTemp(type, this.schemaComposer);
} else if (type instanceof InputTypeComposer) {
tc = type;
}

if (tc) {
this.addFields(tc.getFields());
} else {
throw new Error(
`Cannot merge ${inspect(
Expand All @@ -667,6 +663,17 @@ export class InputTypeComposer<TContext> {
);
}

// deep clone all fields
const fields = ({ ...tc.getFields() }: any);
Object.keys(fields).forEach((fieldName) => {
fields[fieldName] = {
...fields[fieldName],
// set type as SDL string, it automatically will be remapped to the correct type instance in the current schema
type: tc.getFieldTypeName(fieldName),
};
});
this.addFields(fields);

return this;
}

Expand Down
42 changes: 33 additions & 9 deletions src/InterfaceTypeComposer.js
Original file line number Diff line number Diff line change
Expand Up @@ -938,16 +938,13 @@ export class InterfaceTypeComposer<TSource, TContext> {
| InterfaceTypeComposer<any, any>
| ObjectTypeComposer<any, any>
): InterfaceTypeComposer<TSource, TContext> {
let tc: ?InterfaceTypeComposer<any, any> | ?ObjectTypeComposer<any, any>;
if (type instanceof GraphQLInterfaceType || type instanceof GraphQLObjectType) {
tc = (this.schemaComposer.createTempTC(type): any);
} else if (type instanceof InterfaceTypeComposer || type instanceof ObjectTypeComposer) {
let tc;
if (type instanceof ObjectTypeComposer || type instanceof InterfaceTypeComposer) {
tc = type;
}

if (tc) {
this.addFields(tc.getFields());
this.addInterfaces(tc.getInterfaces());
} else if (type instanceof GraphQLObjectType) {
tc = ObjectTypeComposer.createTemp(type, this.schemaComposer);
} else if (type instanceof GraphQLInterfaceType) {
tc = InterfaceTypeComposer.createTemp(type, this.schemaComposer);
} else {
throw new Error(
`Cannot merge ${inspect(
Expand All @@ -956,6 +953,33 @@ export class InterfaceTypeComposer<TSource, TContext> {
);
}

// deep clone all fields with args
const fields = ({ ...tc.getFields() }: any);
Object.keys(fields).forEach((fieldName) => {
fields[fieldName] = {
...fields[fieldName],
args: {
...fields[fieldName].args,
},
// set type as SDL string, it automatically will be remapped to the correct type instance in the current schema
type: tc.getFieldTypeName(fieldName),
};
tc.getFieldArgNames(fieldName).forEach((argName) => {
fields[fieldName].args[argName] = {
...fields[fieldName].args[argName],
// set type as SDL string, it automatically will be remapped to the correct type instance in the current schema
type: tc.getFieldArgTypeName(fieldName, argName),
};
});
});
this.addFields(fields);

// set interfaces as SDL string, it automatically will be remapped to the correct type instance in the current schema
this.addInterfaces(tc.getInterfaces().map((i) => i.getTypeName()));

// Feel free to add other properties for merging two TypeComposers.
// For simplicity it just merge fields and interfaces.

return this;
}

Expand Down
48 changes: 34 additions & 14 deletions src/ObjectTypeComposer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1125,28 +1125,48 @@ export class ObjectTypeComposer<TSource, TContext> {
| ObjectTypeComposer<any, any>
| InterfaceTypeComposer<any, any>
): ObjectTypeComposer<TSource, TContext> {
if (type instanceof GraphQLObjectType) {
const tmp = ObjectTypeComposer.createTemp(type, this.schemaComposer);
this.addFields(tmp.getFields());
this.addInterfaces(tmp.getInterfaces());
let tc;
if (type instanceof ObjectTypeComposer || type instanceof InterfaceTypeComposer) {
tc = type;
} else if (type instanceof GraphQLObjectType) {
tc = ObjectTypeComposer.createTemp(type, this.schemaComposer);
} else if (type instanceof GraphQLInterfaceType) {
const tmp = InterfaceTypeComposer.createTemp(type, this.schemaComposer);
this.addFields(tmp.getFields());
} else if (type instanceof ObjectTypeComposer) {
this.addFields(type.getFields());
this.addInterfaces(type.getInterfaces());
// Feel free to add other properties for merging two TypeComposers.
// For simplicity it just merge fields and interfaces.
} else if (type instanceof InterfaceTypeComposer) {
this.addFields(type.getFields());
tc = InterfaceTypeComposer.createTemp(type, this.schemaComposer);
} else {
throw new Error(
`Cannot merge ${inspect(
type
)} with ObjectType(${this.getTypeName()}). Provided type should be GraphQLObjectType or ObjectTypeComposer.`
)} with ObjectType(${this.getTypeName()}). Provided type should be GraphQLInterfaceType, GraphQLObjectType, InterfaceTypeComposer or ObjectTypeComposer.`
);
}

// deep clone all fields with args
const fields = ({ ...tc.getFields() }: any);
Object.keys(fields).forEach((fieldName) => {
fields[fieldName] = {
...fields[fieldName],
args: {
...fields[fieldName].args,
},
// set type as SDL string, it automatically will be remapped to the correct type instance in the current schema
type: tc.getFieldTypeName(fieldName),
};
tc.getFieldArgNames(fieldName).forEach((argName) => {
fields[fieldName].args[argName] = {
...fields[fieldName].args[argName],
// set type as SDL string, it automatically will be remapped to the correct type instance in the current schema
type: tc.getFieldArgTypeName(fieldName, argName),
};
});
});
this.addFields(fields);

// set interfaces as SDL string, it automatically will be remapped to the correct type instance in the current schema
this.addInterfaces(tc.getInterfaces().map((i) => i.getTypeName()));

// Feel free to add other properties for merging two TypeComposers.
// For simplicity it just merge fields and interfaces.

return this;
}

Expand Down
16 changes: 8 additions & 8 deletions src/SchemaComposer.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ export class SchemaComposer<TContext> extends TypeStorage<any, NamedTypeComposer
this.getAnyTC(typeName).merge((type: any));
} else {
// add a new type
const tc = this.createTC(type);
const tc = type.cloneTo(this);
this.set(key, tc);
if (typeName && typeName !== key) {
this.set(typeName, tc);
Expand Down Expand Up @@ -753,7 +753,7 @@ export class SchemaComposer<TContext> extends TypeStorage<any, NamedTypeComposer
addDirective(directive: GraphQLDirective): SchemaComposer<TContext> {
if (!(directive instanceof GraphQLDirective)) {
throw new Error(dedent`
You should provide GraphQLDirective to schemaComposer.addDirective(), but recieved:
You should provide GraphQLDirective to schemaComposer.addDirective(), but received:
${inspect(directive)}
`);
}
Expand Down Expand Up @@ -883,14 +883,14 @@ export class SchemaComposer<TContext> extends TypeStorage<any, NamedTypeComposer
ENUM_TYPE: (tc: EnumTypeComposer<any>) => {
const typename = tc.getTypeName();
if (exclude.includes(typename)) return;
let hasDifferentIntervalVals = false;
const internalVals = {};
let hasDifferentIntervalValues = false;
const internalValues = {};
forEachKey(tc.getFields(), (fc, fieldName) => {
if (fc.value !== fieldName) hasDifferentIntervalVals = true;
internalVals[fieldName] = fc.value;
if (fc.value !== fieldName) hasDifferentIntervalValues = true;
internalValues[fieldName] = fc.value;
});
if (hasDifferentIntervalVals) {
resolveMethods[typename] = internalVals;
if (hasDifferentIntervalValues) {
resolveMethods[typename] = internalValues;
}
},
});
Expand Down
7 changes: 3 additions & 4 deletions src/UnionTypeComposer.js
Original file line number Diff line number Diff line change
Expand Up @@ -460,10 +460,6 @@ export class UnionTypeComposer<TSource, TContext> {
tc = UnionTypeComposer.createTemp(type, this.schemaComposer);
} else if (type instanceof UnionTypeComposer) {
tc = type;
}

if (tc) {
this.addTypes(tc.getTypes());
} else {
throw new Error(
`Cannot merge ${inspect(
Expand All @@ -472,6 +468,9 @@ export class UnionTypeComposer<TSource, TContext> {
);
}

// set types as SDL string, it automatically will be remapped to the correct type instance in the current schema
this.addTypes(tc.getTypes().map((t) => t.getTypeName()));

return this;
}

Expand Down
75 changes: 74 additions & 1 deletion src/__tests__/github_issues/287-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { buildSchema, GraphQLSchema } from 'graphql';
import { SchemaComposer, dedent } from '../..';

describe('github issue #287: Can we merge schemas with overriding types in fields', () => {
it('merge two schemas', () => {
it('merge two simple schemas', () => {
const schemaA = buildSchema(`
type Query {
field1: Int
Expand Down Expand Up @@ -41,4 +41,77 @@ describe('github issue #287: Can we merge schemas with overriding types in field

expect(sc.buildSchema()).toBeInstanceOf(GraphQLSchema);
});

it('it merges field & arg types', () => {
const schemaA = buildSchema(`
# An object with an ID
interface Node {
# The id of the object.
id: ID!
}
type Post implements Node {
id: ID!
content: String
fieldA: Int
fieldB: String
}
type Query {
post: Post
# Fetches an object given its ID
node(
# The ID of an object
id: ID!
): Node
}
`);

const schemaB = buildSchema(`
# An object with an ID
interface Node {
# The id of the object.
id: ID!
}
type Post implements Node {
id: ID!
content: String
}
type Query {
post: Post
# Fetches an object given its ID
node(
# The ID of an object
id: ID!
): Node
}
`);

const sc = new SchemaComposer(schemaA);
sc.merge(schemaB);

expect(sc.toSDL({ omitScalars: true, omitDirectiveDefinitions: true })).toEqual(dedent`
type Mutation
interface Node {
id: ID!
}
type Post implements Node {
id: ID!
content: String
fieldA: Int
fieldB: String
}
type Query {
post: Post
node(id: ID!): Node
}
type Subscription
`);
});
});

0 comments on commit 03b1d39

Please sign in to comment.