From c36f23d27182e143284432f259fe412e90b1041a Mon Sep 17 00:00:00 2001 From: Mohamed Ishad Date: Thu, 24 Oct 2024 06:57:26 +0530 Subject: [PATCH 1/3] Implement type generation with @graphql:ID annotation for ID types --- .../typesWithMutationDefault.bal | 2 +- .../generator/ServiceTypesGenerator.java | 78 ++++++++++++++++--- 2 files changed, 70 insertions(+), 10 deletions(-) diff --git a/graphql-cli/src/test/resources/serviceGen/expectedServices/typesWithMutationDefault.bal b/graphql-cli/src/test/resources/serviceGen/expectedServices/typesWithMutationDefault.bal index dc59263b..247bd9dd 100644 --- a/graphql-cli/src/test/resources/serviceGen/expectedServices/typesWithMutationDefault.bal +++ b/graphql-cli/src/test/resources/serviceGen/expectedServices/typesWithMutationDefault.bal @@ -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 { diff --git a/graphql-code-generator/src/main/java/io/ballerina/graphql/generator/service/generator/ServiceTypesGenerator.java b/graphql-code-generator/src/main/java/io/ballerina/graphql/generator/service/generator/ServiceTypesGenerator.java index fa7ddb23..4a2b4928 100644 --- a/graphql-code-generator/src/main/java/io/ballerina/graphql/generator/service/generator/ServiceTypesGenerator.java +++ b/graphql-code-generator/src/main/java/io/ballerina/graphql/generator/service/generator/ServiceTypesGenerator.java @@ -450,17 +450,36 @@ private NodeList generateRecordTypeFieldsForGraphQLInputObjectFields( List inputTypeFields) throws ServiceGenerationException { List fields = new ArrayList<>(); for (GraphQLInputObjectField field : inputTypeFields) { + MetadataNode metadataNode; + if (CodeGeneratorConstants.GRAPHQL_ID_TYPE.equals(field.getType())) { + metadataNode = createMetadataNode( + null, + createNodeList( + createAnnotationNode( + createToken(SyntaxKind.AT_TOKEN), + createQualifiedNameReferenceNode( + createIdentifierToken(CodeGeneratorConstants.GRAPHQL), + createToken(SyntaxKind.COLON_TOKEN), + createIdentifierToken(CodeGeneratorConstants.GRAPHQL_ID_TYPE) + ), + null + ) + ) + ); + } else { + metadataNode = generateMetadata(field.getDescription(), null, field.isDeprecated(), + field.getDeprecationReason(), false); + } + 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))); } } @@ -471,9 +490,28 @@ private NodeList generateRecordTypeFieldsForGraphQLFieldDefinitions( List typeInputFields) throws ServiceGenerationException { List fields = new ArrayList<>(); for (GraphQLFieldDefinition field : typeInputFields) { + MetadataNode metadataNode; + if (CodeGeneratorConstants.GRAPHQL_ID_TYPE.equals(field.getType())) { + metadataNode = createMetadataNode( + null, + createNodeList( + createAnnotationNode( + createToken(SyntaxKind.AT_TOKEN), + createQualifiedNameReferenceNode( + createIdentifierToken(CodeGeneratorConstants.GRAPHQL), + createToken(SyntaxKind.COLON_TOKEN), + createIdentifierToken(CodeGeneratorConstants.GRAPHQL_ID_TYPE) + ), + null + ) + ) + ); + } else { + metadataNode = generateMetadata(field.getDescription(), null, field.isDeprecated(), + field.getDeprecationReason(), false); + } fields.add(createRecordFieldNode( - generateMetadata(field.getDescription(), field.getArguments(), field.isDeprecated(), - field.getDeprecationReason(), false), null, generateTypeDescriptor(field.getType()), + metadataNode, null, generateTypeDescriptor(field.getType()), createIdentifierToken(field.getName()), null, createToken(SyntaxKind.SEMICOLON_TOKEN))); } return createNodeList(fields); @@ -801,15 +839,37 @@ private MarkdownDocumentationLineNode generateMarkdownDocumentationLine(String d private ReturnTypeDescriptorNode generateMethodSignatureReturnType(GraphQLOutputType type, boolean isStream) throws ServiceGenerationException { TypeDescriptorNode typeDescriptor = generateTypeDescriptor(type); + GraphQLType unWrappedType = type; + if (type instanceof GraphQLNonNull) { + unWrappedType = ((GraphQLNonNull) type).getWrappedType(); + } + + NodeList nodeList; + if (unWrappedType instanceof GraphQLScalarType + && ((GraphQLScalarType) unWrappedType).getName().equals(CodeGeneratorConstants.GRAPHQL_ID_TYPE)) { + nodeList = createNodeList( + createAnnotationNode( + createToken(SyntaxKind.AT_TOKEN), + createQualifiedNameReferenceNode( + createIdentifierToken(CodeGeneratorConstants.GRAPHQL), + createToken(SyntaxKind.COLON_TOKEN), + createIdentifierToken(CodeGeneratorConstants.GRAPHQL_ID_TYPE) + ), + null + ) + ); + } else { + nodeList = createEmptyNodeList(); + } if (isStream) { StreamTypeDescriptorNode streamTypeDescriptor = 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), nodeList, streamTypeDescriptor); } else { - return createReturnTypeDescriptorNode(createToken(SyntaxKind.RETURNS_KEYWORD), createEmptyNodeList(), + return createReturnTypeDescriptorNode(createToken(SyntaxKind.RETURNS_KEYWORD), nodeList, typeDescriptor); } } From 9dec925799419e9a4e39d428e8bdb39e5afbd7d6 Mon Sep 17 00:00:00 2001 From: Mohamed Ishad Date: Fri, 25 Oct 2024 20:50:14 +0530 Subject: [PATCH 2/3] Implement service generation with @graphql:ID annotation in service functions --- .../ballerina/ServiceGeneratorTest.java | 29 ++++ .../ballerina/ServiceTypesGeneratorTest.java | 26 ++++ .../serviceForSchemaWithIDTypeApi.bal | 15 ++ .../typesWithIDTypeRecordsAllowed.bal | 20 +++ .../valid/SchemaWithIDTypeApi.graphql | 21 +++ .../generator/ServiceTypesGenerator.java | 133 +++++++++--------- 6 files changed, 176 insertions(+), 68 deletions(-) create mode 100644 graphql-cli/src/test/resources/serviceGen/expectedServices/serviceForSchemaWithIDTypeApi.bal create mode 100644 graphql-cli/src/test/resources/serviceGen/expectedServices/typesWithIDTypeRecordsAllowed.bal create mode 100644 graphql-cli/src/test/resources/serviceGen/graphqlSchemas/valid/SchemaWithIDTypeApi.graphql diff --git a/graphql-cli/src/test/java/io/ballerina/graphql/generator/ballerina/ServiceGeneratorTest.java b/graphql-cli/src/test/java/io/ballerina/graphql/generator/ballerina/ServiceGeneratorTest.java index 2bfaa69d..1cccf98b 100644 --- a/graphql-cli/src/test/java/io/ballerina/graphql/generator/ballerina/ServiceGeneratorTest.java +++ b/graphql-cli/src/test/java/io/ballerina/graphql/generator/ballerina/ServiceGeneratorTest.java @@ -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()); + } + } } diff --git a/graphql-cli/src/test/java/io/ballerina/graphql/generator/ballerina/ServiceTypesGeneratorTest.java b/graphql-cli/src/test/java/io/ballerina/graphql/generator/ballerina/ServiceTypesGeneratorTest.java index 96732bce..10228e73 100644 --- a/graphql-cli/src/test/java/io/ballerina/graphql/generator/ballerina/ServiceTypesGeneratorTest.java +++ b/graphql-cli/src/test/java/io/ballerina/graphql/generator/ballerina/ServiceTypesGeneratorTest.java @@ -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[][]{ diff --git a/graphql-cli/src/test/resources/serviceGen/expectedServices/serviceForSchemaWithIDTypeApi.bal b/graphql-cli/src/test/resources/serviceGen/expectedServices/serviceForSchemaWithIDTypeApi.bal new file mode 100644 index 00000000..1dcdaf9a --- /dev/null +++ b/graphql-cli/src/test/resources/serviceGen/expectedServices/serviceForSchemaWithIDTypeApi.bal @@ -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[] { + } +} diff --git a/graphql-cli/src/test/resources/serviceGen/expectedServices/typesWithIDTypeRecordsAllowed.bal b/graphql-cli/src/test/resources/serviceGen/expectedServices/typesWithIDTypeRecordsAllowed.bal new file mode 100644 index 00000000..8b414b3e --- /dev/null +++ b/graphql-cli/src/test/resources/serviceGen/expectedServices/typesWithIDTypeRecordsAllowed.bal @@ -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; +|}; diff --git a/graphql-cli/src/test/resources/serviceGen/graphqlSchemas/valid/SchemaWithIDTypeApi.graphql b/graphql-cli/src/test/resources/serviceGen/graphqlSchemas/valid/SchemaWithIDTypeApi.graphql new file mode 100644 index 00000000..c852b271 --- /dev/null +++ b/graphql-cli/src/test/resources/serviceGen/graphqlSchemas/valid/SchemaWithIDTypeApi.graphql @@ -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! +} diff --git a/graphql-code-generator/src/main/java/io/ballerina/graphql/generator/service/generator/ServiceTypesGenerator.java b/graphql-code-generator/src/main/java/io/ballerina/graphql/generator/service/generator/ServiceTypesGenerator.java index 4a2b4928..d68a22e0 100644 --- a/graphql-code-generator/src/main/java/io/ballerina/graphql/generator/service/generator/ServiceTypesGenerator.java +++ b/graphql-code-generator/src/main/java/io/ballerina/graphql/generator/service/generator/ServiceTypesGenerator.java @@ -450,27 +450,8 @@ private NodeList generateRecordTypeFieldsForGraphQLInputObjectFields( List inputTypeFields) throws ServiceGenerationException { List fields = new ArrayList<>(); for (GraphQLInputObjectField field : inputTypeFields) { - MetadataNode metadataNode; - if (CodeGeneratorConstants.GRAPHQL_ID_TYPE.equals(field.getType())) { - metadataNode = createMetadataNode( - null, - createNodeList( - createAnnotationNode( - createToken(SyntaxKind.AT_TOKEN), - createQualifiedNameReferenceNode( - createIdentifierToken(CodeGeneratorConstants.GRAPHQL), - createToken(SyntaxKind.COLON_TOKEN), - createIdentifierToken(CodeGeneratorConstants.GRAPHQL_ID_TYPE) - ), - null - ) - ) - ); - } else { - metadataNode = generateMetadata(field.getDescription(), null, field.isDeprecated(), - field.getDeprecationReason(), false); - } - + MetadataNode metadataNode = getMetadataNode(field.getType(), field.getDescription(), + field.isDeprecated(), field.getDeprecationReason()); if (field.hasSetDefaultValue()) { Object value = field.getInputFieldDefaultValue().getValue(); ExpressionNode generatedDefaultValue = generateArgDefaultValue(value); @@ -490,28 +471,10 @@ private NodeList generateRecordTypeFieldsForGraphQLFieldDefinitions( List typeInputFields) throws ServiceGenerationException { List fields = new ArrayList<>(); for (GraphQLFieldDefinition field : typeInputFields) { - MetadataNode metadataNode; - if (CodeGeneratorConstants.GRAPHQL_ID_TYPE.equals(field.getType())) { - metadataNode = createMetadataNode( - null, - createNodeList( - createAnnotationNode( - createToken(SyntaxKind.AT_TOKEN), - createQualifiedNameReferenceNode( - createIdentifierToken(CodeGeneratorConstants.GRAPHQL), - createToken(SyntaxKind.COLON_TOKEN), - createIdentifierToken(CodeGeneratorConstants.GRAPHQL_ID_TYPE) - ), - null - ) - ) - ); - } else { - metadataNode = generateMetadata(field.getDescription(), null, field.isDeprecated(), - field.getDeprecationReason(), false); - } fields.add(createRecordFieldNode( - metadataNode, null, generateTypeDescriptor(field.getType()), + getMetadataNode(field.getType(), field.getDescription(), field.isDeprecated(), + field.getDeprecationReason()), + null, generateTypeDescriptor(field.getType()), createIdentifierToken(field.getName()), null, createToken(SyntaxKind.SEMICOLON_TOKEN))); } return createNodeList(fields); @@ -839,37 +802,15 @@ private MarkdownDocumentationLineNode generateMarkdownDocumentationLine(String d private ReturnTypeDescriptorNode generateMethodSignatureReturnType(GraphQLOutputType type, boolean isStream) throws ServiceGenerationException { TypeDescriptorNode typeDescriptor = generateTypeDescriptor(type); - GraphQLType unWrappedType = type; - if (type instanceof GraphQLNonNull) { - unWrappedType = ((GraphQLNonNull) type).getWrappedType(); - } - - NodeList nodeList; - if (unWrappedType instanceof GraphQLScalarType - && ((GraphQLScalarType) unWrappedType).getName().equals(CodeGeneratorConstants.GRAPHQL_ID_TYPE)) { - nodeList = createNodeList( - createAnnotationNode( - createToken(SyntaxKind.AT_TOKEN), - createQualifiedNameReferenceNode( - createIdentifierToken(CodeGeneratorConstants.GRAPHQL), - createToken(SyntaxKind.COLON_TOKEN), - createIdentifierToken(CodeGeneratorConstants.GRAPHQL_ID_TYPE) - ), - null - ) - ); - } else { - nodeList = createEmptyNodeList(); - } if (isStream) { StreamTypeDescriptorNode streamTypeDescriptor = createStreamTypeDescriptorNode(createToken(SyntaxKind.STREAM_KEYWORD), createStreamTypeParamsNode(createToken(SyntaxKind.LT_TOKEN), typeDescriptor, null, null, createToken(SyntaxKind.GT_TOKEN))); - return createReturnTypeDescriptorNode(createToken(SyntaxKind.RETURNS_KEYWORD), nodeList, + return createReturnTypeDescriptorNode(createToken(SyntaxKind.RETURNS_KEYWORD), getAnnotationNodeList(type), streamTypeDescriptor); } else { - return createReturnTypeDescriptorNode(createToken(SyntaxKind.RETURNS_KEYWORD), nodeList, + return createReturnTypeDescriptorNode(createToken(SyntaxKind.RETURNS_KEYWORD), getAnnotationNodeList(type), typeDescriptor); } } @@ -891,13 +832,13 @@ private SeparatedNodeList generateMethodSignatureParams(List 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(); + } + } + } From 8e71722c592b49e69d62af512b87828f45060d58 Mon Sep 17 00:00:00 2001 From: Mohamed Ishad Date: Tue, 29 Oct 2024 09:27:02 +0530 Subject: [PATCH 3/3] Fix failing test case --- .../generator/service/generator/ServiceTypesGenerator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphql-code-generator/src/main/java/io/ballerina/graphql/generator/service/generator/ServiceTypesGenerator.java b/graphql-code-generator/src/main/java/io/ballerina/graphql/generator/service/generator/ServiceTypesGenerator.java index d68a22e0..21642481 100644 --- a/graphql-code-generator/src/main/java/io/ballerina/graphql/generator/service/generator/ServiceTypesGenerator.java +++ b/graphql-code-generator/src/main/java/io/ballerina/graphql/generator/service/generator/ServiceTypesGenerator.java @@ -450,7 +450,7 @@ private NodeList generateRecordTypeFieldsForGraphQLInputObjectFields( List inputTypeFields) throws ServiceGenerationException { List fields = new ArrayList<>(); for (GraphQLInputObjectField field : inputTypeFields) { - MetadataNode metadataNode = getMetadataNode(field.getType(), field.getDescription(), + MetadataNode metadataNode = getMetadataNode(getUnwrappedType(field.getType()), field.getDescription(), field.isDeprecated(), field.getDeprecationReason()); if (field.hasSetDefaultValue()) { Object value = field.getInputFieldDefaultValue().getValue(); @@ -472,7 +472,7 @@ private NodeList generateRecordTypeFieldsForGraphQLFieldDefinitions( List fields = new ArrayList<>(); for (GraphQLFieldDefinition field : typeInputFields) { fields.add(createRecordFieldNode( - getMetadataNode(field.getType(), field.getDescription(), field.isDeprecated(), + getMetadataNode(getUnwrappedType(field.getType()), field.getDescription(), field.isDeprecated(), field.getDeprecationReason()), null, generateTypeDescriptor(field.getType()), createIdentifierToken(field.getName()), null, createToken(SyntaxKind.SEMICOLON_TOKEN)));