From c24746315c7c6ca1ada5796b7f1ab5effba42b40 Mon Sep 17 00:00:00 2001 From: Maksim Ignatev Date: Mon, 6 Nov 2023 18:00:55 +0400 Subject: [PATCH] Add option to render Kotlin functions with suspend modifier --- docs/codegen-options.md | 3 +- .../gradle/GraphQLCodegenGradleTask.java | 14 +++++ .../graphql/codegen/GraphQLCodegenMojo.java | 9 +++ ...dDefinitionsToResolverDataModelMapper.java | 1 + .../model/GraphQLCodegenConfiguration.java | 7 +++ .../graphql/codegen/model/MappingConfig.java | 12 ++++ .../codegen/model/MappingConfigConstants.java | 3 + ...MappingConfigDefaultValuesInitializer.java | 4 ++ .../graphql/codegen/model/MappingContext.java | 5 ++ .../codegen/model/OperationDefinition.java | 9 +++ .../templates/kotlin-lang/operations.ftl | 2 +- .../kotlin/GraphQLCodegenSuspendTest.java | 62 +++++++++++++++++++ .../expected-classes/kt/suspend/Friend.kt.txt | 9 +++ .../kt/suspend/FriendsQueryResolver.kt.txt | 10 +++ .../kt/suspend/QueryResolver.kt.txt | 10 +++ .../resources/schemas/kt/suspend.graphqls | 7 +++ 16 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/kobylynskyi/graphql/codegen/kotlin/GraphQLCodegenSuspendTest.java create mode 100644 src/test/resources/expected-classes/kt/suspend/Friend.kt.txt create mode 100644 src/test/resources/expected-classes/kt/suspend/FriendsQueryResolver.kt.txt create mode 100644 src/test/resources/expected-classes/kt/suspend/QueryResolver.kt.txt create mode 100644 src/test/resources/schemas/kt/suspend.graphqls diff --git a/docs/codegen-options.md b/docs/codegen-options.md index f42e47dec..0a552a2a4 100644 --- a/docs/codegen-options.md +++ b/docs/codegen-options.md @@ -31,7 +31,7 @@ | `typeResolverPrefix` | String | Empty | Sets the prefix for GraphQL type resolver classes. | | `typeResolverSuffix` | String | `Resolver` | Sets the suffix for GraphQL type resolver classes. | | `customTypesMapping` | Map(String,String) | Empty | *See [CustomTypesMapping](#option-customtypesmapping)* | -| `customTemplatesRoot` | File | Project's dir | Use to supply the path the to custom FreeMarker templates root directory. | +| `customTemplatesRoot` | File | Project's dir | Use to supply the path the to custom FreeMarker templates root directory. | | `customTemplates` | Map(String,String) | Empty | Use to supply paths to custom FreeMarker templates for code generation. | | `customAnnotationsMapping` | Map(String,String[]) | Empty | *See [CustomAnnotationsMapping](#option-customannotationsmapping)* | | `directiveAnnotationsMapping` | Map(String,String[]) | Empty | *See [DirectiveAnnotationsMapping](#option-directiveannotationsmapping)* | @@ -45,6 +45,7 @@ | `generateModelsForRootTypes` | Boolean | False | Specifies whether model classes should be generated for `type Query`, `type Subscription`, `type Mutation`. | | `useOptionalForNullableReturnTypes` | Boolean | False | Specifies whether nullable return types of api methods should be wrapped into [`java.util.Optional<>`](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/Optional.html). Lists will not be wrapped. | | `generateApisWithThrowsException` | Boolean | True | Specifies whether api interface methods should have `throws Exception` in signature. | +| `generateApisWithSuspendFunctions` | Boolean | False | Specifies whether api interface methods should have `suspend` modifier in signature. Only supported in Kotlin. | | `generateNoArgsConstructorOnly` | Boolean | False | Specifies whether model classes should only have a no-args constructor. All-args constructor will not be generated in case value is true | | `generateModelsWithPublicFields` | Boolean | False | Specifies whether model classes should have public fields and NO getters/setters. By default, fields are private and there are getters/setters for each field. | | `apiReturnType` | String | Empty | Return type for api methods (query/mutation). For example: `reactor.core.publisher.Mono`, etc. | diff --git a/plugins/gradle/graphql-java-codegen-gradle-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/gradle/GraphQLCodegenGradleTask.java b/plugins/gradle/graphql-java-codegen-gradle-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/gradle/GraphQLCodegenGradleTask.java index 89eb7f255..d7b958e2e 100644 --- a/plugins/gradle/graphql-java-codegen-gradle-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/gradle/GraphQLCodegenGradleTask.java +++ b/plugins/gradle/graphql-java-codegen-gradle-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/gradle/GraphQLCodegenGradleTask.java @@ -87,6 +87,8 @@ public class GraphQLCodegenGradleTask extends DefaultTask implements GraphQLCode private Boolean generateModelsForRootTypes = MappingConfigConstants.DEFAULT_GENERATE_MODELS_FOR_ROOT_TYPES; private Boolean useOptionalForNullableReturnTypes = MappingConfigConstants.DEFAULT_USE_OPTIONAL_FOR_NULLABLE_RETURN_TYPES; private Boolean generateApisWithThrowsException = MappingConfigConstants.DEFAULT_GENERATE_APIS_WITH_THROWS_EXCEPTION; + private Boolean generateApisWithSuspendFunctions = + MappingConfigConstants.DEFAULT_GENERATE_APIS_WITH_SUSPEND_FUNCTIONS; private Boolean generateJacksonTypeIdResolver = MappingConfigConstants.DEFAULT_GENERATE_JACKSON_TYPE_ID_RESOLVER; private Boolean addGeneratedAnnotation = MappingConfigConstants.DEFAULT_ADD_GENERATED_ANNOTATION; private Boolean generateNoArgsConstructorOnly = MappingConfigConstants.DEFAULT_GENERATE_NOARGS_CONSTRUCTOR_ONLY; @@ -167,6 +169,7 @@ public void generate() throws Exception { mappingConfig.setGenerateToString(generateToString); mappingConfig.setUseOptionalForNullableReturnTypes(useOptionalForNullableReturnTypes); mappingConfig.setGenerateApisWithThrowsException(generateApisWithThrowsException); + mappingConfig.setGenerateApisWithSuspendFunctions(generateApisWithSuspendFunctions); mappingConfig.setGenerateJacksonTypeIdResolver(generateJacksonTypeIdResolver); mappingConfig.setGenerateNoArgsConstructorOnly(generateNoArgsConstructorOnly); mappingConfig.setGenerateModelsWithPublicFields(generateModelsWithPublicFields); @@ -688,6 +691,17 @@ public void setGenerateApisWithThrowsException(Boolean generateApisWithThrowsExc this.generateApisWithThrowsException = generateApisWithThrowsException; } + @Input + @Optional + @Override + public Boolean getGenerateApisWithSuspendFunctions() { + return generateApisWithSuspendFunctions; + } + + public void setGenerateApisWithSuspendFunctions(Boolean generateApisWithSuspendFunctions) { + this.generateApisWithSuspendFunctions = generateApisWithSuspendFunctions; + } + @Input @Optional @Override diff --git a/plugins/maven/graphql-java-codegen-maven-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/GraphQLCodegenMojo.java b/plugins/maven/graphql-java-codegen-maven-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/GraphQLCodegenMojo.java index 25eac92e4..2d7a8380a 100644 --- a/plugins/maven/graphql-java-codegen-maven-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/GraphQLCodegenMojo.java +++ b/plugins/maven/graphql-java-codegen-maven-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/GraphQLCodegenMojo.java @@ -155,6 +155,9 @@ public class GraphQLCodegenMojo extends AbstractMojo implements GraphQLCodegenCo @Parameter(defaultValue = MappingConfigConstants.DEFAULT_GENERATE_APIS_WITH_THROWS_EXCEPTION_STRING) private boolean generateApisWithThrowsException; + @Parameter(defaultValue = MappingConfigConstants.DEFAULT_GENERATE_APIS_WITH_SUSPEND_FUNCTIONS_STRING) + private boolean generateApisWithSuspendFunctions; + @Parameter(defaultValue = MappingConfigConstants.DEFAULT_GENERATE_JACKSON_TYPE_ID_RESOLVER_STRING) private boolean generateJacksonTypeIdResolver; @@ -285,6 +288,7 @@ public void execute() throws MojoExecutionException { mappingConfig.setGenerateModelsForRootTypes(generateModelsForRootTypes); mappingConfig.setUseOptionalForNullableReturnTypes(useOptionalForNullableReturnTypes); mappingConfig.setGenerateApisWithThrowsException(generateApisWithThrowsException); + mappingConfig.setGenerateApisWithSuspendFunctions(generateApisWithSuspendFunctions); mappingConfig.setGenerateJacksonTypeIdResolver(generateJacksonTypeIdResolver); mappingConfig.setAddGeneratedAnnotation(addGeneratedAnnotation); mappingConfig.setGeneratedAnnotation(generatedAnnotation); @@ -550,6 +554,11 @@ public Boolean getGenerateApisWithThrowsException() { return generateApisWithThrowsException; } + @Override + public Boolean getGenerateApisWithSuspendFunctions() { + return generateApisWithSuspendFunctions; + } + @Override public Boolean getGenerateJacksonTypeIdResolver() { return generateJacksonTypeIdResolver; diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/FieldDefinitionsToResolverDataModelMapper.java b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/FieldDefinitionsToResolverDataModelMapper.java index ab33b9658..a8a0626f2 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/FieldDefinitionsToResolverDataModelMapper.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/FieldDefinitionsToResolverDataModelMapper.java @@ -212,6 +212,7 @@ private OperationDefinition map(MappingContext mappingContext, ExtendedFieldDefi operation.setJavaDoc(fieldDef.getJavaDoc()); operation.setDeprecated(DeprecatedDefinitionBuilder.build(mappingContext, fieldDef)); operation.setThrowsException(mappingContext.getGenerateApisWithThrowsException()); + operation.setSuspend(mappingContext.getGenerateApisWithSuspendFunctions()); return operation; } diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/model/GraphQLCodegenConfiguration.java b/src/main/java/com/kobylynskyi/graphql/codegen/model/GraphQLCodegenConfiguration.java index 387fd0f09..dfb7db970 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/model/GraphQLCodegenConfiguration.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/model/GraphQLCodegenConfiguration.java @@ -267,6 +267,13 @@ public interface GraphQLCodegenConfiguration { */ Boolean getGenerateApisWithThrowsException(); + /** + * Whether signatures of API interface methods should have suspend modifier. + * + * @return true if API interfaces methods signature should contain suspend modifier. + */ + Boolean getGenerateApisWithSuspendFunctions(); + /** * Specifies whether generated classes should be annotated with @Generated * diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/model/MappingConfig.java b/src/main/java/com/kobylynskyi/graphql/codegen/model/MappingConfig.java index 0ab5e3b22..dc29f4607 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/model/MappingConfig.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/model/MappingConfig.java @@ -53,6 +53,7 @@ public class MappingConfig implements GraphQLCodegenConfiguration, Combinable javaDoc = new ArrayList<>(); private DeprecatedDefinition deprecated; private boolean throwsException; + private boolean suspend; public String getName() { return name; @@ -90,4 +91,12 @@ public boolean isThrowsException() { public void setThrowsException(boolean throwsException) { this.throwsException = throwsException; } + + public boolean isSuspend() { + return suspend; + } + + public void setSuspend(boolean suspend) { + this.suspend = suspend; + } } diff --git a/src/main/resources/templates/kotlin-lang/operations.ftl b/src/main/resources/templates/kotlin-lang/operations.ftl index 859744daa..1802a3537 100755 --- a/src/main/resources/templates/kotlin-lang/operations.ftl +++ b/src/main/resources/templates/kotlin-lang/operations.ftl @@ -38,7 +38,7 @@ interface ${className}<#if implements?has_content> : <#list implements as interf <#if operation.throwsException> @Throws(Exception::class) - fun ${operation.name}(<#list operation.parameters as param><#list param.annotations as paramAnnotation>@${paramAnnotation}<#if param.annotations?has_content> ${param.name}: ${param.type}<#if param_has_next>, ): ${operation.type} + <#if operation.suspend>suspend fun ${operation.name}(<#list operation.parameters as param><#list param.annotations as paramAnnotation>@${paramAnnotation}<#if param.annotations?has_content> ${param.name}: ${param.type}<#if param_has_next>, ): ${operation.type} } diff --git a/src/test/java/com/kobylynskyi/graphql/codegen/kotlin/GraphQLCodegenSuspendTest.java b/src/test/java/com/kobylynskyi/graphql/codegen/kotlin/GraphQLCodegenSuspendTest.java new file mode 100644 index 000000000..7f99e676e --- /dev/null +++ b/src/test/java/com/kobylynskyi/graphql/codegen/kotlin/GraphQLCodegenSuspendTest.java @@ -0,0 +1,62 @@ +package com.kobylynskyi.graphql.codegen.kotlin; + +import com.kobylynskyi.graphql.codegen.MaxQueryTokensExtension; +import com.kobylynskyi.graphql.codegen.TestUtils; +import com.kobylynskyi.graphql.codegen.model.GeneratedLanguage; +import com.kobylynskyi.graphql.codegen.model.MappingConfig; +import com.kobylynskyi.graphql.codegen.utils.Utils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.io.File; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import static com.kobylynskyi.graphql.codegen.TestUtils.assertSameTrimmedContent; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toSet; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@ExtendWith(MaxQueryTokensExtension.class) +class GraphQLCodegenSuspendTest { + + private final File outputBuildDir = new File("build/generated"); + private final MappingConfig mappingConfig = new MappingConfig(); + + @BeforeEach + void init() { + mappingConfig.setGenerateApisWithSuspendFunctions(true); + mappingConfig.setGeneratedLanguage(GeneratedLanguage.KOTLIN); + } + + @AfterEach + void cleanup() { + Utils.deleteDir(outputBuildDir); + } + + @Test + void generate_ApiWithSuspendFunction() throws Exception { + new KotlinGraphQLCodegen( + singletonList("src/test/resources/schemas/kt/suspend.graphqls"), + outputBuildDir, mappingConfig, TestUtils.getStaticGeneratedInfo(mappingConfig) + ).generate(); + + File[] files = Objects.requireNonNull(outputBuildDir.listFiles()); + + Set generatedFileNames = Arrays.stream(files).map(File::getName).collect(toSet()); + assertEquals(new HashSet<>(asList("FriendsQueryResolver.kt", "QueryResolver.kt", "Friend.kt")), + generatedFileNames); + + for (File file : files) { + assertSameTrimmedContent( + new File(String.format("src/test/resources/expected-classes/kt/suspend/%s.txt", file.getName())), + file); + } + } + +} diff --git a/src/test/resources/expected-classes/kt/suspend/Friend.kt.txt b/src/test/resources/expected-classes/kt/suspend/Friend.kt.txt new file mode 100644 index 000000000..4fd887790 --- /dev/null +++ b/src/test/resources/expected-classes/kt/suspend/Friend.kt.txt @@ -0,0 +1,9 @@ +@javax.annotation.Generated( + value = ["com.kobylynskyi.graphql.codegen.GraphQLCodegen"], + date = "2020-12-31T23:59:59-0500" +) +data class Friend( + val name: String +) { + +} diff --git a/src/test/resources/expected-classes/kt/suspend/FriendsQueryResolver.kt.txt b/src/test/resources/expected-classes/kt/suspend/FriendsQueryResolver.kt.txt new file mode 100644 index 000000000..4600a6f47 --- /dev/null +++ b/src/test/resources/expected-classes/kt/suspend/FriendsQueryResolver.kt.txt @@ -0,0 +1,10 @@ +@javax.annotation.Generated( + value = ["com.kobylynskyi.graphql.codegen.GraphQLCodegen"], + date = "2020-12-31T23:59:59-0500" +) +interface FriendsQueryResolver { + + @Throws(Exception::class) + suspend fun friends(num: Int): List + +} diff --git a/src/test/resources/expected-classes/kt/suspend/QueryResolver.kt.txt b/src/test/resources/expected-classes/kt/suspend/QueryResolver.kt.txt new file mode 100644 index 000000000..1fe98c154 --- /dev/null +++ b/src/test/resources/expected-classes/kt/suspend/QueryResolver.kt.txt @@ -0,0 +1,10 @@ +@javax.annotation.Generated( + value = ["com.kobylynskyi.graphql.codegen.GraphQLCodegen"], + date = "2020-12-31T23:59:59-0500" +) +interface QueryResolver { + + @Throws(Exception::class) + suspend fun friends(num: Int): List + +} diff --git a/src/test/resources/schemas/kt/suspend.graphqls b/src/test/resources/schemas/kt/suspend.graphqls new file mode 100644 index 000000000..7193c52e5 --- /dev/null +++ b/src/test/resources/schemas/kt/suspend.graphqls @@ -0,0 +1,7 @@ +type Query { + friends(num: Int!): [Friend!]! +} + +type Friend { + name: String! +}