提交 4379b02c 编写于 作者: B Bogdan Kobylynskyi

Introduce ApiRootInterfaceStrategy and ApiNamePrefixStrategy #142 (#171)

上级 08c594d9
# Codegen Options
| Option | Data Type | Default value | Description |
| :---------------------------------------------: | :------------------------------------------------: | :-------------------------------------------: | ----------- |
| `graphqlSchemaPaths` | List(String) | (falls back to `graphqlSchemas`) | GraphQL schema locations. You can supply multiple paths to GraphQL schemas. To include many schemas from a folder hierarchy, use the `graphqlSchemas` block instead. |
| `graphqlSchemas` | *See [graphqlSchemas](#option-graphqlschemas)* | All `.graphqls`/`.graphql` files in resources | Block to define the input GraphQL schemas, when exact paths are too cumbersome. See table below for a list of options. |
| `outputDir` | String | None | The output target directory into which code will be generated. |
| `jsonConfigurationFile` | String | Empty | Path to an external mapping configuration. |
| `packageName` | String | Empty | Java package for generated classes. |
| `apiPackageName` | String | Empty | Java package for generated api classes (Query, Mutation, Subscription). |
| `modelPackageName` | String | Empty | Java package for generated model classes (type, input, interface, enum, union). |
| `generateBuilder` | Boolean | True | Specifies whether generated model classes should have builder. |
| `generateApis` | Boolean | True | Specifies whether api classes should be generated as well as model classes. |
| `generateAsyncApi` | Boolean | False | If true, then wrap type into `java.util.concurrent.CompletableFuture` or `subscriptionReturnType` |
| `generateDataFetchingEnvironmentArgumentInApis` | Boolean | False | If true, then `graphql.schema.DataFetchingEnvironment env` will be added as a last argument to all methods of root type resolvers and field resolvers. |
| `generateEqualsAndHashCode` | Boolean | False | Specifies whether generated model classes should have equals and hashCode methods defined. |
| `generateToString` | Boolean | False | Specifies whether generated model classes should have toString method defined. |
| `apiNamePrefix` | String | Empty | Sets the prefix for GraphQL api classes (query, mutation, subscription). |
| `apiNameSuffix` | String | Resolver | Sets the suffix for GraphQL api classes (query, mutation, subscription). |
| `modelNamePrefix` | String | Empty | Sets the prefix for GraphQL model classes (type, input, interface, enum, union). |
| `modelNameSuffix` | String | Empty | Sets the suffix for GraphQL model classes (type, input, interface, enum, union). |
| `modelValidationAnnotation` | String | @javax.validation.<br>constraints.NotNull | Annotation for mandatory (NonNull) fields. Can be null/empty. |
| `customTypesMapping` | Map(String,String) | Empty | Can be used to supply custom mappings for scalars. <br/> Supports:<br/> * Map of (GraphqlObjectName.fieldName) to (JavaType) <br/> * Map of (GraphqlType) to (JavaType) |
| `customAnnotationsMapping` | Map(String,String) | Empty | Can be used to supply custom annotations (serializers) for scalars. <br/> Supports:<br/> * Map of (GraphqlObjectName.fieldName) to (JavaAnnotation) <br/> * Map of (GraphqlType) to (JavaAnnotation) |
| `fieldsWithResolvers` | Set(String) | Empty | Fields that require Resolvers should be defined here in format: `TypeName.fieldName` or `TypeName`. |
| `fieldsWithoutResolvers` | Set(String) | Empty | Fields that DO NOT require Resolvers should be defined here in format: `TypeName.fieldName` or `TypeName`. Can be used in conjunction with `generateExtensionFieldsResolvers` option. |
| `generateParameterizedFieldsResolvers` | Boolean | True | If true, then generate separate `Resolver` interface for parametrized fields. If false, then add field to the type definition and ignore field parameters. |
| `generateExtensionFieldsResolvers` | Boolean | False | Specifies whether all fields in extensions (`extend type` and `extend interface`) should be present in Resolver interface instead of the type class itself. |
| `subscriptionReturnType` | String | Empty | Return type for subscription methods. For example: `org.reactivestreams.Publisher`, `io.reactivex.Observable`, etc. |
| `generateClient` | Boolean | False | Specifies whether client-side classes should be generated for each query, mutation and subscription. This includes: `Request` classes (contain input data), `ResponseProjection` classes for each type (contain response fields) and `Response` classes (contain response data). |
| `requestSuffix` | String | Request | Sets the suffix for `Request` classes. |
| `responseSuffix` | String | Response | Sets the suffix for `Response` classes. |
| `responseProjectionSuffix` | String | ResponseProjection | Sets the suffix for `ResponseProjection` classes. |
| `parametrizedInputSuffix` | String | ParametrizedInput | Sets the suffix for `ParametrizedInput` classes. |
| `parentInterfaces` | *See [parentInterfaces](#option-parentinterfaces)* | Empty | Block to define parent interfaces for generated interfaces (query / mutation / subscription / type resolver) |
| Option | Data Type | Default value | Description |
| :---------------------------------------------: | :----------------------------------------------------------------: | :-------------------------------------------: | ----------- |
| `graphqlSchemaPaths` | List(String) | (falls back to `graphqlSchemas`) | GraphQL schema locations. You can supply multiple paths to GraphQL schemas. To include many schemas from a folder hierarchy, use the `graphqlSchemas` block instead. |
| `graphqlSchemas` | *See [graphqlSchemas](#option-graphqlschemas)* | All `.graphqls`/`.graphql` files in resources | Block to define the input GraphQL schemas, when exact paths are too cumbersome. See table below for a list of options. |
| `outputDir` | String | None | The output target directory into which code will be generated. |
| `jsonConfigurationFile` | String | Empty | Path to an external mapping configuration. |
| `packageName` | String | Empty | Java package for generated classes. |
| `apiPackageName` | String | Empty | Java package for generated api classes (Query, Mutation, Subscription). |
| `modelPackageName` | String | Empty | Java package for generated model classes (type, input, interface, enum, union). |
| `generateBuilder` | Boolean | True | Specifies whether generated model classes should have builder. |
| `generateApis` | Boolean | True | Specifies whether api classes should be generated as well as model classes. |
| `generateAsyncApi` | Boolean | False | If true, then wrap type into `java.util.concurrent.CompletableFuture` or `subscriptionReturnType` |
| `generateDataFetchingEnvironmentArgumentInApis` | Boolean | False | If true, then `graphql.schema.DataFetchingEnvironment env` will be added as a last argument to all methods of root type resolvers and field resolvers. |
| `generateEqualsAndHashCode` | Boolean | False | Specifies whether generated model classes should have equals and hashCode methods defined. |
| `generateToString` | Boolean | False | Specifies whether generated model classes should have toString method defined. |
| `apiNamePrefix` | String | Empty | Sets the prefix for GraphQL api classes (query, mutation, subscription). |
| `apiNameSuffix` | String | Resolver | Sets the suffix for GraphQL api classes (query, mutation, subscription). |
| `apiRootInterfaceStrategy` | *See [ApiRootInterfaceStrategy](#option-apirootinterfacestrategy)* | `SINGLE_INTERFACE` | |
| `apiNamePrefixStrategy` | *See [ApiNamePrefixStrategy](#option-apinameprefixstrategy)* | `CONSTANT` | |
| `modelNamePrefix` | String | Empty | Sets the prefix for GraphQL model classes (type, input, interface, enum, union). |
| `modelNameSuffix` | String | Empty | Sets the suffix for GraphQL model classes (type, input, interface, enum, union). |
| `modelValidationAnnotation` | String | @javax.validation.<br>constraints.NotNull | Annotation for mandatory (NonNull) fields. Can be null/empty. |
| `customTypesMapping` | Map(String,String) | Empty | Can be used to supply custom mappings for scalars. <br/> Supports:<br/> * Map of (GraphqlObjectName.fieldName) to (JavaType) <br/> * Map of (GraphqlType) to (JavaType) |
| `customAnnotationsMapping` | Map(String,String) | Empty | Can be used to supply custom annotations (serializers) for scalars. <br/> Supports:<br/> * Map of (GraphqlObjectName.fieldName) to (JavaAnnotation) <br/> * Map of (GraphqlType) to (JavaAnnotation) |
| `fieldsWithResolvers` | Set(String) | Empty | Fields that require Resolvers should be defined here in format: `TypeName.fieldName` or `TypeName`. |
| `fieldsWithoutResolvers` | Set(String) | Empty | Fields that DO NOT require Resolvers should be defined here in format: `TypeName.fieldName` or `TypeName`. Can be used in conjunction with `generateExtensionFieldsResolvers` option. |
| `generateParameterizedFieldsResolvers` | Boolean | True | If true, then generate separate `Resolver` interface for parametrized fields. If false, then add field to the type definition and ignore field parameters. |
| `generateExtensionFieldsResolvers` | Boolean | False | Specifies whether all fields in extensions (`extend type` and `extend interface`) should be present in Resolver interface instead of the type class itself. |
| `subscriptionReturnType` | String | Empty | Return type for subscription methods. For example: `org.reactivestreams.Publisher`, `io.reactivex.Observable`, etc. |
| `generateClient` | Boolean | False | Specifies whether client-side classes should be generated for each query, mutation and subscription. This includes: `Request` classes (contain input data), `ResponseProjection` classes for each type (contain response fields) and `Response` classes (contain response data). |
| `requestSuffix` | String | Request | Sets the suffix for `Request` classes. |
| `responseSuffix` | String | Response | Sets the suffix for `Response` classes. |
| `responseProjectionSuffix` | String | ResponseProjection | Sets the suffix for `ResponseProjection` classes. |
| `parametrizedInputSuffix` | String | ParametrizedInput | Sets the suffix for `ParametrizedInput` classes. |
| `parentInterfaces` | *See [parentInterfaces](#option-parentinterfaces)* | Empty | Block to define parent interfaces for generated interfaces (query / mutation / subscription / type resolver) |
### Option `graphqlSchemas`
......@@ -48,6 +50,27 @@ The parameters inside that block are the following:
| `excludedFiles` | Set<String> | (empty set) | A set of files to exclude, even if they match the include pattern. These paths should be either absolute or relative to the provided `rootDir`. |
### Option `ApiRootInterfaceStrategy`
Defines how root interface (`QueryResolver` / `MutationResolver` / `SubscriptionResolver` will be generated (in addition to separate interfaces for each query/mutation/subscription)
| Value | Description |
| ---------------------- | ----------- |
| `INTERFACE_PER_SCHEMA` | Generate multiple super-interfaces for each graphql file. <br>Takes into account `apiNamePrefixStrategy`. <br>E.g.: `OrderServiceQueryResolver.java`, `ProductServiceQueryResolver.java`, etc. |
| `SINGLE_INTERFACE` | Generate a single `QueryResolver.java`, `MutationResolver.java`, `SubscriptionResolver.java` for all graphql schema files. |
### Option `ApiNamePrefixStrategy`
Defines which prefix to use for API interfaces.
| Value | Description |
| ----------------------- | ----------- |
| `FILE_NAME_AS_PREFIX` | Will take GraphQL file name as a prefix for all generated API interfaces + value of `apiNamePrefix` config option. <br>E.g.:<br> * following schemas: *resources/schemas/order-service.graphql*, *resources/schemas/product-service.graphql*<br> * will result in: `OrderServiceQueryResolver.java`, `ProductServiceQueryResolver.java`, etc |
| `FOLDER_NAME_AS_PREFIX` | Will take parent folder name as a prefix for all generated API interfaces + value of `apiNamePrefix` config option. E.g.:<br> * following schemas: *resources/order-service/schema1.graphql*, *resources/order-service/schema2.graphql*<br> * will result in: `OrderServiceQueryResolver.java`, `OrderServiceGetOrderByIdQueryResolver.java`, etc |
| `CONSTANT` | Will take only the value of `apiNamePrefix` config option. |
### Option `parentInterfaces`
Following options can be defined if you want generated resolvers to extend certain interfaces.
......
package io.github.kobylynskyi.graphql.codegen.gradle;
import com.kobylynskyi.graphql.codegen.GraphQLCodegen;
import com.kobylynskyi.graphql.codegen.model.ApiNamePrefixStrategy;
import com.kobylynskyi.graphql.codegen.model.ApiRootInterfaceStrategy;
import com.kobylynskyi.graphql.codegen.model.GraphQLCodegenConfiguration;
import com.kobylynskyi.graphql.codegen.model.MappingConfig;
import com.kobylynskyi.graphql.codegen.model.MappingConfigConstants;
......@@ -10,14 +12,25 @@ import com.kobylynskyi.graphql.codegen.supplier.SchemaFinder;
import org.gradle.api.Action;
import org.gradle.api.DefaultTask;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Nested;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.*;
import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskAction;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Gradle task for GraphQL code generation
......@@ -29,10 +42,13 @@ public class GraphQLCodegenGradleTask extends DefaultTask implements GraphQLCode
private List<String> graphqlSchemaPaths;
private final SchemaFinderConfig graphqlSchemas = new SchemaFinderConfig();
private File outputDir;
private Map<String, String> customTypesMapping = new HashMap<>();
private Map<String, String> customAnnotationsMapping = new HashMap<>();
private String packageName;
private String apiPackageName;
private ApiNamePrefixStrategy apiNamePrefixStrategy = MappingConfigConstants.DEFAULT_API_NAME_PREFIX_STRATEGY;
private ApiRootInterfaceStrategy apiRootInterfaceStrategy = MappingConfigConstants.DEFAULT_API_ROOT_INTERFACE_STRATEGY;
private String apiNamePrefix;
private String apiNameSuffix;
private String modelPackageName;
......@@ -72,6 +88,8 @@ public class GraphQLCodegenGradleTask extends DefaultTask implements GraphQLCode
mappingConfig.setCustomTypesMapping(customTypesMapping);
mappingConfig.setApiNameSuffix(apiNameSuffix);
mappingConfig.setApiNamePrefix(apiNamePrefix);
mappingConfig.setApiRootInterfaceStrategy(apiRootInterfaceStrategy);
mappingConfig.setApiNamePrefixStrategy(apiNamePrefixStrategy);
mappingConfig.setModelNamePrefix(modelNamePrefix);
mappingConfig.setModelNameSuffix(modelNameSuffix);
mappingConfig.setApiPackageName(apiPackageName);
......@@ -231,6 +249,28 @@ public class GraphQLCodegenGradleTask extends DefaultTask implements GraphQLCode
this.apiPackageName = apiPackageName;
}
@Input
@Optional
@Override
public ApiRootInterfaceStrategy getApiRootInterfaceStrategy() {
return apiRootInterfaceStrategy;
}
public void setApiRootInterfaceStrategy(ApiRootInterfaceStrategy apiRootInterfaceStrategy) {
this.apiRootInterfaceStrategy = apiRootInterfaceStrategy;
}
@Input
@Optional
@Override
public ApiNamePrefixStrategy getApiNamePrefixStrategy() {
return apiNamePrefixStrategy;
}
public void setApiNamePrefixStrategy(ApiNamePrefixStrategy apiNamePrefixStrategy) {
this.apiNamePrefixStrategy = apiNamePrefixStrategy;
}
@Input
@Optional
@Override
......
package io.github.kobylynskyi.graphql.codegen;
import com.kobylynskyi.graphql.codegen.GraphQLCodegen;
import com.kobylynskyi.graphql.codegen.model.ApiNamePrefixStrategy;
import com.kobylynskyi.graphql.codegen.model.ApiRootInterfaceStrategy;
import com.kobylynskyi.graphql.codegen.model.GraphQLCodegenConfiguration;
import com.kobylynskyi.graphql.codegen.model.MappingConfig;
import com.kobylynskyi.graphql.codegen.model.MappingConfigConstants;
......@@ -81,6 +83,12 @@ public class GraphQLCodegenMojo extends AbstractMojo implements GraphQLCodegenCo
@Parameter
private String subscriptionReturnType;
@Parameter(defaultValue = MappingConfigConstants.DEFAULT_API_ROOT_INTERFACE_STRATEGY_STRING)
private ApiRootInterfaceStrategy apiRootInterfaceStrategy;
@Parameter(defaultValue = MappingConfigConstants.DEFAULT_API_NAME_PREFIX_STRATEGY_STRING)
private ApiNamePrefixStrategy apiNamePrefixStrategy;
@Parameter(defaultValue = MappingConfigConstants.DEFAULT_GENERATE_ASYNC_APIS_STRING)
private Boolean generateAsyncApi;
......@@ -138,6 +146,8 @@ public class GraphQLCodegenMojo extends AbstractMojo implements GraphQLCodegenCo
mappingConfig.setCustomTypesMapping(customTypesMapping != null ? customTypesMapping : new HashMap<>());
mappingConfig.setApiNameSuffix(apiNameSuffix);
mappingConfig.setApiNamePrefix(apiNamePrefix);
mappingConfig.setApiRootInterfaceStrategy(apiRootInterfaceStrategy);
mappingConfig.setApiNamePrefixStrategy(apiNamePrefixStrategy);
mappingConfig.setModelNamePrefix(modelNamePrefix);
mappingConfig.setModelNameSuffix(modelNameSuffix);
mappingConfig.setApiPackageName(apiPackageName);
......@@ -408,6 +418,24 @@ public class GraphQLCodegenMojo extends AbstractMojo implements GraphQLCodegenCo
this.generateDataFetchingEnvironmentArgumentInApis = generateDataFetchingEnvironmentArgumentInApis;
}
@Override
public ApiRootInterfaceStrategy getApiRootInterfaceStrategy() {
return apiRootInterfaceStrategy;
}
public void setApiRootInterfaceStrategy(ApiRootInterfaceStrategy apiRootInterfaceStrategy) {
this.apiRootInterfaceStrategy = apiRootInterfaceStrategy;
}
@Override
public ApiNamePrefixStrategy getApiNamePrefixStrategy() {
return apiNamePrefixStrategy;
}
public void setApiNamePrefixStrategy(ApiNamePrefixStrategy apiNamePrefixStrategy) {
this.apiNamePrefixStrategy = apiNamePrefixStrategy;
}
@Override
public Set<String> getFieldsWithResolvers() {
return fieldsWithResolvers;
......
package com.kobylynskyi.graphql.codegen;
import com.kobylynskyi.graphql.codegen.model.UnableToLoadFreeMarkerTemplateException;
import com.kobylynskyi.graphql.codegen.model.exception.UnableToLoadFreeMarkerTemplateException;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
......
......@@ -8,6 +8,8 @@ import com.kobylynskyi.graphql.codegen.mapper.InterfaceDefinitionToDataModelMapp
import com.kobylynskyi.graphql.codegen.mapper.RequestResponseDefinitionToDataModelMapper;
import com.kobylynskyi.graphql.codegen.mapper.TypeDefinitionToDataModelMapper;
import com.kobylynskyi.graphql.codegen.mapper.UnionDefinitionToDataModelMapper;
import com.kobylynskyi.graphql.codegen.model.ApiNamePrefixStrategy;
import com.kobylynskyi.graphql.codegen.model.ApiRootInterfaceStrategy;
import com.kobylynskyi.graphql.codegen.model.MappingConfig;
import com.kobylynskyi.graphql.codegen.model.MappingConfigConstants;
import com.kobylynskyi.graphql.codegen.model.MappingContext;
......@@ -111,10 +113,21 @@ public class GraphQLCodegen {
if (mappingConfig.getGenerateDataFetchingEnvironmentArgumentInApis() == null) {
mappingConfig.setGenerateDataFetchingEnvironmentArgumentInApis(MappingConfigConstants.DEFAULT_GENERATE_DATA_FETCHING_ENV);
}
if (mappingConfig.getApiNamePrefixStrategy() == null) {
mappingConfig.setApiNamePrefixStrategy(MappingConfigConstants.DEFAULT_API_NAME_PREFIX_STRATEGY);
}
if (mappingConfig.getApiRootInterfaceStrategy() == null) {
mappingConfig.setApiRootInterfaceStrategy(MappingConfigConstants.DEFAULT_API_ROOT_INTERFACE_STRATEGY);
}
if (mappingConfig.getGenerateClient()) {
// required for request serialization
mappingConfig.setGenerateToString(true);
}
if (mappingConfig.getApiRootInterfaceStrategy() == ApiRootInterfaceStrategy.INTERFACE_PER_SCHEMA &&
mappingConfig.getApiNamePrefixStrategy() == ApiNamePrefixStrategy.CONSTANT) {
// that's because we will have a conflict in case there is "type Query" in multiple graphql schema files
throw new IllegalArgumentException("API prefix should not be CONSTANT for INTERFACE_PER_SCHEMA option");
}
}
public List<File> generate() throws Exception {
......@@ -144,7 +157,12 @@ public class GraphQLCodegen {
.ifPresent(generatedFiles::add);
}
for (ExtendedObjectTypeDefinition extendedObjectTypeDefinition : document.getOperationDefinitions()) {
generatedFiles.addAll(generateOperation(context, extendedObjectTypeDefinition));
if (mappingConfig.getGenerateApis()) {
generatedFiles.addAll(generateServerOperations(context, extendedObjectTypeDefinition));
}
if (mappingConfig.getGenerateClient()) {
generatedFiles.addAll(generateClient(context, extendedObjectTypeDefinition));
}
}
for (ExtendedInputObjectTypeDefinition extendedInputObjectTypeDefinition : document.getInputDefinitions()) {
generatedFiles.add(generateInput(context, extendedInputObjectTypeDefinition));
......@@ -177,32 +195,70 @@ public class GraphQLCodegen {
return GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.interfaceTemplate, dataModel, outputDir);
}
private List<File> generateOperation(MappingContext mappingContext, ExtendedObjectTypeDefinition definition) {
private List<File> generateServerOperations(MappingContext mappingContext, ExtendedObjectTypeDefinition definition) {
List<File> generatedFiles = new ArrayList<>();
// Generate a root interface with all operations inside
// Relates to https://github.com/facebook/relay/issues/112
switch (mappingContext.getApiRootInterfaceStrategy()) {
case INTERFACE_PER_SCHEMA:
for (ExtendedObjectTypeDefinition defInFile : definition.groupBySourceLocationFile().values()) {
generatedFiles.add(generateRootApi(mappingContext, defInFile));
}
break;
case SINGLE_INTERFACE:
default:
generatedFiles.add(generateRootApi(mappingContext, definition));
break;
}
// Generate separate interfaces for all queries, mutations and subscriptions
List<String> fieldNames = definition.getFieldDefinitions().stream().map(FieldDefinition::getName).collect(toList());
if (mappingConfig.getGenerateApis()) {
for (ExtendedFieldDefinition operationDef : definition.getFieldDefinitions()) {
Map<String, Object> dataModel = FieldDefinitionsToResolverDataModelMapper.mapRootTypeField(mappingContext, operationDef, definition.getName(), fieldNames);
generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.operationsTemplate, dataModel, outputDir));
}
// We need to generate a root object to workaround https://github.com/facebook/relay/issues/112
Map<String, Object> dataModel = FieldDefinitionsToResolverDataModelMapper.mapRootTypeFields(mappingContext, definition);
generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.operationsTemplate, dataModel, outputDir));
switch (mappingContext.getApiNamePrefixStrategy()) {
case FOLDER_NAME_AS_PREFIX:
for (ExtendedObjectTypeDefinition fileDef : definition.groupBySourceLocationFolder().values()) {
generatedFiles.addAll(generateApis(mappingContext, fileDef, fieldNames));
}
break;
case FILE_NAME_AS_PREFIX:
for (ExtendedObjectTypeDefinition fileDef : definition.groupBySourceLocationFile().values()) {
generatedFiles.addAll(generateApis(mappingContext, fileDef, fieldNames));
}
break;
case CONSTANT:
default:
generatedFiles.addAll(generateApis(mappingContext, definition, fieldNames));
break;
}
return generatedFiles;
}
if (mappingConfig.getGenerateClient()) {
// generate request objects for graphql operations
for (ExtendedFieldDefinition operationDef : definition.getFieldDefinitions()) {
Map<String, Object> requestDataModel = RequestResponseDefinitionToDataModelMapper.mapRequest(mappingContext, operationDef, definition.getName(), fieldNames);
generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.requestTemplate, requestDataModel, outputDir));
private List<File> generateClient(MappingContext mappingContext, ExtendedObjectTypeDefinition definition) {
List<File> generatedFiles = new ArrayList<>();
List<String> fieldNames = definition.getFieldDefinitions().stream().map(FieldDefinition::getName).collect(toList());
for (ExtendedFieldDefinition operationDef : definition.getFieldDefinitions()) {
Map<String, Object> requestDataModel = RequestResponseDefinitionToDataModelMapper.mapRequest(mappingContext, operationDef, definition.getName(), fieldNames);
generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.requestTemplate, requestDataModel, outputDir));
Map<String, Object> responseDataModel = RequestResponseDefinitionToDataModelMapper.mapResponse(mappingContext, operationDef, definition.getName(), fieldNames);
generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.responseTemplate, responseDataModel, outputDir));
}
Map<String, Object> responseDataModel = RequestResponseDefinitionToDataModelMapper.mapResponse(mappingContext, operationDef, definition.getName(), fieldNames);
generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.responseTemplate, responseDataModel, outputDir));
}
return generatedFiles;
}
private List<File> generateApis(MappingContext mappingContext, ExtendedObjectTypeDefinition definition, List<String> fieldNames) {
List<File> generatedFiles = new ArrayList<>();
for (ExtendedFieldDefinition operationDef : definition.getFieldDefinitions()) {
Map<String, Object> dataModel = FieldDefinitionsToResolverDataModelMapper.mapRootTypeField(mappingContext, operationDef, definition.getName(), fieldNames);
generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.operationsTemplate, dataModel, outputDir));
}
return generatedFiles;
}
private File generateRootApi(MappingContext mappingContext, ExtendedObjectTypeDefinition definition) {
Map<String, Object> dataModel = FieldDefinitionsToResolverDataModelMapper.mapRootTypeFields(mappingContext, definition);
return GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.operationsTemplate, dataModel, outputDir);
}
private List<File> generateType(MappingContext mappingContext, ExtendedObjectTypeDefinition definition) {
List<File> generatedFiles = new ArrayList<>();
Map<String, Object> dataModel = TypeDefinitionToDataModelMapper.map(mappingContext, definition);
......
package com.kobylynskyi.graphql.codegen;
import com.kobylynskyi.graphql.codegen.model.DataModelFields;
import com.kobylynskyi.graphql.codegen.model.UnableToCreateFileException;
import com.kobylynskyi.graphql.codegen.model.exception.UnableToCreateFileException;
import com.kobylynskyi.graphql.codegen.utils.Utils;
import freemarker.template.Template;
......
package com.kobylynskyi.graphql.codegen;
import com.kobylynskyi.graphql.codegen.model.SchemaValidationException;
import com.kobylynskyi.graphql.codegen.model.exception.SchemaValidationException;
import graphql.GraphQLException;
import lombok.AllArgsConstructor;
......
package com.kobylynskyi.graphql.codegen.mapper;
import com.kobylynskyi.graphql.codegen.model.ApiRootInterfaceStrategy;
import com.kobylynskyi.graphql.codegen.model.MappingContext;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedDefinition;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedDocument;
......@@ -9,14 +10,17 @@ import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedObjectTypeDefin
import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLOperation;
import com.kobylynskyi.graphql.codegen.utils.Utils;
import graphql.language.InputValueDefinition;
import graphql.language.SourceLocation;
import graphql.language.TypeName;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringJoiner;
import java.util.function.Function;
import java.util.stream.Collectors;
class MapperUtils {
......@@ -90,9 +94,7 @@ class MapperUtils {
String rootTypeName,
List<String> fieldNames) {
StringBuilder classNameBuilder = new StringBuilder();
if (Utils.isNotBlank(mappingContext.getApiNamePrefix())) {
classNameBuilder.append(mappingContext.getApiNamePrefix());
}
classNameBuilder.append(getApiPrefix(mappingContext, fieldDefinition.getSourceLocation()));
classNameBuilder.append(Utils.capitalize(fieldDefinition.getName()));
if (Collections.frequency(fieldNames, fieldDefinition.getName()) > 1) {
// Examples: EventsByIdsQuery, EventsByCategoryAndStatusQuery
......@@ -118,8 +120,13 @@ class MapperUtils {
static String getApiClassNameWithPrefixAndSuffix(MappingContext mappingContext,
ExtendedObjectTypeDefinition definition) {
StringBuilder classNameBuilder = new StringBuilder();
if (Utils.isNotBlank(mappingContext.getApiNamePrefix())) {
classNameBuilder.append(mappingContext.getApiNamePrefix());
if (mappingContext.getApiRootInterfaceStrategy() == ApiRootInterfaceStrategy.SINGLE_INTERFACE) {
// we don't need to consider apiNamePrefixStrategy in case we are generating a single root interface
if (Utils.isNotBlank(mappingContext.getApiNamePrefix())) {
classNameBuilder.append(mappingContext.getApiNamePrefix());
}
} else {
classNameBuilder.append(getApiPrefix(mappingContext, definition.getSourceLocation()));
}
classNameBuilder.append(Utils.capitalize(definition.getName()));
if (Utils.isNotBlank(mappingContext.getApiNameSuffix())) {
......@@ -128,6 +135,55 @@ class MapperUtils {
return classNameBuilder.toString();
}
/**
* Get the prefix for api class name based on the defined strategy and GraphQL node source location.
*
* @param mappingContext Global mapping context
* @param sourceLocation GraphQL node SourceLocation
* @return prefix for the api class
*/
private static String getApiPrefix(MappingContext mappingContext,
SourceLocation sourceLocation) {
switch (mappingContext.getApiNamePrefixStrategy()) {
case FILE_NAME_AS_PREFIX:
return getPrefixFromSourceLocation(sourceLocation, File::getName);
case FOLDER_NAME_AS_PREFIX:
return getPrefixFromSourceLocation(sourceLocation, getParentFileNameFunction());
case CONSTANT:
default:
if (Utils.isNotBlank(mappingContext.getApiNamePrefix())) {
return mappingContext.getApiNamePrefix();
}
}
return "";
}
/**
* Get the prefix (used as a prefix for class names) from GraphQL source location (file) using the supplied
* function {@param fileStringFunction}.
* Examples:
* ("src/test/resources/order-service/schema.graphql", File::getParent) => "OrderService"
* ("src/test/resources/product-service/ProductApis.graphql", File::getName) => "ProductApis"
*
* @param sourceLocation source location of GraphQL definition
* @param fileStringFunction function to fetch File's attribute (name, parent, etc)
* @return prefix of the file.
*/
private static String getPrefixFromSourceLocation(SourceLocation sourceLocation,
Function<File, String> fileStringFunction) {
if (sourceLocation == null || sourceLocation.getSourceName() == null) {
return "";
}
String fileName = fileStringFunction.apply(new File(sourceLocation.getSourceName()));
// remove prefix
fileName = fileName.replaceFirst("[.][^.]+$", "");
// capitalize
fileName = Utils.capitalizeString(fileName);
// leave only alphanumeric
fileName = fileName.replaceAll("[^A-Za-z0-9]", "");
return fileName;
}
/**
* Generates a class name for ParametrizedInput
*
......@@ -252,4 +308,8 @@ class MapperUtils {
.collect(Collectors.toList());
}
private static Function<File, String> getParentFileNameFunction() {
return file -> file != null && file.getParentFile() != null ? file.getParentFile().getName() : null;
}
}
package com.kobylynskyi.graphql.codegen.model;
public enum ApiNamePrefixStrategy {
/**
* Will take GraphQL file name as a prefix for all generated API interfaces + value of apiNamePrefix config option.
* e.g.:
* following schemas: resources/schemas/order-service.graphql, resources/schemas/product-service.graphql
* will result in: OrderServiceQueryResolver.java, ProductServiceQueryResolver.java, etc
*/
FILE_NAME_AS_PREFIX,
/**
* Will take parent folder name as a prefix for all generated API interfaces + value of apiNamePrefix config option.
* e.g.:
* following schemas: resources/order-service/schema1.graphql, resources/order-service/schema2.graphql
* will result in: OrderServiceQueryResolver.java, OrderServiceGetOrderByIdQueryResolver.java, etc
*/
FOLDER_NAME_AS_PREFIX,
/**
* Will take only the value of apiNamePrefix config option.
*/
CONSTANT;
}
package com.kobylynskyi.graphql.codegen.model;
public enum ApiRootInterfaceStrategy {
/**
* Generate multiple super-interfaces for each graphql file.
* Takes into account apiNamePrefixStrategy.
* e.g.: OrderServiceQueryResolver.java, ProductServiceQueryResolver.java, etc.
* (in addition to separate interfaces for each query/mutation/subscription)
*/
INTERFACE_PER_SCHEMA,
/**
* Generate a single QueryResolver.java, MutationResolver.java, SubscriptionResolver.java for all graphqls
* (in addition to separate interfaces for each query/mutation/subscription)
*/
SINGLE_INTERFACE;
}
......@@ -42,6 +42,13 @@ public interface GraphQLCodegenConfiguration {
*/
Boolean getGenerateApis();
/**
* Specifies the strategy of generating root api interface.
*
* @return strategy of generating root api interface.
*/
ApiRootInterfaceStrategy getApiRootInterfaceStrategy();
/**
* Java package for generated classes.
*
......@@ -77,6 +84,13 @@ public interface GraphQLCodegenConfiguration {
*/
String getModelNameSuffix();
/**
* Sets prefix strategy for GraphQL api classes (query, mutation, subscription).
*
* @return Prefix strategy for GraphQL api classes.
*/
ApiNamePrefixStrategy getApiNamePrefixStrategy();
/**
* Sets the prefix for GraphQL api classes (query, mutation, subscription).
*
......
......@@ -18,16 +18,24 @@ public class MappingConfig implements GraphQLCodegenConfiguration, Combinable<Ma
private Map<String, String> customTypesMapping = new HashMap<>();
private Map<String, String> customAnnotationsMapping = new HashMap<>();
private Boolean generateApis;
// package name configs:
private String packageName;
private String apiPackageName;
private String modelPackageName;
// suffix/prefix/strategies
private String modelNamePrefix;
private String modelNameSuffix;
private String apiNamePrefix;
private String apiNameSuffix;
private ApiRootInterfaceStrategy apiRootInterfaceStrategy;
private ApiNamePrefixStrategy apiNamePrefixStrategy;
private String modelValidationAnnotation;
private String subscriptionReturnType;
// various toggles
private Boolean generateApis;
private Boolean generateBuilder;
private Boolean generateEqualsAndHashCode;
private Boolean generateToString;
......@@ -35,8 +43,12 @@ public class MappingConfig implements GraphQLCodegenConfiguration, Combinable<Ma
private Boolean generateParameterizedFieldsResolvers;
private Boolean generateExtensionFieldsResolvers;
private Boolean generateDataFetchingEnvironmentArgumentInApis;
// field resolvers configs:
private Set<String> fieldsWithResolvers = new HashSet<>();
private Set<String> fieldsWithoutResolvers = new HashSet<>();
// parent interfaces configs:
private String queryResolverParentInterface;
private String mutationResolverParentInterface;
private String subscriptionResolverParentInterface;
......@@ -72,6 +84,8 @@ public class MappingConfig implements GraphQLCodegenConfiguration, Combinable<Ma
this.modelNameSuffix = source.modelNameSuffix != null ? source.modelNameSuffix : this.modelNameSuffix;
this.apiNamePrefix = source.apiNamePrefix != null ? source.apiNamePrefix : this.apiNamePrefix;
this.apiNameSuffix = source.apiNameSuffix != null ? source.apiNameSuffix : this.apiNameSuffix;
this.apiRootInterfaceStrategy = source.apiRootInterfaceStrategy != null ? source.apiRootInterfaceStrategy : this.apiRootInterfaceStrategy;
this.apiNamePrefixStrategy = source.apiNamePrefixStrategy != null ? source.apiNamePrefixStrategy : this.apiNamePrefixStrategy;
this.modelValidationAnnotation = source.modelValidationAnnotation != null ? source.modelValidationAnnotation : this.modelValidationAnnotation;
this.subscriptionReturnType = source.subscriptionReturnType != null ? source.subscriptionReturnType : this.subscriptionReturnType;
this.generateBuilder = source.generateBuilder != null ? source.generateBuilder : this.generateBuilder;
......
......@@ -21,6 +21,10 @@ public class MappingConfigConstants {
public static final String DEFAULT_GENERATE_EXTENSION_FIELDS_RESOLVERS_STRING = "false";
public static final boolean DEFAULT_GENERATE_DATA_FETCHING_ENV = false;
public static final String DEFAULT_GENERATE_DATA_FETCHING_ENV_STRING = "false";
public static final ApiNamePrefixStrategy DEFAULT_API_NAME_PREFIX_STRATEGY = ApiNamePrefixStrategy.CONSTANT;
public static final String DEFAULT_API_NAME_PREFIX_STRATEGY_STRING = DEFAULT_API_NAME_PREFIX_STRATEGY.name();
public static final ApiRootInterfaceStrategy DEFAULT_API_ROOT_INTERFACE_STRATEGY = ApiRootInterfaceStrategy.SINGLE_INTERFACE;
public static final String DEFAULT_API_ROOT_INTERFACE_STRATEGY_STRING = DEFAULT_API_ROOT_INTERFACE_STRATEGY.name();
public static final boolean DEFAULT_GENERATE_CLIENT = false;
public static final String DEFAULT_GENERATE_CLIENT_STRING = "false";
......
......@@ -29,6 +29,11 @@ public class MappingContext implements GraphQLCodegenConfiguration {
return config.getGenerateApis();
}
@Override
public ApiRootInterfaceStrategy getApiRootInterfaceStrategy() {
return config.getApiRootInterfaceStrategy();
}
@Override
public String getPackageName() {
return config.getPackageName();
......@@ -54,6 +59,11 @@ public class MappingContext implements GraphQLCodegenConfiguration {
return config.getModelNameSuffix();
}
@Override
public ApiNamePrefixStrategy getApiNamePrefixStrategy() {
return config.getApiNamePrefixStrategy();
}
@Override
public String getApiNamePrefix() {
return config.getApiNamePrefix();
......
......@@ -3,6 +3,7 @@ package com.kobylynskyi.graphql.codegen.model.definitions;
import graphql.language.Comment;
import graphql.language.NamedNode;
import graphql.language.Node;
import graphql.language.SourceLocation;
import lombok.Getter;
import lombok.Setter;
......@@ -35,6 +36,14 @@ public abstract class ExtendedDefinition<T extends NamedNode<T>, E extends T> {
}
}
public SourceLocation getSourceLocation() {
if (definition != null) {
return definition.getSourceLocation();
} else {
return extensions.stream().map(Node::getSourceLocation).findFirst().orElse(null);
}
}
public List<String> getJavaDoc() {
List<String> comments = new ArrayList<>();
if (definition != null && definition.getComments() != null) {
......
......@@ -4,9 +4,13 @@ import graphql.language.ObjectTypeDefinition;
import graphql.language.ObjectTypeExtensionDefinition;
import graphql.language.Type;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public class ExtendedObjectTypeDefinition extends ExtendedDefinition<ObjectTypeDefinition, ObjectTypeExtensionDefinition> {
......@@ -25,6 +29,35 @@ public class ExtendedObjectTypeDefinition extends ExtendedDefinition<ObjectTypeD
return definitions;
}
/**
* Get definition and its extensions grouped by source location.
*
* @return {@link HashMap} where the key is definition SourceLocation
* and value is a list of {@link ExtendedObjectTypeDefinition}s
*/
public Map<String, ExtendedObjectTypeDefinition> groupBySourceLocationFile() {
return groupBySourceLocation(File::getName);
}
public Map<String, ExtendedObjectTypeDefinition> groupBySourceLocationFolder() {
return groupBySourceLocation(File::getParent);
}
private Map<String, ExtendedObjectTypeDefinition> groupBySourceLocation(Function<File, String> fileStringFunction) {
Map<String, ExtendedObjectTypeDefinition> definitionMap = new HashMap<>();
if (definition != null) {
File file = new File(definition.getSourceLocation().getSourceName());
definitionMap.computeIfAbsent(fileStringFunction.apply(file), d -> new ExtendedObjectTypeDefinition())
.setDefinition(definition);
}
for (ObjectTypeExtensionDefinition extension : extensions) {
File file = new File(extension.getSourceLocation().getSourceName());
definitionMap.computeIfAbsent(fileStringFunction.apply(file), d -> new ExtendedObjectTypeDefinition())
.getExtensions().add(extension);
}
return definitionMap;
}
@SuppressWarnings("rawtypes")
public List<Type> getImplements() {
List<Type> definitionImplements = new ArrayList<>();
......
package com.kobylynskyi.graphql.codegen.model;
package com.kobylynskyi.graphql.codegen.model.exception;
/**
* Exception that indicates invalid GraphQL schema
......
package com.kobylynskyi.graphql.codegen.model;
package com.kobylynskyi.graphql.codegen.model.exception;
/**
* Exception that indicates error while creating directory
......
package com.kobylynskyi.graphql.codegen.model;
package com.kobylynskyi.graphql.codegen.model.exception;
/**
* Exception that indicates error while creating a file
......
package com.kobylynskyi.graphql.codegen.model;
package com.kobylynskyi.graphql.codegen.model.exception;
/**
* Exception that indicates error while deleting directory
......
package com.kobylynskyi.graphql.codegen.model;
package com.kobylynskyi.graphql.codegen.model.exception;
/**
* Exception that indicates error while loading Apache FreeMarker template
......
package com.kobylynskyi.graphql.codegen.utils;
import com.kobylynskyi.graphql.codegen.model.UnableToCreateDirectoryException;
import com.kobylynskyi.graphql.codegen.model.UnableToDeleteDirectoryException;
import com.kobylynskyi.graphql.codegen.model.exception.UnableToCreateDirectoryException;
import com.kobylynskyi.graphql.codegen.model.exception.UnableToDeleteDirectoryException;
import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLOperation;
import java.io.File;
......@@ -45,6 +45,20 @@ public final class Utils {
return new String(chars);
}
public static String capitalizeString(String aString) {
char[] chars = aString.toLowerCase().toCharArray();
boolean found = false;
for (int i = 0; i < chars.length; i++) {
if (!found && Character.isLetter(chars[i])) {
chars[i] = Character.toUpperCase(chars[i]);
found = true;
} else if (Character.isWhitespace(chars[i]) || chars[i] == '-' || chars[i] == '_' || chars[i] == '.') {
found = false;
}
}
return String.valueOf(chars);
}
/**
* Uncapitalize a string. Make first letter as lowercase
*
......
package com.kobylynskyi.graphql.codegen;
import com.kobylynskyi.graphql.codegen.model.ApiNamePrefixStrategy;
import com.kobylynskyi.graphql.codegen.model.ApiRootInterfaceStrategy;
import com.kobylynskyi.graphql.codegen.model.MappingConfig;
import com.kobylynskyi.graphql.codegen.supplier.SchemaFinder;
import com.kobylynskyi.graphql.codegen.utils.Utils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toSet;
import static org.junit.jupiter.api.Assertions.assertEquals;
class GraphQLCodegenApisTest {
private final File outputBuildDir = new File("build/generated");
private final File outputJavaClassesDir = new File("build/generated");
private final MappingConfig mappingConfig = new MappingConfig();
private final SchemaFinder schemaFinder = new SchemaFinder(Paths.get("src/test/resources"));
@BeforeEach
void init() {
schemaFinder.setIncludePattern("sub-schema.*\\.graphqls");
}
@AfterEach
void cleanup() {
Utils.deleteDir(new File("build/generated"));
}
@Test
void generate_FileNameAsPrefix() throws Exception {
mappingConfig.setApiNamePrefixStrategy(ApiNamePrefixStrategy.FILE_NAME_AS_PREFIX);
new GraphQLCodegen(schemaFinder.findSchemas(), outputBuildDir, mappingConfig).generate();
File[] files = Objects.requireNonNull(outputJavaClassesDir.listFiles());
Set<String> generatedFileNames = Arrays.stream(files).map(File::getName).collect(toSet());
assertEquals(new HashSet<>(asList("SubSchema1PingQueryResolver.java", "SubSchema2PongQueryResolver.java",
"QueryResolver.java")), generatedFileNames);
}
@Test
void generate_FolderNameAsPrefix() throws Exception {
mappingConfig.setApiNamePrefixStrategy(ApiNamePrefixStrategy.FOLDER_NAME_AS_PREFIX);
new GraphQLCodegen(schemaFinder.findSchemas(), outputBuildDir, mappingConfig).generate();
File[] files = Objects.requireNonNull(outputJavaClassesDir.listFiles());
Set<String> generatedFileNames = Arrays.stream(files).map(File::getName).collect(toSet());
assertEquals(new HashSet<>(asList("SubProj1PingQueryResolver.java", "SubProj2PongQueryResolver.java",
"QueryResolver.java")), generatedFileNames);
}
@Test
void generate_Constant() throws Exception {
mappingConfig.setApiNamePrefixStrategy(ApiNamePrefixStrategy.CONSTANT);
new GraphQLCodegen(schemaFinder.findSchemas(), outputBuildDir, mappingConfig).generate();
File[] files = Objects.requireNonNull(outputJavaClassesDir.listFiles());
Set<String> generatedFileNames = Arrays.stream(files).map(File::getName).collect(toSet());
assertEquals(new HashSet<>(asList("PingQueryResolver.java", "PongQueryResolver.java",
"QueryResolver.java")), generatedFileNames);
}
@Test
void generate_InterfacePerSchemaAndFolderNameAsPrefix() throws Exception {
mappingConfig.setApiNamePrefixStrategy(ApiNamePrefixStrategy.FOLDER_NAME_AS_PREFIX);
mappingConfig.setApiRootInterfaceStrategy(ApiRootInterfaceStrategy.INTERFACE_PER_SCHEMA);
new GraphQLCodegen(schemaFinder.findSchemas(), outputBuildDir, mappingConfig).generate();
File[] files = Objects.requireNonNull(outputJavaClassesDir.listFiles());
Set<String> generatedFileNames = Arrays.stream(files).map(File::getName).collect(toSet());
assertEquals(new HashSet<>(asList("SubProj1PingQueryResolver.java", "SubProj2PongQueryResolver.java",
"SubProj1QueryResolver.java", "SubProj2QueryResolver.java")), generatedFileNames);
}
@Test
void generate_InterfacePerSchemaAndFileNameAsPrefix() throws Exception {
mappingConfig.setApiNamePrefixStrategy(ApiNamePrefixStrategy.FILE_NAME_AS_PREFIX);
mappingConfig.setApiRootInterfaceStrategy(ApiRootInterfaceStrategy.INTERFACE_PER_SCHEMA);
new GraphQLCodegen(schemaFinder.findSchemas(), outputBuildDir, mappingConfig).generate();
File[] files = Objects.requireNonNull(outputJavaClassesDir.listFiles());
Set<String> generatedFileNames = Arrays.stream(files).map(File::getName).collect(toSet());
assertEquals(new HashSet<>(asList("SubSchema1PingQueryResolver.java", "SubSchema2PongQueryResolver.java",
"SubSchema1QueryResolver.java", "SubSchema2QueryResolver.java")), generatedFileNames);
}
@Test
void generate_InterfacePerSchemaAndConstantPrefix() {
mappingConfig.setApiNamePrefixStrategy(ApiNamePrefixStrategy.CONSTANT);
mappingConfig.setApiRootInterfaceStrategy(ApiRootInterfaceStrategy.INTERFACE_PER_SCHEMA);
Assertions.assertThrows(IllegalArgumentException.class, () -> {
new GraphQLCodegen(schemaFinder.findSchemas(), outputBuildDir, mappingConfig).generate();
});
}
}
\ No newline at end of file
package com.kobylynskyi.graphql.codegen;
import com.kobylynskyi.graphql.codegen.model.SchemaValidationException;
import com.kobylynskyi.graphql.codegen.model.exception.SchemaValidationException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
......
extend type Query {
ping: String!
}
\ No newline at end of file
extend type Query {
pong: String!
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册