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

fix(mapSchema): mapping fields to new types #1877

Merged
merged 2 commits into from Aug 4, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 9 additions & 5 deletions packages/utils/src/rewire.ts
Expand Up @@ -41,10 +41,14 @@ export function rewireTypes(
typeMap: TypeMap;
directives: Array<GraphQLDirective>;
} {
const referenceTypeMap = Object.create(null);
Object.keys(originalTypeMap).forEach(typeName => {
referenceTypeMap[typeName] = originalTypeMap[typeName];
});
const newTypeMap: TypeMap = Object.create(null);

Object.keys(originalTypeMap).forEach(typeName => {
const namedType = originalTypeMap[typeName];
Object.keys(referenceTypeMap).forEach(typeName => {
const namedType = referenceTypeMap[typeName];

if (namedType == null || typeName.startsWith('__')) {
return;
Expand Down Expand Up @@ -194,10 +198,10 @@ export function rewireTypes(
const rewiredType = rewireType(type.ofType);
return rewiredType != null ? (new GraphQLNonNull(rewiredType) as T) : null;
} else if (isNamedType(type)) {
let rewiredType = originalTypeMap[type.name];
let rewiredType = referenceTypeMap[type.name];
if (rewiredType === undefined) {
rewiredType = isNamedStub(type) ? getBuiltInForStub(type) : type;
newTypeMap[rewiredType.name] = rewiredType;
rewiredType = isNamedStub(type) ? getBuiltInForStub(type) : rewireNamedType(type);
newTypeMap[rewiredType.name] = referenceTypeMap[type.name] = rewiredType;
}
return rewiredType != null ? (newTypeMap[rewiredType.name] as T) : null;
}
Expand Down
108 changes: 108 additions & 0 deletions packages/utils/tests/schemaTransforms.test.ts
Expand Up @@ -20,6 +20,8 @@ import {
GraphQLEnumType,
GraphQLInt,
GraphQLList,
getNamedType,
GraphQLNonNull,
} from 'graphql';

import formatDate from 'dateformat';
Expand All @@ -33,6 +35,8 @@ import {
ExecutionResult,
} from '@graphql-tools/utils';

import { addMocksToSchema } from '@graphql-tools/mock';

const typeDefs = `
directive @schemaDirective(role: String) on SCHEMA
directive @schemaExtensionDirective(role: String) on SCHEMA
Expand Down Expand Up @@ -1129,4 +1133,108 @@ describe('@directives', () => {
});
});
});

test('allows creation of types that reference other types (issue #1877)', async () => {
function listWrapperTransformer(schema: GraphQLSchema) {
const listWrapperTypes = new Map();
return mapSchema(schema, {
[MapperKind.COMPOSITE_FIELD]: (fieldConfig, fieldName) => {
const hasDirectiveAnnotation = !!getDirectives(schema, fieldConfig)['addListWrapper'];

// Leave the field untouched if it does not have the directive annotation
if (!hasDirectiveAnnotation) {
return undefined;
}

const itemTypeInList = getNamedType(fieldConfig.type);
const itemTypeNameInList = itemTypeInList.name;

// 1. Creating the XListWrapper type and replace the type of the field with that
if (!listWrapperTypes.has(itemTypeNameInList)) {
listWrapperTypes.set(itemTypeNameInList, new GraphQLObjectType({
name: `${itemTypeNameInList}ListWrapper`,
fields: {
// Adding `size` field
size: {
type: new GraphQLNonNull(GraphQLInt),
description: 'The number of items in the `items` field',
},
// Creating a new List which contains the same type than the original List
items: {
type: new GraphQLNonNull(new GraphQLList(itemTypeInList))
}
}
}));
}

fieldConfig.type = listWrapperTypes.get(itemTypeNameInList);

// 2. Replacing resolver to return `{ size, items }`
const originalResolver = fieldConfig.resolve;

fieldConfig.resolve = (parent, args, ctx, info) => {
const value = originalResolver ? originalResolver(parent, args, ctx, info) : parent[fieldName];
const items = value || [];

return {
size: items.length,
items
};
};

// 3. Returning the updated `fieldConfig`
return fieldConfig;
},
});
}

let schema = makeExecutableSchema({
typeDefs: `
directive @addListWrapper on FIELD_DEFINITION

type Query {
me: Person
}
type Person {
name: String!
friends: [Person] @addListWrapper
}
`,
schemaTransforms: [listWrapperTransformer]
});

schema = addMocksToSchema({ schema });

const result = await graphql(
schema,
`
query {
me {
friends {
items {
name
}
}
}
}
`,
);

const expectedResult: any = {
me: {
friends: {
items: [
{
name: 'Hello World',
},
{
name: 'Hello World',
},
]
},
}
};

expect(result.data).toEqual(expectedResult);
});
});