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

Modify ast nodes when transforming schemas #1762

Merged
merged 6 commits into from
Jul 13, 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
72 changes: 67 additions & 5 deletions packages/stitch/tests/alternateStitchSchemas.test.ts
Expand Up @@ -969,8 +969,8 @@ describe('rename nested object fields with interfaces', () => {
});
});

describe('WrapType query transform', () => {
test('should work', async () => {
describe('WrapType', () => {
test('Query transform should work', async () => {
const transformedBookingSchema = wrapSchema(bookingSchema, [
new WrapType('Query', 'Namespace_Query', 'namespace'),
]);
Expand Down Expand Up @@ -1022,10 +1022,8 @@ describe('WrapType query transform', () => {

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

describe('WrapType mutation transform', () => {
test('should work', async () => {
test('Mutation transform should work', async () => {
const transformedBookingSchema = wrapSchema(bookingSchema, [
new WrapType('Mutation', 'Namespace_Mutation', 'namespace'),
]);
Expand Down Expand Up @@ -1084,6 +1082,70 @@ describe('WrapType mutation transform', () => {

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

test('namespacing different subschemas with overlapping root field names', async () => {
const typeDefGen = (i: number) => `
type Query {
test: Test${i}Response
}

type Test${i}Response {
aString: String!
}
`;

const resolverGen = (i: number) => ({
Query: {
test: () => ({
aString: `test${i}`,
}),
},
});

const subschemaGen = (i: number) => ({
schema: makeExecutableSchema({
typeDefs: typeDefGen(i),
resolvers: resolverGen(i)
}),
transforms: [new WrapType(`Query`, `Test${i}_Query`, `test${i}`)]
});

const stitchedSchema = stitchSchemas({
subschemas: [subschemaGen(1), subschemaGen(2)]
});

const query = `{
test1 {
test {
aString
}
}
test2 {
test {
aString
}
}
}`;

const result = await graphql(stitchedSchema, query);

const expectedResult = {
data: {
test1: {
test: {
aString: 'test1',
},
},
test2: {
test: {
aString: 'test2',
},
},
},
};

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

describe('schema transformation with extraction of nested fields', () => {
Expand Down
55 changes: 54 additions & 1 deletion packages/utils/src/fields.ts
@@ -1,4 +1,12 @@
import { GraphQLFieldConfigMap, GraphQLObjectType, GraphQLFieldConfig, GraphQLSchema } from 'graphql';
import {
GraphQLFieldConfigMap,
GraphQLObjectType,
GraphQLFieldConfig,
GraphQLSchema,
ObjectTypeDefinitionNode,
ObjectTypeExtensionNode,
FieldDefinitionNode,
} from 'graphql';
import { MapperKind } from './Interfaces';
import { mapSchema } from './mapSchema';
import { addTypes } from './addTypes';
Expand Down Expand Up @@ -34,6 +42,8 @@ export function appendObjectFields(
return new GraphQLObjectType({
...config,
fields: newFieldConfigMap,
astNode: rebuildAstNode(config.astNode, newFieldConfigMap),
extensionASTNodes: rebuildExtensionAstNodes(config.extensionASTNodes),
});
}
},
Expand Down Expand Up @@ -65,6 +75,8 @@ export function removeObjectFields(
return new GraphQLObjectType({
...config,
fields: newFieldConfigMap,
astNode: rebuildAstNode(config.astNode, newFieldConfigMap),
extensionASTNodes: rebuildExtensionAstNodes(config.extensionASTNodes),
});
}
},
Expand Down Expand Up @@ -131,10 +143,51 @@ export function modifyObjectFields(
return new GraphQLObjectType({
...config,
fields: newFieldConfigMap,
astNode: rebuildAstNode(config.astNode, newFieldConfigMap),
extensionASTNodes: rebuildExtensionAstNodes(config.extensionASTNodes),
});
}
},
});

return [newSchema, removedFields];
}

function rebuildAstNode(
astNode: ObjectTypeDefinitionNode,
fieldConfigMap: Record<string, GraphQLFieldConfig<any, any>>
): ObjectTypeDefinitionNode {
if (astNode == null) {
return undefined;
}

const newAstNode: ObjectTypeDefinitionNode = {
...astNode,
fields: undefined,
};

const fields: Array<FieldDefinitionNode> = [];
Object.values(fieldConfigMap).forEach(fieldConfig => {
if (fieldConfig.astNode != null) {
fields.push(fieldConfig.astNode);
}
});

return {
...newAstNode,
fields,
};
}

function rebuildExtensionAstNodes(
extensionASTNodes: ReadonlyArray<ObjectTypeExtensionNode>
): Array<ObjectTypeExtensionNode> {
if (!extensionASTNodes?.length) {
return [];
}

return extensionASTNodes.map(node => ({
...node,
fields: undefined,
}));
}
77 changes: 74 additions & 3 deletions packages/utils/src/mapSchema.ts
Expand Up @@ -24,6 +24,14 @@ import {
GraphQLList,
GraphQLNonNull,
GraphQLEnumType,
ObjectTypeDefinitionNode,
InterfaceTypeDefinitionNode,
InputObjectTypeDefinitionNode,
InputObjectTypeExtensionNode,
InterfaceTypeExtensionNode,
ObjectTypeExtensionNode,
InputValueDefinitionNode,
FieldDefinitionNode,
} from 'graphql';

import {
Expand Down Expand Up @@ -239,6 +247,15 @@ function mapFields(originalTypeMap: TypeMap, schema: GraphQLSchema, schemaMapper
newFieldConfigMap[fieldName] = originalFieldConfig;
} else if (Array.isArray(mappedField)) {
const [newFieldName, newFieldConfig] = mappedField;
if (newFieldConfig.astNode != null) {
newFieldConfig.astNode = {
...newFieldConfig.astNode,
name: {
...newFieldConfig.astNode.name,
value: newFieldName,
},
};
}
newFieldConfigMap[newFieldName] = newFieldConfig;
} else if (mappedField !== null) {
newFieldConfigMap[fieldName] = mappedField;
Expand All @@ -247,18 +264,26 @@ function mapFields(originalTypeMap: TypeMap, schema: GraphQLSchema, schemaMapper

if (isObjectType(originalType)) {
newTypeMap[typeName] = new GraphQLObjectType({
...((config as unknown) as GraphQLObjectTypeConfig<any, any>),
...(config as GraphQLObjectTypeConfig<any, any>),
fields: newFieldConfigMap,
astNode: rebuildAstNode((config as GraphQLObjectTypeConfig<any, any>).astNode, newFieldConfigMap),
extensionASTNodes: rebuildExtensionAstNodes((config as GraphQLObjectTypeConfig<any, any>).extensionASTNodes),
});
} else if (isInterfaceType(originalType)) {
newTypeMap[typeName] = new GraphQLInterfaceType({
...((config as unknown) as GraphQLInterfaceTypeConfig<any, any>),
...(config as GraphQLInterfaceTypeConfig<any, any>),
fields: newFieldConfigMap,
astNode: rebuildAstNode((config as GraphQLInterfaceTypeConfig<any, any>).astNode, newFieldConfigMap),
extensionASTNodes: rebuildExtensionAstNodes(
(config as GraphQLInterfaceTypeConfig<any, any>).extensionASTNodes
),
});
} else {
newTypeMap[typeName] = new GraphQLInputObjectType({
...((config as unknown) as GraphQLInputObjectTypeConfig),
...(config as GraphQLInputObjectTypeConfig),
fields: newFieldConfigMap,
astNode: rebuildAstNode((config as GraphQLInputObjectTypeConfig).astNode, newFieldConfigMap),
extensionASTNodes: rebuildExtensionAstNodes((config as GraphQLInputObjectTypeConfig).extensionASTNodes),
});
}
}
Expand Down Expand Up @@ -471,3 +496,49 @@ function getEnumValueMapper(schemaMapper: SchemaMapper): EnumValueMapper | null
const enumValueMapper = schemaMapper[MapperKind.ENUM_VALUE];
return enumValueMapper != null ? enumValueMapper : null;
}

function rebuildAstNode<
TypeDefinitionNode extends ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode | InputObjectTypeDefinitionNode
>(
astNode: TypeDefinitionNode,
fieldOrInputFieldConfigMap: Record<
string,
TypeDefinitionNode extends ObjectTypeDefinitionNode | InterfaceTypeDefinitionNode
? GraphQLFieldConfig<any, any>
: GraphQLInputFieldConfig
>
): TypeDefinitionNode {
if (astNode == null) {
return undefined;
}

const newAstNode: TypeDefinitionNode = {
...astNode,
fields: undefined,
};

const fields: Array<FieldDefinitionNode | InputValueDefinitionNode> = [];
Object.values(fieldOrInputFieldConfigMap).forEach(fieldOrInputFieldConfig => {
if (fieldOrInputFieldConfig.astNode != null) {
fields.push(fieldOrInputFieldConfig.astNode);
}
});

return {
...newAstNode,
fields,
};
}

function rebuildExtensionAstNodes<
TypeExtensionNode extends ObjectTypeExtensionNode | InterfaceTypeExtensionNode | InputObjectTypeExtensionNode
>(extensionASTNodes: ReadonlyArray<TypeExtensionNode>): Array<TypeExtensionNode> {
if (!extensionASTNodes?.length) {
return [];
}

return extensionASTNodes.map(node => ({
...node,
fields: undefined,
}));
}
2 changes: 1 addition & 1 deletion packages/wrap/src/transforms/MapFields.ts
Expand Up @@ -15,7 +15,7 @@ export default class MapFields implements Transform {
errorsTransformer?: ErrorsTransformer
) {
this.transformer = new TransformCompositeFields(
(_typeName, _fieldName, fieldConfig) => fieldConfig,
() => undefined,
(typeName, fieldName, fieldNode, fragments, transformationContext) => {
const typeTransformers = fieldNodeTransformerMap[typeName];
if (typeTransformers == null) {
Expand Down