Skip to content

Commit

Permalink
Fix codegen when multiple queries having same name #106
Browse files Browse the repository at this point in the history
  • Loading branch information
kobylynskyi committed Apr 25, 2020
1 parent 02a61d4 commit 436efe8
Show file tree
Hide file tree
Showing 28 changed files with 316 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,10 @@ private File generateInterface(ExtendedInterfaceTypeDefinition definition) {

private List<File> generateOperation(ExtendedObjectTypeDefinition definition) {
List<File> generatedFiles = new ArrayList<>();
List<String> fieldNames = definition.getFieldDefinitions().stream().map(FieldDefinition::getName).collect(toList());
if (Boolean.TRUE.equals(mappingConfig.getGenerateApis())) {
for (ExtendedFieldDefinition operationDef : definition.getFieldDefinitions()) {
Map<String, Object> dataModel = FieldDefinitionsToResolverDataModelMapper.mapRootTypeField(mappingConfig, operationDef, definition.getName());
Map<String, Object> dataModel = FieldDefinitionsToResolverDataModelMapper.mapRootTypeField(mappingConfig, 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
Expand All @@ -159,7 +160,7 @@ private List<File> generateOperation(ExtendedObjectTypeDefinition definition) {
if (Boolean.TRUE.equals(mappingConfig.getGenerateRequests())) {
// generate request objects for graphql operations
for (ExtendedFieldDefinition operationDef : definition.getFieldDefinitions()) {
Map<String, Object> requestDataModel = FieldDefinitionToRequestDataModelMapper.map(mappingConfig, operationDef, definition.getName());
Map<String, Object> requestDataModel = FieldDefinitionToRequestDataModelMapper.map(mappingConfig, operationDef, definition.getName(), fieldNames);
generatedFiles.add(GraphQLCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.requestTemplate, requestDataModel, outputDir));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ static void prepareOutputDir(File outputDir) {
private static File getFileTargetDirectory(Map<String, Object> dataModel, File outputDir) {
File targetDir;
Object packageName = dataModel.get(DataModelFields.PACKAGE);
if (packageName != null && !Utils.isBlank(packageName.toString())) {
if (packageName != null && Utils.isNotBlank(packageName.toString())) {
targetDir = new File(outputDir, packageName.toString().replace(".", File.separator));
} else {
targetDir = outputDir;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import com.kobylynskyi.graphql.codegen.model.MappingConfig;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedFieldDefinition;
import com.kobylynskyi.graphql.codegen.utils.Utils;
import graphql.language.FieldDefinition;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.kobylynskyi.graphql.codegen.model.DataModelFields.*;
Expand All @@ -23,15 +24,17 @@ public class FieldDefinitionToRequestDataModelMapper {
* @param mappingConfig Global mapping configuration
* @param operationDef GraphQL operation definition
* @param objectTypeName Object type (e.g.: "Query", "Mutation" or "Subscription")
* @param fieldNames Names of all fields inside the rootType. Used to detect duplicate
* @return Freemarker data model of the GraphQL request
*/
public static Map<String, Object> map(MappingConfig mappingConfig,
ExtendedFieldDefinition operationDef,
String objectTypeName) {
String objectTypeName,
List<String> fieldNames) {
Map<String, Object> dataModel = new HashMap<>();
// Request classes are sharing the package with the model classes, so no imports are needed
dataModel.put(PACKAGE, MapperUtils.getModelPackageName(mappingConfig));
dataModel.put(CLASS_NAME, getClassName(operationDef.getName(), objectTypeName, mappingConfig.getRequestSuffix()));
dataModel.put(CLASS_NAME, getClassName(operationDef, fieldNames, objectTypeName, mappingConfig.getRequestSuffix()));
dataModel.put(JAVA_DOC, operationDef.getJavaDoc());
dataModel.put(OPERATION_NAME, operationDef.getName());
dataModel.put(OPERATION_TYPE, objectTypeName.toUpperCase());
Expand All @@ -45,14 +48,23 @@ public static Map<String, Object> map(MappingConfig mappingConfig,

/**
* Examples:
* - EventsByIdsQueryRequest
* - EventsByCategoryQueryRequest
* - CreateEventMutationRequest
*/
private static String getClassName(String operationDefName, String objectType, String requestSuffix) {
if (Utils.isBlank(requestSuffix)) {
return Utils.capitalize(operationDefName) + objectType;
} else {
return Utils.capitalize(operationDefName) + objectType + requestSuffix;
private static String getClassName(ExtendedFieldDefinition operationDef,
List<String> fieldNames,
String objectType,
String requestSuffix) {
StringBuilder classNameBuilder = new StringBuilder();
classNameBuilder.append(Utils.capitalize(operationDef.getName()));
if (Collections.frequency(fieldNames, operationDef.getName()) > 1) {
classNameBuilder.append(MapperUtils.getClassNameSuffixWithInputValues(operationDef));
}
classNameBuilder.append(objectType);
if (Utils.isNotBlank(requestSuffix)) {
classNameBuilder.append(requestSuffix);
}
return classNameBuilder.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import com.kobylynskyi.graphql.codegen.model.MappingConfig;
import com.kobylynskyi.graphql.codegen.model.OperationDefinition;
import com.kobylynskyi.graphql.codegen.model.ParameterDefinition;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedObjectTypeDefinition;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedFieldDefinition;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedObjectTypeDefinition;
import com.kobylynskyi.graphql.codegen.utils.Utils;
import graphql.language.InputValueDefinition;
import graphql.language.TypeName;

import java.util.*;
Expand Down Expand Up @@ -43,13 +44,20 @@ public static Map<String, Object> mapToTypeResolver(MappingConfig mappingConfig,
* @param mappingConfig Global mapping configuration
* @param fieldDefinition GraphQL field definition
* @param rootTypeName Object type (e.g.: "Query", "Mutation" or "Subscription")
* @param fieldNames Names of all fields inside the rootType. Used to detect duplicate
* @return Freemarker data model of the GraphQL field
*/
public static Map<String, Object> mapRootTypeField(MappingConfig mappingConfig,
ExtendedFieldDefinition fieldDefinition,
String rootTypeName) {
// Examples: VersionQuery, CreateEventMutation (rootTypeName is "Query" or the likes)
String className = Utils.capitalize(fieldDefinition.getName()) + rootTypeName;
String rootTypeName,
List<String> fieldNames) {
String fieldDefinitionName = fieldDefinition.getName();
if (Collections.frequency(fieldNames, fieldDefinitionName) > 1) {
// Examples: EventsByIdsQuery, EventsByCategoryAndStatusQuery
fieldDefinitionName += MapperUtils.getClassNameSuffixWithInputValues(fieldDefinition);
}
// Examples: CreateEventMutation, EventsQuery, EventsByIdsQuery (rootTypeName is "Query" or the likes)
String className = Utils.capitalize(fieldDefinitionName) + rootTypeName;
List<ExtendedFieldDefinition> fieldDefs = Collections.singletonList(fieldDefinition);
return mapToResolverModel(mappingConfig, rootTypeName, className, fieldDefs, fieldDefinition.getJavaDoc());
}
Expand Down Expand Up @@ -83,6 +91,7 @@ private static Map<String, Object> mapToResolverModel(MappingConfig mappingConfi
dataModel.put(IMPORTS, imports);
dataModel.put(CLASS_NAME, className);
dataModel.put(OPERATIONS, operations);
dataModel.put(JAVA_DOC, javaDoc);
return dataModel;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ private static List<String> getAnnotations(MappingConfig mappingConfig, String g
List<String> annotations = new ArrayList<>();
if (mandatory) {
String modelValidationAnnotation = mappingConfig.getModelValidationAnnotation();
if (!Utils.isBlank(modelValidationAnnotation)) {
if (Utils.isNotBlank(modelValidationAnnotation)) {
annotations.add(modelValidationAnnotation);
}
}
Expand Down Expand Up @@ -185,7 +185,7 @@ static String wrapIntoAsyncIfRequired(MappingConfig mappingConfig, String javaTy
*/
private static String wrapIntoSubscriptionIfRequired(MappingConfig mappingConfig, String javaTypeName, String parentTypeName) {
if (parentTypeName.equalsIgnoreCase(GraphQLOperation.SUBSCRIPTION.name())
&& !Utils.isBlank(mappingConfig.getSubscriptionReturnType())) {
&& Utils.isNotBlank(mappingConfig.getSubscriptionReturnType())) {
return String.format("%s<%s>", mappingConfig.getSubscriptionReturnType(), javaTypeName);
}
return javaTypeName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

import com.kobylynskyi.graphql.codegen.model.MappingConfig;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedDefinition;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedFieldDefinition;
import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLOperation;
import com.kobylynskyi.graphql.codegen.utils.Utils;
import graphql.language.InputValueDefinition;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.StringJoiner;

class MapperUtils {

Expand Down Expand Up @@ -55,11 +58,11 @@ static String getClassNameWithPrefixAndSuffix(MappingConfig mappingConfig,
*/
static String getClassNameWithPrefixAndSuffix(MappingConfig mappingConfig, String definitionName) {
StringBuilder classNameBuilder = new StringBuilder();
if (!Utils.isBlank(mappingConfig.getModelNamePrefix())) {
if (Utils.isNotBlank(mappingConfig.getModelNamePrefix())) {
classNameBuilder.append(mappingConfig.getModelNamePrefix());
}
classNameBuilder.append(Utils.capitalize(definitionName));
if (!Utils.isBlank(mappingConfig.getModelNameSuffix())) {
if (Utils.isNotBlank(mappingConfig.getModelNameSuffix())) {
classNameBuilder.append(mappingConfig.getModelNameSuffix());
}
return classNameBuilder.toString();
Expand All @@ -72,7 +75,7 @@ static String getClassNameWithPrefixAndSuffix(MappingConfig mappingConfig, Strin
* @return api package name if present. Generic package name otherwise
*/
static String getApiPackageName(MappingConfig mappingConfig) {
if (!Utils.isBlank(mappingConfig.getApiPackageName())) {
if (Utils.isNotBlank(mappingConfig.getApiPackageName())) {
return mappingConfig.getApiPackageName();
} else {
return mappingConfig.getPackageName();
Expand All @@ -86,7 +89,7 @@ static String getApiPackageName(MappingConfig mappingConfig) {
* @return model package name if present. Generic package name otherwise
*/
static String getModelPackageName(MappingConfig mappingConfig) {
if (!Utils.isBlank(mappingConfig.getModelPackageName())) {
if (Utils.isNotBlank(mappingConfig.getModelPackageName())) {
return mappingConfig.getModelPackageName();
} else {
return mappingConfig.getPackageName();
Expand All @@ -105,11 +108,11 @@ static String getModelPackageName(MappingConfig mappingConfig) {
static Set<String> getImports(MappingConfig mappingConfig, String packageName) {
Set<String> imports = new HashSet<>();
String modelPackageName = mappingConfig.getModelPackageName();
if (!Utils.isBlank(modelPackageName) && !modelPackageName.equals(packageName)) {
if (Utils.isNotBlank(modelPackageName) && !modelPackageName.equals(packageName)) {
imports.add(modelPackageName);
}
String genericPackageName = mappingConfig.getPackageName();
if (!Utils.isBlank(genericPackageName) && !genericPackageName.equals(packageName)) {
if (Utils.isNotBlank(genericPackageName) && !genericPackageName.equals(packageName)) {
imports.add(genericPackageName);
}
// not adding apiPackageName because it should not be imported in any other generated classes
Expand All @@ -128,4 +131,26 @@ static boolean shouldUseAsyncMethods(MappingConfig mappingConfig, String typeNam

return isAsyncApi && !GraphQLOperation.SUBSCRIPTION.name().equalsIgnoreCase(typeName);
}

/**
* Builds a className suffix based on the input values.
* Examples:
* 1. fieldDefinition has some input values:
* "ids" => "ByIds"
* "category", "status" => "ByCategoryAndStatus"
*
* @param fieldDefinition field definition that has some InputValueDefinitions
* @return className suffix based on the input values
*/
static String getClassNameSuffixWithInputValues(ExtendedFieldDefinition fieldDefinition) {
StringJoiner inputValueNamesJoiner = new StringJoiner("And");
fieldDefinition.getInputValueDefinitions().stream()
.map(InputValueDefinition::getName).map(Utils::capitalize)
.forEach(inputValueNamesJoiner::add);
String inputValueNames = inputValueNamesJoiner.toString();
if (inputValueNames.isEmpty()) {
return inputValueNames;
}
return "By" + inputValueNames;
}
}
12 changes: 6 additions & 6 deletions src/main/java/com/kobylynskyi/graphql/codegen/utils/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,22 @@ public static String uncapitalize(String aString) {
}

/**
* Basically copy of org.apache.commons.lang3.StringUtils.isBlank(CharSequence cs)
* Inverted copy of org.apache.commons.lang3.StringUtils.isBlank(CharSequence cs)
*
* @param cs the CharSequence to check, may be null
* @return {@code true} if the CharSequence is null, empty or whitespace only
* @return {@code false} if the CharSequence is null, empty or whitespace only
*/
public static boolean isBlank(CharSequence cs) {
public static boolean isNotBlank(CharSequence cs) {
int strLen;
if (cs == null || (strLen = cs.length()) == 0) {
return true;
return false;
}
for (int i = 0; i < strLen; i++) {
if (!Character.isWhitespace(cs.charAt(i))) {
return false;
return true;
}
}
return true;
return false;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,20 @@ void generate_apiImportForModelClassesIfResolversExtensions() throws Exception {
getGeneratedFile(files, "EventsByCategoryAndStatusQueryRequest.java"));
}

@Test
void generate_QueriesWithSameName() throws Exception {
new GraphQLCodegen(singletonList("src/test/resources/schemas/queries-same-name.graphqls"),
outputBuildDir, mappingConfig).generate();

File[] files = Objects.requireNonNull(outputJavaClassesDir.listFiles());

assertSameTrimmedContent(new File("src/test/resources/expected-classes/request/ProductsByCategoryIdAndStatusQueryRequest.java.txt"),
getGeneratedFile(files, "ProductsByCategoryIdAndStatusQueryRequest.java"));

assertSameTrimmedContent(new File("src/test/resources/expected-classes/request/ProductsByIdsQueryRequest.java.txt"),
getGeneratedFile(files, "ProductsByIdsQueryRequest.java"));
}

private static File getGeneratedFile(File[] files, String fileName) throws FileNotFoundException {
return Arrays.stream(files)
.filter(f -> f.getName().equalsIgnoreCase(fileName))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,23 @@ void generate_deprecated() throws Exception {
}
}

@Test
void generate_QueriesWithSameName() throws Exception {
new GraphQLCodegen(singletonList("src/test/resources/schemas/queries-same-name.graphqls"),
outputBuildDir, mappingConfig).generate();

File[] files = Objects.requireNonNull(outputJavaClassesDir.listFiles());

assertEquals(Utils.getFileContent("src/test/resources/expected-classes/ProductsByCategoryIdAndStatusQuery.java.txt"),
Utils.getFileContent(
Arrays.stream(files).filter(f -> f.getName().equals("ProductsByCategoryIdAndStatusQuery.java")).map(File::getPath)
.findFirst().orElseThrow(FileNotFoundException::new)));
assertEquals(Utils.getFileContent("src/test/resources/expected-classes/ProductsByIdsQuery.java.txt"),
Utils.getFileContent(
Arrays.stream(files).filter(f -> f.getName().equals("ProductsByIdsQuery.java")).map(File::getPath)
.findFirst().orElseThrow(FileNotFoundException::new)));
}

private void assertFileContainsElements(File[] files, String fileName, String... elements)
throws IOException {
File file = getFile(files, fileName);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.github.graphql;


/**
* Resolver for AcceptTopicSuggestionPayload
*/
public interface AcceptTopicSuggestionPayloadResolver {

GithubTopicTO topic(GithubAcceptTopicSuggestionPayloadTO githubAcceptTopicSuggestionPayloadTO, graphql.schema.DataFetchingEnvironment env) throws Exception;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.github.graphql;


/**
* Resolver for CommentDeletedEvent
*/
public interface CommentDeletedEventResolver {

Actor actor(CommentDeletedEvent commentDeletedEvent) throws Exception;
Expand Down
3 changes: 3 additions & 0 deletions src/test/resources/expected-classes/CommitResolver.java.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.github.graphql;


/**
* Resolver for Commit
*/
public interface CommitResolver {

PullRequestConnection associatedPullRequests(Commit commit, String after, String before, Integer first, Integer last, PullRequestOrder orderBy, graphql.schema.DataFetchingEnvironment env) throws Exception;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.kobylynskyi.graphql.test1;


/**
* Create a new event.
*/
public interface CreateEventMutation {

/**
Expand Down
3 changes: 3 additions & 0 deletions src/test/resources/expected-classes/EventByIdQuery.java.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.kobylynskyi.graphql.test1;


/**
* Single event by ID.
*/
public interface EventByIdQuery {

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.kobylynskyi.graphql.test1;


/**
* List of events of a specified category.
*/
public interface EventsByCategoryAndStatusQuery {

/**
Expand Down
3 changes: 3 additions & 0 deletions src/test/resources/expected-classes/EventsByIdsQuery.java.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.kobylynskyi.graphql.test1;


/**
* Events by IDs.
*/
public interface EventsByIdsQuery {

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.kobylynskyi.graphql.test1;


/**
* Subscribe to events
*/
public interface EventsCreatedSubscription {

/**
Expand Down
Loading

0 comments on commit 436efe8

Please sign in to comment.