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 issue with Relay serialization #2638

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions packages/relay-compiler/core/GraphQLIRPrinter.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const {DEFAULT_HANDLE_KEY} = require('../util/DefaultHandleKey');
const {
GraphQLEnumType,
GraphQLInputObjectType,
GraphQLScalarType,
GraphQLList,
GraphQLNonNull,
} = require('graphql');
Expand Down Expand Up @@ -304,13 +305,18 @@ function printLiteral(value: mixed, type: ?GraphQLInputType): string {
type = type.ofType;
}
if (type instanceof GraphQLEnumType) {
const result = type.serialize(value);
invariant(
typeof value === 'string',
'GraphQLIRPrinter: Expected value of type %s to be a string, got `%s`.',
typeof result === 'string',
'GraphQLIRPrinter: Expected value of type %s to be a valid enum value, got `%s`.',
type.name,
value,
);
return value;
return result;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't remember if GraphQLInputObject and GraphQLList also support serialize(), if they do can we just use the approach here for all types (ie replace the body of printLiteral w the body of the enum case)? Even if lists/objects don't work, we could do this for all enums and scalars.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only scalars and enums have serialize.

}
if (type instanceof GraphQLScalarType) {
const result = type.serialize(value);
return JSON.stringify(result);
}
if (Array.isArray(value)) {
invariant(
Expand Down
16 changes: 14 additions & 2 deletions packages/relay-compiler/core/__tests__/GraphQLIRVisitor-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,23 @@ describe('GraphQLIRVisitor', () => {
},
Literal: {
leave(node: Literal) {
const mutator = value => {
// Keep enums valid
if (value === 'WEB') {
return 'MOBILE';
} else if (value === 'HELPFUL') {
return 'DERISIVE';
} else if (typeof value === 'number') {
return value + 10;
} else {
return String(value) + '_mutated';
}
};
return {
...node,
value: Array.isArray(node.value)
? node.value.map(item => String(node.value) + '_mutated')
: String(node.value) + '_mutated',
? node.value.map(mutator)
: mutator(node.value),
};
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,16 @@ fragment UserFragment_mutated on User @argumentDefinitions(
... on Mutated @include(if: $cond_mutated) {
id_mutated
__typename_mutated
checkins_mutated(environments_mutated: [WEB_mutated]) {
checkins_mutated(environments_mutated: [MOBILE]) {
__typename_mutated
}
friends_mutated(after_mutated: $after_mutated, first_mutated: $first_mutated, traits_mutated: [HELPFUL_mutated]) {
friends_mutated(after_mutated: $after_mutated, first_mutated: $first_mutated, traits_mutated: [DERISIVE]) {
Copy link
Contributor

@josephsavona josephsavona Feb 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's keep the original N/N_mutated style just to make it more obvious what the test is doing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't, serialization for enums only returns valid values and HEPLFUL_mutated isn't a valid enum value for traits.

count_mutated
}
... on Mutated @skip(if: $cond_mutated) {
name_mutated
}
thumbnail: profilePicture_mutated(size_mutated: "32_mutated") {
thumbnail: profilePicture_mutated(size_mutated: 42) {
height_mutated
width_mutated
src: uri_mutated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@ fragment UserFragment on User @argumentDefinitions(
friends(after: $after, first: $first, traits: [HELPFUL]) {
count
}
secondFriends: friends(first: 10) {
count
}
name @include(if: $cond)
otherName: name @customDirective(level: 3)
thumbnail: profilePicture(size: 32) {
thumbnail: profilePicture2(size: 32, cropPosition: CENTER, fileExtension: PNG, additionalParameters: { filter: "Boston" }) {
height
width
src: uri
Expand Down Expand Up @@ -88,9 +91,12 @@ fragment UserFragment on User @argumentDefinitions(
friends(after: $after, first: $first, traits: [HELPFUL]) {
count
}
secondFriends: friends(first: 10) {
count
}
name @include(if: $cond)
otherName: name @customDirective(level: 3)
thumbnail: profilePicture(size: 32) {
thumbnail: profilePicture2(size: 32, cropPosition: CENTER, fileExtension: PNG, additionalParameters: {"filter":"Boston"}) {
height
width
src: uri
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ fragment UserFragment on User @argumentDefinitions(
friends(after: $after, first: $first, traits: [HELPFUL]) {
count
}
secondFriends: friends(first: 10) {
count
}
name @include(if: $cond)
otherName: name @customDirective(level: 3)
thumbnail: profilePicture(size: 32) {
thumbnail: profilePicture2(size: 32, cropPosition: CENTER, fileExtension: PNG, additionalParameters: { filter: "Boston" }) {
height
width
src: uri
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ query TestQuery(
query TestQuery2(
$usedInTestQuery2: Boolean!
) {
node(id: 4) {
node(id: "4") {
... on Actor {
id @skip(if: $usedInTestQuery2)
}
Expand Down
101 changes: 97 additions & 4 deletions packages/relay-test-utils/RelayTestSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,101 @@ const RelayTestSchemaPath = require('./RelayTestSchemaPath');

const fs = require('fs');

const {buildASTSchema, parse} = require('graphql');
const {
buildASTSchema,
parse,
GraphQLEnumType,
GraphQLSchema,
GraphQLScalarType,
Kind,
extendSchema,
} = require('graphql');

module.exports = buildASTSchema(
parse(fs.readFileSync(RelayTestSchemaPath, 'utf8'), {assumeValid: true}),
);
function buildSchema() {
const CropPosition = new GraphQLEnumType({
name: 'CropPosition',
values: {
TOP: {value: 1},
CENTER: {value: 2},
BOTTOM: {value: 3},
LEFT: {value: 4},
RIGHT: {value: 5},
},
});
const FileExtension = new GraphQLEnumType({
name: 'FileExtension',
values: {
JPG: {value: 'jpg'},
PNG: {value: 'png'},
},
});
let schema = new GraphQLSchema({
types: [CropPosition, FileExtension, GraphQLJSONType],
});
schema = extendSchema(
schema,
parse(fs.readFileSync(RelayTestSchemaPath, 'utf8')),
);
// AST Builder doesn't allow things undefined in AST to be argument types it
// seems
return extendSchema(
schema,
parse(`
extend type User {
profilePicture2(
size: [Int],
preset: PhotoSize,
cropPosition: CropPosition,
fileExtension: FileExtension
additionalParameters: JSON
): Image
}
`),
);
}

function identity(value) {
return value;
}

function parseLiteral(ast, variables) {
switch (ast.kind) {
case Kind.STRING:
case Kind.BOOLEAN:
return ast.value;
case Kind.INT:
case Kind.FLOAT:
return parseFloat(ast.value);
case Kind.OBJECT: {
const value = Object.create(null);
ast.fields.forEach(field => {
value[field.name.value] = parseLiteral(field.value, variables);
});

return value;
}
case Kind.LIST:
return ast.values.map(n => parseLiteral(n, variables));
case Kind.NULL:
return null;
case Kind.VARIABLE: {
const name = ast.name.value;
return variables ? variables[name] : undefined;
}
default:
return undefined;
}
}

const GraphQLJSONType = new GraphQLScalarType({
name: 'JSON',
description:
'The `JSON` scalar type represents JSON values as specified by ' +
'[ECMA-404](http://www.ecma-international.org/' +
'publications/files/ECMA-ST/ECMA-404.pdf).',
serialize: identity,
parseValue: identity,
parseLiteral,
});

module.exports = buildSchema();
27 changes: 26 additions & 1 deletion packages/relay-test-utils/testschema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,14 @@ type Comment implements Node {
message: Text
name: String
profilePicture(size: [Int], preset: PhotoSize): Image
# This is added in RelayTestSchema
# profilePicture2(
# size: [Int],
# preset: PhotoSize,
# cropPosition: CropPosition,
# fileExtension: FileExtension,
# additionalParameters: JSON
# ): Image
segments(first: Int): Segments
screennames: [Screenname]
subscribeStatus: String
Expand Down Expand Up @@ -744,7 +752,10 @@ type User implements Named & Node & Actor {
nameRenderer(supported: [String!]!): UserNameRenderer
storySearch(query: StorySearchInput): [Story]
storyCommentSearch(query: StoryCommentSearchInput): [Comment]
profilePicture(size: [Int], preset: PhotoSize): Image
profilePicture(
size: [Int],
preset: PhotoSize
): Image
profile_picture(scale: Float): Image
segments(first: Int): Segments
screennames: [Screenname]
Expand Down Expand Up @@ -838,6 +849,20 @@ enum TopLevelCommentsOrdering {
toplevel
}

# This is added in RelayTestSchema
# enum CropPosition {
# TOP
# CENTER
# BOTTOM
# LEFT
# RIGHT
# }
#
# enum FileExtension {
# JPG
# PNG
# }

type Settings {
cache_id: ID
notificationSounds: Boolean
Expand Down