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: Convert modern GraphQL descriptions into comments #639

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
9 changes: 8 additions & 1 deletion src/__tests__/fixtures/schemas/multiple/post.graphql
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
extend type Query {
getPost(id: ID!): Post!
getPost(
"This is an inline description"
id: ID!
): Post!
}

extend type Mutation {
"""
This is a description
that includes multiple lines
"""
createPost(post: PostInput!): Post!
}

Expand Down
36 changes: 23 additions & 13 deletions src/__tests__/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ describe('schema', () => {
createUser(post: UserInput!): User!
}

\\"\\"\\"
A User
\\"\\"\\"
#A User
type User {
id: ID!
name: String!
Expand All @@ -43,6 +41,7 @@ describe('schema', () => {
input UserInput {
name: String!
}

",
},
"Type": "AWS::AppSync::GraphQLSchema",
Expand All @@ -60,6 +59,8 @@ describe('schema', () => {
]);
expect(schema.generateSchema()).toMatchInlineSnapshot(`
"type Mutation {
#This is a description
#that includes multiple lines
createPost(post: PostInput!): Post!
createUser(post: UserInput!): User!
}
Expand All @@ -71,13 +72,16 @@ describe('schema', () => {
updatedAt: AWSDateTime!
}

\\"\\"\\"This is a description\\"\\"\\"
#This is a description
input PostInput {
title: String!
}

type Query {
getPost(id: ID!): Post!
getPost(
#This is an inline description
id: ID!
): Post!
getUser: User!
}

Expand All @@ -91,7 +95,8 @@ describe('schema', () => {

input UserInput {
name: String!
}"
}
"
`);
});

Expand All @@ -102,6 +107,8 @@ describe('schema', () => {
]);
expect(schema.generateSchema()).toMatchInlineSnapshot(`
"type Mutation {
#This is a description
#that includes multiple lines
createPost(post: PostInput!): Post!
createUser(post: UserInput!): User!
}
Expand All @@ -113,13 +120,16 @@ describe('schema', () => {
updatedAt: AWSDateTime!
}

\\"\\"\\"This is a description\\"\\"\\"
#This is a description
input PostInput {
title: String!
}

type Query {
getPost(id: ID!): Post!
getPost(
#This is an inline description
id: ID!
): Post!
getUser: User!
}

Expand All @@ -133,7 +143,8 @@ describe('schema', () => {

input UserInput {
name: String!
}"
}
"
`);
});

Expand All @@ -153,7 +164,7 @@ describe('schema', () => {
`);
});

it('should return single files schemas as-is', () => {
it('should return single files schemas with converted descriptions', () => {
const api = new Api(given.appSyncConfig(), plugin);
const schema = new Schema(api, [
'src/__tests__/fixtures/schemas/single/schema.graphql',
Expand All @@ -167,9 +178,7 @@ describe('schema', () => {
createUser(post: UserInput!): User!
}

\\"\\"\\"
A User
\\"\\"\\"
#A User
type User {
id: ID!
name: String!
Expand All @@ -179,6 +188,7 @@ describe('schema', () => {
input UserInput {
name: String!
}

"
`);
});
Expand Down
64 changes: 53 additions & 11 deletions src/resources/Schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { CfnResources } from '../types/cloudFormation';
import { Api } from './Api';
import { flatten } from 'lodash';
import { parse, print } from 'graphql';
import ServerlessError from 'serverless/lib/serverless-error';
import { validateSDL } from 'graphql/validation/validate';
import { mergeTypeDefs } from '@graphql-tools/merge';

Expand Down Expand Up @@ -47,7 +46,7 @@ export class Schema {
};
}

valdiateSchema(schema: string) {
validateSchema(schema: string) {
const errors = validateSDL(parse(schema));
if (errors.length > 0) {
throw new this.api.plugin.serverless.classes.Error(
Expand All @@ -57,6 +56,47 @@ export class Schema {
}
}

// AppSync does not support descriptions from June 2018 spec
// https://spec.graphql.org/June2018/#sec-Descriptions
// so they need to be converted to comments, the space after the # will also be included
// by AppSync in the generated description so we remove it
convertDescriptions(schema: string): string {
const lines = schema.split('\n');
const singleLineComment = /^(?<indent> *)"(?<comment>[^"]+?)"$/;
const singleLineMultilineComment = /^(?<indent> *)"""(?<comment>.+?)"""$/;
const multilineCommentDelimiter = /^(?<indent> *)"""$/;

let inComment = false;
let result = '';

for (const line of lines) {
switch (true) {
case singleLineComment.test(line):
result += `${line.match(singleLineComment)?.groups?.indent}#${
line.match(singleLineComment)?.groups?.comment
}\n`;
break;
case singleLineMultilineComment.test(line):
result += `${
line.match(singleLineMultilineComment)?.groups?.indent
}#${line.match(singleLineMultilineComment)?.groups?.comment}\n`;
break;
case multilineCommentDelimiter.test(line):
inComment = !inComment;
break;
case inComment:
result += `${
line.match(/^(?<indent> *)/)?.groups?.indent
}#${line.trimStart()}\n`;
break;
default:
result += line + '\n';
}
}

return result;
}

generateSchema() {
const schemaFiles = flatten(globby.sync(this.schemas));

Expand All @@ -67,23 +107,25 @@ export class Schema {
);
});

this.valdiateSchema(AWS_TYPES + '\n' + schemas.join('\n'));
this.validateSchema(AWS_TYPES + '\n' + schemas.join('\n'));

// Return single files as-is.
if (schemas.length === 1) {
return schemas[0];
return this.convertDescriptions(schemas[0]);
}

// AppSync does not support Object extensions
// https://spec.graphql.org/October2021/#sec-Object-Extensions
// Merge the schemas
return print(
mergeTypeDefs(schemas, {
forceSchemaDefinition: false,
useSchemaDefinition: false,
sort: true,
throwOnConflict: true,
}),
return this.convertDescriptions(
print(
mergeTypeDefs(schemas, {
forceSchemaDefinition: false,
useSchemaDefinition: false,
sort: true,
throwOnConflict: true,
}),
),
);
}
}