Skip to content
This repository has been archived by the owner on Apr 15, 2020. It is now read-only.

Commit

Permalink
feat(stitching): restore onTypeConflict option to mergeSchemas
Browse files Browse the repository at this point in the history
  • Loading branch information
yaacovCR committed Sep 22, 2019
1 parent 19a4a31 commit afdd01e
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 6 deletions.
6 changes: 3 additions & 3 deletions docs/source/schema-stitching.md
Original file line number Diff line number Diff line change
Expand Up @@ -391,12 +391,12 @@ type OnTypeConflict = (

The `onTypeConflict` option to `mergeSchemas` allows customization of type resolving logic.

The default behavior of `mergeSchemas` is to take the first encountered type of all the types with the same name. If there are conflicts, `onTypeConflict` enables explicit selection of the winning type.
The default behavior of `mergeSchemas` is to take the *last* encountered type of all the types with the same name, with a warning that type conflicts have been encountered. If specified, `onTypeConflict` enables explicit selection of the winning type.

For example, here's how we could select the last type among multiple types with the same name:
For example, here's how we could select the *first* type among multiple types with the same name:

```js
const onTypeConflict = (left, right) => right;
const onTypeConflict = (left, right) => left;
```

And here's how we might select the type whose schema has the latest `version`:
Expand Down
36 changes: 33 additions & 3 deletions src/stitching/mergeSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ export type OnTypeConflict = (
},
) => GraphQLNamedType;

type CandidateSelector = (
candidates: Array<MergeTypeCandidate>,
) => MergeTypeCandidate;

export default function mergeSchemas({
schemas,
onTypeConflict,
Expand All @@ -76,6 +80,7 @@ export default function mergeSchemas({
}): GraphQLSchema {
return mergeSchemasImplementation({
schemas,
onTypeConflict,
resolvers,
schemaDirectives,
inheritResolversFromInterfaces,
Expand All @@ -85,6 +90,7 @@ export default function mergeSchemas({

function mergeSchemasImplementation({
schemas,
onTypeConflict,
resolvers,
schemaDirectives,
inheritResolversFromInterfaces,
Expand All @@ -93,6 +99,7 @@ function mergeSchemasImplementation({
schemas: Array<
string | GraphQLSchema | DocumentNode | Array<GraphQLNamedType>
>;
onTypeConflict?: OnTypeConflict;
resolvers?: IResolversParameter;
schemaDirectives?: { [name: string]: typeof SchemaDirectiveVisitor };
inheritResolversFromInterfaces?: boolean;
Expand Down Expand Up @@ -225,6 +232,7 @@ function mergeSchemasImplementation({
const resultType: VisitTypeResult = defaultVisitType(
typeName,
typeCandidates[typeName],
onTypeConflict ? onTypeConflictToCandidateSelector(onTypeConflict) : undefined
);
if (resultType === null) {
types[typeName] = null;
Expand Down Expand Up @@ -441,12 +449,34 @@ function addTypeCandidate(
typeCandidates[name].push(typeCandidate);
}

function onTypeConflictToCandidateSelector(onTypeConflict: OnTypeConflict): CandidateSelector {
return cands =>
cands.reduce((prev, next) => {
const type = onTypeConflict(prev.type, next.type, {
left: {
schema: prev.schema,
},
right: {
schema: next.schema,
},
});
if (prev.type === type) {
return prev;
} else if (next.type === type) {
return next;
} else {
return {
schemaName: 'unknown',
type
};
}
});
}

function defaultVisitType(
name: string,
candidates: Array<MergeTypeCandidate>,
candidateSelector?: (
candidates: Array<MergeTypeCandidate>,
) => MergeTypeCandidate,
candidateSelector?: CandidateSelector
) {
if (!candidateSelector) {
candidateSelector = cands => cands[cands.length - 1];
Expand Down
87 changes: 87 additions & 0 deletions src/test/testAlternateMergeSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -537,3 +537,90 @@ describe('mergeSchemas', () => {
expect(response.errors).to.be.undefined;
});
});

describe('onTypeConflict', () => {
let schema1: GraphQLSchema;
let schema2: GraphQLSchema;

beforeEach(() => {
const typeDefs1 = `
type Query {
test1: Test
}
type Test {
fieldA: String
fieldB: String
}
`;

const typeDefs2 = `
type Query {
test2: Test
}
type Test {
fieldA: String
fieldC: String
}
`;

schema1 = makeExecutableSchema({
typeDefs: typeDefs1,
resolvers: {
Query: {
test1: () => ({})
},
Test: {
fieldA: () => 'A',
fieldB: () => 'B'
}
}
});

schema2 = makeExecutableSchema({
typeDefs: typeDefs2,
resolvers: {
Query: {
test2: () => ({})
},
Test: {
fieldA: () => 'A',
fieldC: () => 'C'
}
}
});
})

it('by default takes last type', async () => {
const mergedSchema = mergeSchemas({
schemas: [schema1, schema2]
});
const result1 = await graphql(mergedSchema, `{ test2 { fieldC } }`);
expect(result1.data.test2.fieldC).to.equal('C');
const result2 = await graphql(mergedSchema, `{ test2 { fieldB } }`);
expect(result2.data).to.be.undefined;
});

it('can use onTypeConflict to select last type', async () => {
const mergedSchema = mergeSchemas({
schemas: [schema1, schema2],
onTypeConflict: (left, right) => right
});
const result1 = await graphql(mergedSchema, `{ test2 { fieldC } }`);
expect(result1.data.test2.fieldC).to.equal('C');
const result2 = await graphql(mergedSchema, `{ test2 { fieldB } }`);
expect(result2.data).to.be.undefined;
});

it('can use onTypeConflict to select first type', async () => {
const mergedSchema = mergeSchemas({
schemas: [schema1, schema2],
onTypeConflict: (left) => left
});
const result1 = await graphql(mergedSchema, `{ test1 { fieldB } }`);
expect(result1.data.test1.fieldB).to.equal('B');
const result2 = await graphql(mergedSchema, `{ test1 { fieldC } }`);
expect(result2.data).to.be.undefined;
});
});

0 comments on commit afdd01e

Please sign in to comment.