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

Annotate GraphQL ID Types with @graphql:ID Annotation in Service Generation #287

Merged
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,33 @@ public void testGenerateServiceForSchemaWithDocumentationInResolverFunctions() {
Assert.fail(e.getMessage());
}
}

@Test
public void testGenerateServiceForSchemaWithIDTypes() {
String fileName = "SchemaWithIDTypeApi";
String expectedFile = "serviceForSchemaWithIDTypeApi.bal";
try {
GraphqlServiceProject project = TestUtils.getValidatedMockServiceProject(
this.resourceDir.resolve(Paths.get("serviceGen", "graphqlSchemas", "valid", fileName + ".graphql"))
.toString(), this.tmpDir);
GraphQLSchema graphQLSchema = project.getGraphQLSchema();

ServiceTypesGenerator serviceTypesGenerator = new ServiceTypesGenerator();
serviceTypesGenerator.setFileName(fileName);
serviceTypesGenerator.generateSrc(graphQLSchema);

ServiceGenerator serviceGenerator = new ServiceGenerator();
serviceGenerator.setFileName(fileName);
serviceGenerator.setMethodDeclarations(serviceTypesGenerator.getServiceMethodDeclarations());
String generatedServiceContent = serviceGenerator.generateSrc();
writeContentTo(generatedServiceContent, this.tmpDir, SERVICE_FILE_NAME);
Path expectedServiceFile = resourceDir.resolve(Paths.get("serviceGen", "expectedServices", expectedFile));
String expectedServiceContent = readContentWithFormat(expectedServiceFile);
String writtenServiceTypesContent =
readContentWithFormat(this.tmpDir.resolve("service.bal"));
Assert.assertEquals(expectedServiceContent, writtenServiceTypesContent);
} catch (ServiceGenerationException | IOException | ValidationException e) {
Assert.fail(e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,32 @@ public void testGenerateSrcForSchemaWithInputTypeFieldsHavingDefaultValues() {
}
}

@Test(groups = {"service-type-for-objects"})
public void testGenerateSrcForSchemaWithGraphQLIDType() {
String fileName = "SchemaWithIDTypeApi";
String expectedFile = "typesWithIDTypeRecordsAllowed.bal";
try {
GraphqlServiceProject project = TestUtils.getValidatedMockServiceProject(
this.resourceDir.resolve(Paths.get("serviceGen", "graphqlSchemas", "valid", fileName + ".graphql"))
.toString(), this.tmpDir);
GraphQLSchema graphQLSchema = project.getGraphQLSchema();

ServiceTypesGenerator serviceTypesGenerator = new ServiceTypesGenerator();
serviceTypesGenerator.setFileName(fileName);
serviceTypesGenerator.setUseRecordsForObjects(true);
String generatedServiceTypesContent = serviceTypesGenerator.generateSrc(graphQLSchema);
writeContentTo(generatedServiceTypesContent, this.tmpDir, TYPES_FILE_NAME);

Path expectedServiceTypesFile =
resourceDir.resolve(Paths.get("serviceGen", "expectedServices", expectedFile));
String expectedServiceTypesContent = readContentWithFormat(expectedServiceTypesFile);
String writtenServiceTypesContent = readContentWithFormat(this.tmpDir.resolve("types.bal"));
Assert.assertEquals(expectedServiceTypesContent, writtenServiceTypesContent);
} catch (ValidationException | IOException | ServiceGenerationException e) {
Assert.fail(e.getMessage());
}
}

@DataProvider(name = "invalidSchemasWithExpectedErrorMessages")
public Object[][] getInvalidSchemasWithExpectedErrorMessages() {
return new Object[][]{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import ballerina/graphql;

configurable int port = 9090;

service SchemaWithIDTypeApi on new graphql:Listener(port) {
# Fetch a book by its id
# + id - The id of the book to fetch
resource function get book(@graphql:ID string id) returns Book? {
}

# Fetch a list of books by their ids
# + ids - The list of book ids to fetch
resource function get books(@graphql:ID string[] ids) returns Book[] {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import ballerina/graphql;

type SchemaWithIDTypeApi service object {
*graphql:Service;
# Fetch a book by its id
# + id - The id of the book to fetch
resource function get book(@graphql:ID string id) returns Book?;
# Fetch a list of books by their ids
# + ids - The list of book ids to fetch
resource function get books(@graphql:ID string[] ids) returns Book[];
};

# Represents a book written by an author
public type Book record {|
# The id of the book, unique identifier
@graphql:ID
string id;
# The title of the book
string title;
|};
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type SchemaWithMutationApi service object {
};

public distinct service class Author {
resource function get id() returns string {
resource function get id() returns @graphql:ID string {
}

resource function get name() returns string {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
type Query {
"Fetch a book by its id"
book(
"The id of the book to fetch"
id: ID!
): Book

"Fetch a list of books by their ids"
books(
"The list of book ids to fetch"
ids: [ID!]!
): [Book!]!
}

"Represents a book written by an author"
type Book {
"The id of the book, unique identifier"
id: ID!
"The title of the book"
title: String!
}
Original file line number Diff line number Diff line change
Expand Up @@ -450,17 +450,17 @@ private NodeList<Node> generateRecordTypeFieldsForGraphQLInputObjectFields(
List<GraphQLInputObjectField> inputTypeFields) throws ServiceGenerationException {
List<Node> fields = new ArrayList<>();
for (GraphQLInputObjectField field : inputTypeFields) {
MetadataNode metadataNode = getMetadataNode(getUnwrappedType(field.getType()), field.getDescription(),
field.isDeprecated(), field.getDeprecationReason());
if (field.hasSetDefaultValue()) {
Object value = field.getInputFieldDefaultValue().getValue();
ExpressionNode generatedDefaultValue = generateArgDefaultValue(value);
fields.add(createRecordFieldWithDefaultValueNode(
generateMetadata(field.getDescription(), null, field.isDeprecated(),
field.getDeprecationReason(), false), null, generateTypeDescriptor(field.getType()),
fields.add(createRecordFieldWithDefaultValueNode(metadataNode, null,
generateTypeDescriptor(field.getType()),
createIdentifierToken(field.getName()), createToken(SyntaxKind.EQUAL_TOKEN),
generatedDefaultValue, createToken(SyntaxKind.SEMICOLON_TOKEN)));
} else {
fields.add(createRecordFieldNode(generateMetadata(field.getDescription(), null, field.isDeprecated(),
field.getDeprecationReason(), false), null, generateTypeDescriptor(field.getType()),
fields.add(createRecordFieldNode(metadataNode, null, generateTypeDescriptor(field.getType()),
createIdentifierToken(field.getName()), null, createToken(SyntaxKind.SEMICOLON_TOKEN)));
}
}
Expand All @@ -472,8 +472,9 @@ private NodeList<Node> generateRecordTypeFieldsForGraphQLFieldDefinitions(
List<Node> fields = new ArrayList<>();
for (GraphQLFieldDefinition field : typeInputFields) {
fields.add(createRecordFieldNode(
generateMetadata(field.getDescription(), field.getArguments(), field.isDeprecated(),
field.getDeprecationReason(), false), null, generateTypeDescriptor(field.getType()),
getMetadataNode(getUnwrappedType(field.getType()), field.getDescription(), field.isDeprecated(),
field.getDeprecationReason()),
null, generateTypeDescriptor(field.getType()),
createIdentifierToken(field.getName()), null, createToken(SyntaxKind.SEMICOLON_TOKEN)));
}
return createNodeList(fields);
Expand Down Expand Up @@ -806,10 +807,10 @@ private ReturnTypeDescriptorNode generateMethodSignatureReturnType(GraphQLOutput
createStreamTypeDescriptorNode(createToken(SyntaxKind.STREAM_KEYWORD),
createStreamTypeParamsNode(createToken(SyntaxKind.LT_TOKEN), typeDescriptor, null, null,
createToken(SyntaxKind.GT_TOKEN)));
return createReturnTypeDescriptorNode(createToken(SyntaxKind.RETURNS_KEYWORD), createEmptyNodeList(),
return createReturnTypeDescriptorNode(createToken(SyntaxKind.RETURNS_KEYWORD), getAnnotationNodeList(type),
streamTypeDescriptor);
} else {
return createReturnTypeDescriptorNode(createToken(SyntaxKind.RETURNS_KEYWORD), createEmptyNodeList(),
return createReturnTypeDescriptorNode(createToken(SyntaxKind.RETURNS_KEYWORD), getAnnotationNodeList(type),
typeDescriptor);
}
}
Expand All @@ -831,13 +832,13 @@ private SeparatedNodeList<ParameterNode> generateMethodSignatureParams(List<Grap
Object value = argument.getArgumentDefaultValue().getValue();
ExpressionNode generatedDefaultValue = generateArgDefaultValue(value);
DefaultableParameterNode defaultableParameterNode =
createDefaultableParameterNode(createEmptyNodeList(), argumentType,
createDefaultableParameterNode(getAnnotationNodeList(argument.getType()), argumentType,
createIdentifierToken(argument.getName()), createToken(SyntaxKind.EQUAL_TOKEN),
generatedDefaultValue);
defaultParams.add(defaultableParameterNode);
} else {
RequiredParameterNode requiredParameterNode =
createRequiredParameterNode(createEmptyNodeList(), argumentType,
createRequiredParameterNode(getAnnotationNodeList(argument.getType()), argumentType,
createIdentifierToken(argument.getName()));
requiredParams.add(requiredParameterNode);
}
Expand Down Expand Up @@ -1041,4 +1042,60 @@ private SyntaxKind getTypeDescFor(String typeName) throws ServiceGenerationExcep
String.format(Constants.ONLY_SCALAR_TYPE_ALLOWED, typeName));
}
}


private MetadataNode getMetadataNode(GraphQLType fieldType, String description,
boolean deprecated, String deprecationReason) {
if (fieldType instanceof GraphQLScalarType &&
((GraphQLScalarType) fieldType).getName().equals(CodeGeneratorConstants.GRAPHQL_ID_TYPE)) {
return createMetadataNode(
createMarkdownDocumentationNode(createNodeList(
generateMarkdownDocumentationLines(description, false))),
createNodeList(
createAnnotationNode(
createToken(SyntaxKind.AT_TOKEN),
createQualifiedNameReferenceNode(
createIdentifierToken(CodeGeneratorConstants.GRAPHQL),
createToken(SyntaxKind.COLON_TOKEN),
createIdentifierToken(CodeGeneratorConstants.GRAPHQL_ID_TYPE)
),
null
)
)
);
}
return generateMetadata(description, null, deprecated,
deprecationReason, false);
}

private GraphQLType getUnwrappedType(GraphQLType graphQLType) {
if (graphQLType instanceof GraphQLNonNull) {
return getUnwrappedType(((GraphQLNonNull) graphQLType).getWrappedType());
} else if (graphQLType instanceof GraphQLList) {
return getUnwrappedType(((GraphQLList) graphQLType).getWrappedType());
} else {
return graphQLType;
}
}

private NodeList<AnnotationNode> getAnnotationNodeList(GraphQLType graphQLType) {
GraphQLType unWrappedType = getUnwrappedType(graphQLType);
if (unWrappedType instanceof GraphQLScalarType
&& ((GraphQLScalarType) unWrappedType).getName().equals(CodeGeneratorConstants.GRAPHQL_ID_TYPE)) {
return createNodeList(
createAnnotationNode(
createToken(SyntaxKind.AT_TOKEN),
createQualifiedNameReferenceNode(
createIdentifierToken(CodeGeneratorConstants.GRAPHQL),
createToken(SyntaxKind.COLON_TOKEN),
createIdentifierToken(CodeGeneratorConstants.GRAPHQL_ID_TYPE)
),
null
)
);
} else {
return createEmptyNodeList();
}
}

}
Loading