Skip to content

Commit

Permalink
Merge pull request #287 from Ishad-M-I-M/service-id-support
Browse files Browse the repository at this point in the history
Annotate GraphQL ID Types with `@graphql:ID` Annotation in Service Generation
  • Loading branch information
DimuthuMadushan authored Oct 31, 2024
2 parents 99973e7 + 8e71722 commit 4c39e24
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 12 deletions.
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();
}
}

}

0 comments on commit 4c39e24

Please sign in to comment.