diff --git a/src/stitching/checkResultAndHandleErrors.ts b/src/stitching/checkResultAndHandleErrors.ts index 18231f0fb52..cd9fca996f1 100644 --- a/src/stitching/checkResultAndHandleErrors.ts +++ b/src/stitching/checkResultAndHandleErrors.ts @@ -5,6 +5,7 @@ import { isObjectType, isListType, isEnumType, + isScalarType, ExecutionResult, GraphQLError, } from 'graphql'; @@ -65,6 +66,8 @@ export function handleResult( if (value) { return value.value; } + } else if (isScalarType(nullableType)) { + return nullableType.parseValue(resultObject); } return resultObject; diff --git a/src/stitching/schemaRecreation.ts b/src/stitching/schemaRecreation.ts index 3c51530d93f..8c794b8e863 100644 --- a/src/stitching/schemaRecreation.ts +++ b/src/stitching/schemaRecreation.ts @@ -107,22 +107,16 @@ export function recreateType( values: newValues, }); } else if (type instanceof GraphQLScalarType) { - if (keepResolvers || isSpecifiedScalarType(type)) { + if (isSpecifiedScalarType(type)) { return type; } else { return new GraphQLScalarType({ name: type.name, description: type.description, astNode: type.astNode, - serialize(value: any) { - return value; - }, - parseValue(value: any) { - return value; - }, - parseLiteral(ast: ValueNode) { - return parseLiteral(ast); - }, + serialize: type.serialize ? type.serialize : (value: any) => value, + parseValue: type.parseValue ? type.parseValue : (value: any) => value, + parseLiteral: type.parseLiteral ? type.parseLiteral : (ast: any) => parseLiteral(ast), }); } } else { diff --git a/src/test/testMergeSchemas.ts b/src/test/testMergeSchemas.ts index c8a23245987..4343d6c9c8c 100644 --- a/src/test/testMergeSchemas.ts +++ b/src/test/testMergeSchemas.ts @@ -4,13 +4,13 @@ import { expect } from 'chai'; import { graphql, GraphQLSchema, + GraphQLField, GraphQLObjectType, GraphQLScalarType, subscribe, parse, ExecutionResult, defaultFieldResolver, - GraphQLField, findDeprecatedUsages, } from 'graphql'; import mergeSchemas from '../stitching/mergeSchemas'; @@ -72,10 +72,32 @@ let scalarTest = ` } type Query { - testingScalar: TestingScalar + testingScalar(input: TestScalar): TestingScalar } `; +let scalarSchema: GraphQLSchema; + +scalarSchema = makeExecutableSchema({ + typeDefs: scalarTest, + resolvers: { + TestScalar: new GraphQLScalarType({ + name: 'TestScalar', + description: undefined, + serialize: value => (value as string).slice(1), + parseValue: value => `_${value}`, + parseLiteral: (ast: any) => `_${ast.value}`, + }), + Query: { + testingScalar(parent, args) { + return { + value: args.input[0] === '_' ? args.input : null + }; + }, + }, + }, +}); + let enumTest = ` """ A type that uses an Enum. @@ -107,7 +129,7 @@ let enumTest = ` } type Query { - color: Color + color(input: Color): Color numericEnum: NumericEnum wrappedEnum: EnumWrapper } @@ -125,8 +147,8 @@ enumSchema = makeExecutableSchema({ TEST: 1, }, Query: { - color() { - return '#EA3232'; + color(parent, args) { + return args.input === '#EA3232' ? args.input : null; }, numericEnum() { return 1; @@ -289,8 +311,8 @@ testCombinations.forEach(async combination => { propertySchema, bookingSchema, productSchema, - scalarTest, interfaceExtensionTest, + scalarSchema, enumSchema, linkSchema, loneExtend, @@ -543,12 +565,45 @@ testCombinations.forEach(async combination => { expect(mergedResult).to.deep.equal(propertyResult); }); + it('works with custom scalars', async () => { + const scalarResult = await graphql( + scalarSchema, + ` + query { + testingScalar(input: "test") { + value + } + } + `, + ); + + const mergedResult = await graphql( + mergedSchema, + ` + query { + testingScalar(input: "test") { + value + } + } + `, + ); + + expect(scalarResult).to.deep.equal({ + data: { + testingScalar: { + value: 'test' + } + }, + }); + expect(mergedResult).to.deep.equal(scalarResult); + }); + it('works with custom enums', async () => { const enumResult = await graphql( enumSchema, ` query { - color + color(input: RED) numericEnum numericEnumInfo: __type(name: "NumericEnum") { enumValues(includeDeprecated: true) { @@ -576,7 +631,7 @@ testCombinations.forEach(async combination => { mergedSchema, ` query { - color + color(input: RED) numericEnum numericEnumInfo: __type(name: "NumericEnum") { enumValues(includeDeprecated: true) { diff --git a/src/transforms/AddArgumentsAsVariables.ts b/src/transforms/AddArgumentsAsVariables.ts index b2378ff8b64..3904c945c8d 100644 --- a/src/transforms/AddArgumentsAsVariables.ts +++ b/src/transforms/AddArgumentsAsVariables.ts @@ -3,17 +3,21 @@ import { DocumentNode, FragmentDefinitionNode, GraphQLArgument, + GraphQLEnumType, + GraphQLInputObjectType, GraphQLInputType, GraphQLList, GraphQLField, GraphQLNonNull, GraphQLObjectType, + GraphQLScalarType, GraphQLSchema, Kind, OperationDefinitionNode, SelectionNode, TypeNode, VariableDefinitionNode, + getNullableType, } from 'graphql'; import { Request } from '../Interfaces'; import { Transform } from './transforms'; @@ -62,6 +66,7 @@ function addVariablesToRootField( ) as Array; const variableNames = {}; + const newVariables = {}; const newOperations = operations.map((operation: OperationDefinitionNode) => { let existingVariables = operation.variableDefinitions.map( @@ -130,6 +135,10 @@ function addVariablesToRootField( }, type: typeToAst(argument.type), }; + newVariables[variableName] = serializeArgumentValue( + argument.type, + args[argument.name], + ); } }); @@ -154,11 +163,6 @@ function addVariablesToRootField( }; }); - const newVariables = {}; - Object.keys(variableNames).forEach(name => { - newVariables[variableNames[name]] = args[name]; - }); - return { document: { ...document, @@ -197,3 +201,30 @@ function typeToAst(type: GraphQLInputType): TypeNode { }; } } + +function serializeArgumentValue(type: GraphQLInputType, value: any): any { + if (value == null) { + return null; + } + + const nullableType = getNullableType(type); + + if (nullableType instanceof GraphQLEnumType || nullableType instanceof GraphQLScalarType) { + return nullableType.serialize(value); + } + + if (nullableType instanceof GraphQLList) { + return value.map((listMember: any) => serializeArgumentValue(nullableType.ofType, listMember)); + } + + if (nullableType instanceof GraphQLInputObjectType) { + const fields = nullableType.getFields(); + const newValue = {}; + Object.keys(value).forEach(key => { + newValue[key] = serializeArgumentValue(fields[key].type, value[key]); + }); + return newValue; + } + + return value; +}