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 1 commit
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
7 changes: 4 additions & 3 deletions packages/relay-compiler/core/GraphQLIRPrinter.js
Expand Up @@ -304,13 +304,14 @@ 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 (Array.isArray(value)) {
invariant(
Expand Down
14 changes: 12 additions & 2 deletions packages/relay-compiler/core/__tests__/GraphQLIRVisitor-test.js
Expand Up @@ -95,11 +95,21 @@ describe('GraphQLIRVisitor', () => {
},
Literal: {
leave(node: Literal) {
const mutator = value => {
// Keep enums valid
if (value === 'WEB') {
return 'MOBILE';
} else if (value === 'HELPFUL') {
return 'DERISIVE';
} 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
Expand Up @@ -79,10 +79,10 @@ 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

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) {
Expand Down
Expand Up @@ -38,7 +38,7 @@ fragment UserFragment on User @argumentDefinitions(
}
name @include(if: $cond)
otherName: name @customDirective(level: 3)
thumbnail: profilePicture(size: 32) {
thumbnail: profilePicture(size: 32, cropPosition: CENTER, fileExtension: PNG) {
height
width
src: uri
Expand Down Expand Up @@ -90,7 +90,7 @@ fragment UserFragment on User @argumentDefinitions(
}
name @include(if: $cond)
otherName: name @customDirective(level: 3)
thumbnail: profilePicture(size: 32) {
thumbnail: profilePicture(size: 32, cropPosition: CENTER, fileExtension: PNG) {
height
width
src: uri
Expand Down
Expand Up @@ -34,7 +34,7 @@ fragment UserFragment on User @argumentDefinitions(
}
name @include(if: $cond)
otherName: name @customDirective(level: 3)
thumbnail: profilePicture(size: 32) {
thumbnail: profilePicture(size: 32, cropPosition: CENTER, fileExtension: PNG) {
height
width
src: uri
Expand Down
36 changes: 33 additions & 3 deletions packages/relay-test-utils/RelayTestSchema.js
Expand Up @@ -15,7 +15,37 @@ const RelayTestSchemaPath = require('./RelayTestSchemaPath');
const fs = require('fs');

const {buildASTSchema, parse} = require('graphql');
const {SchemaComposer} = require('graphql-compose');

module.exports = buildASTSchema(
parse(fs.readFileSync(RelayTestSchemaPath, 'utf8'), {assumeValid: true}),
);
function buildSchema() {
// Compose upstream is going to add AST directives soon, making this simpler
const composer = new SchemaComposer();
Copy link
Contributor

Choose a reason for hiding this comment

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

graphql-js supports extending schemas, can we just use that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could you point me to that? extendSchema only support extending via AST.

const initialSchema = buildASTSchema(
parse(fs.readFileSync(RelayTestSchemaPath, 'utf8'), {assumeValid: true}),
);

Object.keys(initialSchema.getTypeMap()).forEach(typeName => {
const type = initialSchema.getType(typeName);
composer.add(type);
});

const CropPositionETC = composer.getETC('CropPosition');
CropPositionETC.setFields({
TOP: {value: 1},
CENTER: {value: 2},
BOTTOM: {value: 3},
LEFT: {value: 4},
RIGHT: {value: 5},
});
const FileExtensionETC = composer.getETC('FileExtension');
FileExtensionETC.setFields({
JPG: {value: 'jpg'},
PNG: {value: 'png'},
});

return composer.buildSchema({
directives: initialSchema.getDirectives(),
});
}

module.exports = buildSchema();
1 change: 1 addition & 0 deletions packages/relay-test-utils/package.json
Expand Up @@ -13,6 +13,7 @@
"dependencies": {
"@babel/runtime": "^7.0.0",
"fbjs": "^1.0.0",
"graphql-compose": "^5.8.0",
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Same question, we should use the built-in mechanism to extend schemas.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's my "lodash" for GraphQL type system 😈

If not today then it will be used tomorrow. Just give me some time to make this lib popular and right solution for such kind of problems.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Which mechanism? extendSchema only supports extending through AST.

"iterall": "^1.2.1",
"prop-types": "^15.5.8",
"relay-compiler": "2.0.0",
Expand Down
20 changes: 19 additions & 1 deletion packages/relay-test-utils/testschema.graphql
Expand Up @@ -744,7 +744,12 @@ 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,
cropPosition: CropPosition,
fileExtension: FileExtension
): Image
profile_picture(scale: Float): Image
segments(first: Int): Segments
screennames: [Screenname]
Expand Down Expand Up @@ -838,6 +843,19 @@ enum TopLevelCommentsOrdering {
toplevel
}

enum CropPosition {
TOP
CENTER
BOTTOM
LEFT
RIGHT
}

enum FileExtension {
JPG
PNG
}

type Settings {
cache_id: ID
notificationSounds: Boolean
Expand Down