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

Commit

Permalink
fix(stitching): serialize/deserialize enum/custom scalar values
Browse files Browse the repository at this point in the history
BREAKING CHANGE: This change allows enums and custom scalars to be used as arguments
within merged schemas. It also fixes seralization and deserialization
more generally within merged schemas. If an implementation is available
for a custom scalar within a merged schema (i.e., the schema is local),
the internal representation will be available for use with stitching.
Previously, the merged schema internally used the serialized version.
  • Loading branch information
stefanprobst authored and yaacovCR committed Oct 25, 2019
1 parent 06e4a70 commit 02f9ae3
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 23 deletions.
3 changes: 3 additions & 0 deletions src/stitching/checkResultAndHandleErrors.ts
Expand Up @@ -5,6 +5,7 @@ import {
isObjectType,
isListType,
isEnumType,
isScalarType,
ExecutionResult,
GraphQLError,
} from 'graphql';
Expand Down Expand Up @@ -65,6 +66,8 @@ export function handleResult(
if (value) {
return value.value;
}
} else if (isScalarType(nullableType)) {
return nullableType.parseValue(resultObject);
}

return resultObject;
Expand Down
14 changes: 4 additions & 10 deletions src/stitching/schemaRecreation.ts
Expand Up @@ -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 {
Expand Down
71 changes: 63 additions & 8 deletions src/test/testMergeSchemas.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -107,7 +129,7 @@ let enumTest = `
}
type Query {
color: Color
color(input: Color): Color
numericEnum: NumericEnum
wrappedEnum: EnumWrapper
}
Expand All @@ -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;
Expand Down Expand Up @@ -289,8 +311,8 @@ testCombinations.forEach(async combination => {
propertySchema,
bookingSchema,
productSchema,
scalarTest,
interfaceExtensionTest,
scalarSchema,
enumSchema,
linkSchema,
loneExtend,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -576,7 +631,7 @@ testCombinations.forEach(async combination => {
mergedSchema,
`
query {
color
color(input: RED)
numericEnum
numericEnumInfo: __type(name: "NumericEnum") {
enumValues(includeDeprecated: true) {
Expand Down
41 changes: 36 additions & 5 deletions src/transforms/AddArgumentsAsVariables.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -62,6 +66,7 @@ function addVariablesToRootField(
) as Array<FragmentDefinitionNode>;

const variableNames = {};
const newVariables = {};

const newOperations = operations.map((operation: OperationDefinitionNode) => {
let existingVariables = operation.variableDefinitions.map(
Expand Down Expand Up @@ -130,6 +135,10 @@ function addVariablesToRootField(
},
type: typeToAst(argument.type),
};
newVariables[variableName] = serializeArgumentValue(
argument.type,
args[argument.name],
);
}
});

Expand All @@ -154,11 +163,6 @@ function addVariablesToRootField(
};
});

const newVariables = {};
Object.keys(variableNames).forEach(name => {
newVariables[variableNames[name]] = args[name];
});

return {
document: {
...document,
Expand Down Expand Up @@ -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;
}

0 comments on commit 02f9ae3

Please sign in to comment.