From d8073a92a5a49df8f886188369ef6017218af28f Mon Sep 17 00:00:00 2001 From: Bogdan Kobylynskyi <92bogdan@gmail.com> Date: Mon, 6 Apr 2020 12:20:01 -0500 Subject: [PATCH] Introduce codegen for client code - Part 1. #37 (#53) * Introduce codegen for client code - Part 1. #37 * Remove redundant null-checks * Minor fixes in Request class generation + improve code coverage. #37 * Customizable suffix of ResponseProjection classes. #37 * Code coverage for MappingConfig #37 * Fix method names #37 #53 --- .../codegen/FreeMarkerTemplatesRegistry.java | 4 + .../graphql/codegen/GraphqlCodegen.java | 118 ++++++++++++------ .../EnumDefinitionToDataModelMapper.java | 3 - .../FieldDefinitionToParameterMapper.java | 79 ++++++++++-- ...eldDefinitionToRequestDataModelMapper.java | 54 ++++++++ .../mapper/GraphqlTypeToJavaTypeMapper.java | 81 +++++------- ...InputValueDefinitionToParameterMapper.java | 29 ++++- .../InterfaceDefinitionToDataModelMapper.java | 2 +- .../graphql/codegen/mapper/MapperUtils.java | 13 ++ .../TypeDefinitionToDataModelMapper.java | 79 ++++++++++-- .../codegen/model/DataModelFields.java | 2 + .../model/DefaultMappingConfigValues.java | 3 + .../graphql/codegen/model/MappingConfig.java | 8 ++ .../model/ProjectionParameterDefinition.java | 19 +++ .../request/GraphQLOperationRequest.java | 15 +++ .../codegen/model/request/GraphQLRequest.java | 29 +++++ .../request/GraphQLRequestSerializer.java | 60 +++++++++ .../request/GraphQLResponseProjection.java | 5 + .../templates/javaClassGraphqlEnum.ftl | 2 +- .../templates/javaClassGraphqlRequest.ftl | 66 ++++++++++ .../javaClassGraphqlResponseProjection.ftl | 56 +++++++++ .../templates/javaClassGraphqlType.ftl | 12 +- .../codegen/GraphqlCodegenRequestTest.java | 115 +++++++++++++++++ .../graphql/codegen/GraphqlCodegenTest.java | 34 ++--- .../codegen/model/MappingConfigTest.java | 15 +++ .../request/GraphQLRequestSerializerTest.java | 93 ++++++++++++++ .../data/EventPropertyResponseProjection.java | 59 +++++++++ .../request/data/EventResponseProjection.java | 67 ++++++++++ ...EventsByCategoryAndStatusQueryRequest.java | 39 ++++++ .../request/data/EventsByIdsQueryRequest.java | 39 ++++++ .../request/data/IssueResponseProjection.java | 35 ++++++ .../codegen/model/request/data/Status.java | 5 + .../model/request/data/UpdateIssueInput.java | 52 ++++++++ .../data/UpdateIssueMutationRequest.java | 37 ++++++ .../UpdateIssuePayloadResponseProjection.java | 37 ++++++ .../request/data/VersionQueryRequest.java | 31 +++++ .../expected-classes/EventStatus.java.txt | 4 +- .../EventsByIdsQuery.java.txt | 10 ++ .../expected-classes/MyEnum.java.txt | 4 +- .../resources/expected-classes/Query.java.txt | 3 + .../AcceptTopicSuggestionInput.java.txt | 57 +++++++++ .../CodeOfConductResponseProjection.java.txt | 57 +++++++++ .../EventPropertyResponseProjection.java.txt | 57 +++++++++ .../request/EventResponseProjection.java.txt | 67 ++++++++++ .../request/EventStatusTO.java.txt | 9 ++ ...tsByCategoryAndStatusQueryRequest.java.txt | 43 +++++++ ...tatusQueryRequest_withModelSuffix.java.txt | 43 +++++++ .../request/EventsByIdsQueryRequest.java.txt | 39 ++++++ .../UpdateRepositoryMutationRequest.java.txt | 39 ++++++ .../request/VersionQueryRequest.java.txt | 35 ++++++ .../eventsByCategoryAndStatusQuery.txt | 17 +++ .../graphql-query/eventsByIdsQuery.txt | 5 + .../graphql-query/updateIssueMutation.txt | 16 +++ .../request/graphql-query/versionQuery.txt | 1 + src/test/resources/schemas/test.graphqls | 3 + 55 files changed, 1763 insertions(+), 143 deletions(-) create mode 100644 src/main/java/com/kobylynskyi/graphql/codegen/mapper/FieldDefinitionToRequestDataModelMapper.java create mode 100644 src/main/java/com/kobylynskyi/graphql/codegen/model/ProjectionParameterDefinition.java create mode 100644 src/main/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLOperationRequest.java create mode 100644 src/main/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLRequest.java create mode 100644 src/main/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLRequestSerializer.java create mode 100644 src/main/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLResponseProjection.java create mode 100644 src/main/resources/templates/javaClassGraphqlRequest.ftl create mode 100644 src/main/resources/templates/javaClassGraphqlResponseProjection.ftl create mode 100644 src/test/java/com/kobylynskyi/graphql/codegen/GraphqlCodegenRequestTest.java create mode 100644 src/test/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLRequestSerializerTest.java create mode 100644 src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/EventPropertyResponseProjection.java create mode 100644 src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/EventResponseProjection.java create mode 100644 src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/EventsByCategoryAndStatusQueryRequest.java create mode 100644 src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/EventsByIdsQueryRequest.java create mode 100644 src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/IssueResponseProjection.java create mode 100644 src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/Status.java create mode 100644 src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/UpdateIssueInput.java create mode 100644 src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/UpdateIssueMutationRequest.java create mode 100644 src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/UpdateIssuePayloadResponseProjection.java create mode 100644 src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/VersionQueryRequest.java create mode 100644 src/test/resources/expected-classes/EventsByIdsQuery.java.txt create mode 100644 src/test/resources/expected-classes/request/AcceptTopicSuggestionInput.java.txt create mode 100644 src/test/resources/expected-classes/request/CodeOfConductResponseProjection.java.txt create mode 100644 src/test/resources/expected-classes/request/EventPropertyResponseProjection.java.txt create mode 100644 src/test/resources/expected-classes/request/EventResponseProjection.java.txt create mode 100644 src/test/resources/expected-classes/request/EventStatusTO.java.txt create mode 100644 src/test/resources/expected-classes/request/EventsByCategoryAndStatusQueryRequest.java.txt create mode 100644 src/test/resources/expected-classes/request/EventsByCategoryAndStatusQueryRequest_withModelSuffix.java.txt create mode 100644 src/test/resources/expected-classes/request/EventsByIdsQueryRequest.java.txt create mode 100644 src/test/resources/expected-classes/request/UpdateRepositoryMutationRequest.java.txt create mode 100644 src/test/resources/expected-classes/request/VersionQueryRequest.java.txt create mode 100644 src/test/resources/expected-classes/request/graphql-query/eventsByCategoryAndStatusQuery.txt create mode 100644 src/test/resources/expected-classes/request/graphql-query/eventsByIdsQuery.txt create mode 100644 src/test/resources/expected-classes/request/graphql-query/updateIssueMutation.txt create mode 100644 src/test/resources/expected-classes/request/graphql-query/versionQuery.txt diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/FreeMarkerTemplatesRegistry.java b/src/main/java/com/kobylynskyi/graphql/codegen/FreeMarkerTemplatesRegistry.java index 11a332ba..c8179d5d 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/FreeMarkerTemplatesRegistry.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/FreeMarkerTemplatesRegistry.java @@ -12,9 +12,11 @@ class FreeMarkerTemplatesRegistry { static Template typeTemplate; static Template enumTemplate; static Template unionTemplate; + static Template requestTemplate; static Template interfaceTemplate; static Template operationsTemplate; static Template fieldsResolverTemplate; + static Template responseProjectionTemplate; static { Configuration configuration = new Configuration(Configuration.VERSION_2_3_28); @@ -28,9 +30,11 @@ class FreeMarkerTemplatesRegistry { typeTemplate = configuration.getTemplate("templates/javaClassGraphqlType.ftl"); enumTemplate = configuration.getTemplate("templates/javaClassGraphqlEnum.ftl"); unionTemplate = configuration.getTemplate("templates/javaClassGraphqlUnion.ftl"); + requestTemplate = configuration.getTemplate("templates/javaClassGraphqlRequest.ftl"); interfaceTemplate = configuration.getTemplate("templates/javaClassGraphqlInterface.ftl"); operationsTemplate = configuration.getTemplate("templates/javaClassGraphqlOperations.ftl"); fieldsResolverTemplate = configuration.getTemplate("templates/javaClassGraphqlFieldsResolver.ftl"); + responseProjectionTemplate = configuration.getTemplate("templates/javaClassGraphqlResponseProjection.ftl"); } catch (IOException e) { throw new UnableToLoadFreeMarkerTemplateException(e); } diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/GraphqlCodegen.java b/src/main/java/com/kobylynskyi/graphql/codegen/GraphqlCodegen.java index 9f148dcd..45403599 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/GraphqlCodegen.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/GraphqlCodegen.java @@ -1,8 +1,12 @@ package com.kobylynskyi.graphql.codegen; import com.kobylynskyi.graphql.codegen.mapper.*; -import com.kobylynskyi.graphql.codegen.model.*; +import com.kobylynskyi.graphql.codegen.model.DefaultMappingConfigValues; +import com.kobylynskyi.graphql.codegen.model.DefinitionTypeDeterminer; +import com.kobylynskyi.graphql.codegen.model.MappingConfig; +import com.kobylynskyi.graphql.codegen.model.UnsupportedGraphqlDefinitionException; import com.kobylynskyi.graphql.codegen.supplier.MappingConfigSupplier; +import com.kobylynskyi.graphql.codegen.utils.Utils; import freemarker.template.TemplateException; import graphql.language.*; import lombok.Getter; @@ -12,17 +16,16 @@ import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import static java.util.stream.Collectors.toList; /** * Generator of: - * - Interface for each GraphQL query - * - Interface for each GraphQL mutation - * - Interface for each GraphQL subscription - * - Class for each GraphQL data type - * - Class for each GraphQL enum type - * - Class for each GraphQL scalar type + * - Interface for each GraphQL query, mutation, subscription, union and field resolvers + * - POJO Class for each GraphQL type and input + * - Enum Class for each GraphQL enum * * @author kobylynskyi * @author valinhadev @@ -55,6 +58,15 @@ public class GraphqlCodegen { if (mappingConfig.getGenerateEqualsAndHashCode() == null) { mappingConfig.setGenerateEqualsAndHashCode(DefaultMappingConfigValues.DEFAULT_EQUALS_AND_HASHCODE); } + if (mappingConfig.getGenerateRequests() == null) { + mappingConfig.setGenerateRequests(DefaultMappingConfigValues.DEFAULT_GENERATE_REQUESTS); + } + if (mappingConfig.getRequestSuffix() == null) { + mappingConfig.setRequestSuffix(DefaultMappingConfigValues.DEFAULT_REQUEST_SUFFIX); + } + if (mappingConfig.getResponseProjectionSuffix() == null) { + mappingConfig.setResponseProjectionSuffix(DefaultMappingConfigValues.DEFAULT_RESPONSE_PROJECTION_SUFFIX); + } if (mappingConfig.getGenerateToString() == null) { mappingConfig.setGenerateToString(DefaultMappingConfigValues.DEFAULT_TO_STRING); } @@ -64,6 +76,10 @@ public class GraphqlCodegen { if (mappingConfig.getGenerateParameterizedFieldsResolvers() == null) { mappingConfig.setGenerateParameterizedFieldsResolvers(DefaultMappingConfigValues.DEFAULT_GENERATE_PARAMETERIZED_FIELDS_RESOLVERS); } + if (mappingConfig.getGenerateRequests()) { + // required for request serialization + mappingConfig.setGenerateToString(true); + } } @@ -72,46 +88,48 @@ public class GraphqlCodegen { long startTime = System.currentTimeMillis(); if (!schemas.isEmpty()) { Document document = GraphqlDocumentParser.getDocument(schemas); - addScalarsToCustomMappingConfig(document); + initCustomTypeMappings(document); processDocument(document); } long elapsed = System.currentTimeMillis() - startTime; - System.out.println(String.format("Finished processing %d schemas in %d ms", schemas.size(), elapsed)); + System.out.println(String.format("Finished processing %d schema(s) in %d ms", schemas.size(), elapsed)); } private void processDocument(Document document) throws IOException, TemplateException { + Set typeNames = getAllTypeNames(document); for (Definition definition : document.getDefinitions()) { - GraphqlDefinitionType definitionType; try { - definitionType = DefinitionTypeDeterminer.determine(definition); - } catch (UnsupportedGraphqlDefinitionException ex) { - continue; - } - switch (definitionType) { - case OPERATION: - generateOperation((ObjectTypeDefinition) definition); - break; - case TYPE: - generateType((ObjectTypeDefinition) definition, document); - generateFieldResolvers((ObjectTypeDefinition) definition); - break; - case INTERFACE: - generateInterface((InterfaceTypeDefinition) definition); - break; - case ENUM: - generateEnum((EnumTypeDefinition) definition); - break; - case INPUT: - generateInput((InputObjectTypeDefinition) definition); - break; - case UNION: - generateUnion((UnionTypeDefinition) definition); + processDefinition(document, definition, typeNames); + } catch (UnsupportedGraphqlDefinitionException ignored) { } } System.out.println(String.format("Generated %d definitions in folder '%s'", document.getDefinitions().size(), outputDir.getAbsolutePath())); } + private void processDefinition(Document document, Definition definition, Set typeNames) throws IOException, TemplateException { + switch (DefinitionTypeDeterminer.determine(definition)) { + case OPERATION: + generateOperation((ObjectTypeDefinition) definition); + break; + case TYPE: + generateType((ObjectTypeDefinition) definition, document, typeNames); + generateFieldResolvers((ObjectTypeDefinition) definition); + break; + case INTERFACE: + generateInterface((InterfaceTypeDefinition) definition); + break; + case ENUM: + generateEnum((EnumTypeDefinition) definition); + break; + case INPUT: + generateInput((InputObjectTypeDefinition) definition); + break; + case UNION: + generateUnion((UnionTypeDefinition) definition); + } + } + private void generateUnion(UnionTypeDefinition definition) throws IOException, TemplateException { Map dataModel = UnionDefinitionToDataModelMapper.map(mappingConfig, definition); GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.unionTemplate, dataModel, outputDir); @@ -124,19 +142,32 @@ public class GraphqlCodegen { private void generateOperation(ObjectTypeDefinition definition) throws IOException, TemplateException { if (Boolean.TRUE.equals(mappingConfig.getGenerateApis())) { - for (FieldDefinition fieldDef : definition.getFieldDefinitions()) { - Map dataModel = FieldDefinitionToDataModelMapper.map(mappingConfig, fieldDef, definition.getName()); + for (FieldDefinition operationDef : definition.getFieldDefinitions()) { + Map dataModel = FieldDefinitionToDataModelMapper.map(mappingConfig, operationDef, definition.getName()); GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.operationsTemplate, dataModel, outputDir); } // We need to generate a root object to workaround https://github.com/facebook/relay/issues/112 Map dataModel = ObjectDefinitionToDataModelMapper.map(mappingConfig, definition); GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.operationsTemplate, dataModel, outputDir); } + + if (Boolean.TRUE.equals(mappingConfig.getGenerateRequests())) { + // generate request objects for graphql operations + for (FieldDefinition operationDef : definition.getFieldDefinitions()) { + Map requestDataModel = FieldDefinitionToRequestDataModelMapper.map(mappingConfig, operationDef, definition.getName()); + GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.requestTemplate, requestDataModel, outputDir); + } + } } - private void generateType(ObjectTypeDefinition definition, Document document) throws IOException, TemplateException { + private void generateType(ObjectTypeDefinition definition, Document document, Set typeNames) throws IOException, TemplateException { Map dataModel = TypeDefinitionToDataModelMapper.map(mappingConfig, definition, document); GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.typeTemplate, dataModel, outputDir); + + if (Boolean.TRUE.equals(mappingConfig.getGenerateRequests())) { + Map responseProjDataModel = TypeDefinitionToDataModelMapper.mapResponseProjection(mappingConfig, definition, document, typeNames); + GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.responseProjectionTemplate, responseProjDataModel, outputDir); + } } private void generateFieldResolvers(ObjectTypeDefinition definition) throws IOException, TemplateException { @@ -154,17 +185,30 @@ public class GraphqlCodegen { GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.typeTemplate, dataModel, outputDir); } + private static Set getAllTypeNames(Document document) { + return document.getDefinitionsOfType(ObjectTypeDefinition.class) + .stream() + .filter(typeDef -> !Utils.isGraphqlOperation(typeDef.getName())) + .map(ObjectTypeDefinition::getName) + .collect(Collectors.toSet()); + } + private void generateEnum(EnumTypeDefinition definition) throws IOException, TemplateException { Map dataModel = EnumDefinitionToDataModelMapper.map(mappingConfig, definition); GraphqlCodegenFileCreator.generateFile(FreeMarkerTemplatesRegistry.enumTemplate, dataModel, outputDir); } - private void addScalarsToCustomMappingConfig(Document document) { + private void initCustomTypeMappings(Document document) { for (Definition definition : document.getDefinitions()) { if (definition instanceof ScalarTypeDefinition) { String scalarName = ((ScalarTypeDefinition) definition).getName(); mappingConfig.putCustomTypeMappingIfAbsent(scalarName, "String"); } } + mappingConfig.putCustomTypeMappingIfAbsent("ID", "String"); + mappingConfig.putCustomTypeMappingIfAbsent("String", "String"); + mappingConfig.putCustomTypeMappingIfAbsent("Int", "Integer"); + mappingConfig.putCustomTypeMappingIfAbsent("Float", "Double"); + mappingConfig.putCustomTypeMappingIfAbsent("Boolean", "Boolean"); } } diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/EnumDefinitionToDataModelMapper.java b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/EnumDefinitionToDataModelMapper.java index 575e0eed..f8410279 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/EnumDefinitionToDataModelMapper.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/EnumDefinitionToDataModelMapper.java @@ -43,9 +43,6 @@ public class EnumDefinitionToDataModelMapper { * @return list of strings */ private static List map(List enumValueDefinitions) { - if (enumValueDefinitions == null) { - return Collections.emptyList(); - } return enumValueDefinitions.stream() .map(EnumValueDefinition::getName) .map(MapperUtils::capitalizeIfRestricted) diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/FieldDefinitionToParameterMapper.java b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/FieldDefinitionToParameterMapper.java index b6904cfe..de518c32 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/FieldDefinitionToParameterMapper.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/FieldDefinitionToParameterMapper.java @@ -2,12 +2,15 @@ package com.kobylynskyi.graphql.codegen.mapper; import com.kobylynskyi.graphql.codegen.model.MappingConfig; import com.kobylynskyi.graphql.codegen.model.ParameterDefinition; +import com.kobylynskyi.graphql.codegen.model.ProjectionParameterDefinition; import com.kobylynskyi.graphql.codegen.utils.Utils; import graphql.language.FieldDefinition; -import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; +import java.util.Set; + +import static com.kobylynskyi.graphql.codegen.mapper.GraphqlTypeToJavaTypeMapper.*; +import static java.util.stream.Collectors.toList; /** * Mapper from GraphQL's FieldDefinition to a Freemarker-understandable format @@ -24,18 +27,74 @@ public class FieldDefinitionToParameterMapper { * @param parentTypeName Name of the parent GraphQL type * @return Freemarker data model of the GraphQL field definition */ - public static List map(MappingConfig mappingConfig, - List fieldDefinitions, - String parentTypeName) { - if (fieldDefinitions == null) { - return Collections.emptyList(); - } + public static List mapFields(MappingConfig mappingConfig, List fieldDefinitions, + String parentTypeName) { return fieldDefinitions.stream() .filter(fieldDef -> !generateResolversForField(mappingConfig, fieldDef, parentTypeName)) - .map(fieldDef -> GraphqlTypeToJavaTypeMapper.map(mappingConfig, fieldDef, parentTypeName)) - .collect(Collectors.toList()); + .map(fieldDef -> mapField(mappingConfig, fieldDef, parentTypeName)) + .collect(toList()); + } + + /** + * Map field definition to a Freemarker-understandable data model type + * + * @param mappingConfig Global mapping configuration + * @param fieldDefinitions List of GraphQL field definitions + * @param parentTypeName Name of the parent GraphQL type + * @param typeNames Names of all GraphQL types + * @return Freemarker data model of the GraphQL field definition + */ + public static List mapProjectionFields(MappingConfig mappingConfig, + List fieldDefinitions, + String parentTypeName, Set typeNames) { + return fieldDefinitions.stream() + .map(fieldDef -> mapProjectionField(mappingConfig, fieldDef, parentTypeName, typeNames)) + .collect(toList()); } + /** + * Map GraphQL's FieldDefinition to a Freemarker-understandable format of parameter + * + * @param mappingConfig Global mapping configuration + * @param fieldDef GraphQL field definition + * @param parentTypeName Name of the parent type + * @return Freemarker-understandable format of parameter (field) + */ + private static ParameterDefinition mapField(MappingConfig mappingConfig, FieldDefinition fieldDef, String parentTypeName) { + ParameterDefinition parameter = new ParameterDefinition(); + parameter.setName(MapperUtils.capitalizeIfRestricted(fieldDef.getName())); + parameter.setType(getJavaType(mappingConfig, fieldDef.getType(), fieldDef.getName(), parentTypeName)); + parameter.setAnnotations(getAnnotations(mappingConfig, fieldDef.getType(), fieldDef.getName(), parentTypeName, false)); + return parameter; + } + + /** + * Map GraphQL's FieldDefinition to a Freemarker-understandable format of parameter + * + * @param mappingConfig Global mapping configuration + * @param fieldDef GraphQL field definition + * @param parentTypeName Name of the parent type + * @param typeNames Names of all GraphQL types + * @return Freemarker-understandable format of parameter (field) + */ + private static ProjectionParameterDefinition mapProjectionField(MappingConfig mappingConfig, FieldDefinition fieldDef, String parentTypeName, Set typeNames) { + ProjectionParameterDefinition parameter = new ProjectionParameterDefinition(); + parameter.setName(MapperUtils.capitalizeIfRestricted(fieldDef.getName())); + String nestedType = getNestedTypeName(fieldDef.getType()); + if (typeNames.contains(nestedType)) { + parameter.setType(nestedType + mappingConfig.getResponseProjectionSuffix()); + } + return parameter; + } + + /** + * Check whether FieldResolver should be generated for a given field. + * + * @param mappingConfig Global mapping configuration + * @param fieldDef GraphQL field definition + * @param parentTypeName Name of the parent type + * @return true if FieldResolver will be generated for the field. false otherwise + */ public static boolean generateResolversForField(MappingConfig mappingConfig, FieldDefinition fieldDef, String parentTypeName) { diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/FieldDefinitionToRequestDataModelMapper.java b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/FieldDefinitionToRequestDataModelMapper.java new file mode 100644 index 00000000..645b2c5d --- /dev/null +++ b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/FieldDefinitionToRequestDataModelMapper.java @@ -0,0 +1,54 @@ +package com.kobylynskyi.graphql.codegen.mapper; + +import com.kobylynskyi.graphql.codegen.model.MappingConfig; +import com.kobylynskyi.graphql.codegen.utils.Utils; +import graphql.language.FieldDefinition; + +import java.util.HashMap; +import java.util.Map; + +import static com.kobylynskyi.graphql.codegen.model.DataModelFields.*; + +/** + * Map field definition to a Request Freemarker data model + * + * @author kobylynskyi + */ +public class FieldDefinitionToRequestDataModelMapper { + + /** + * Map field definition to a Request Freemarker data model. + * + * @param mappingConfig Global mapping configuration + * @param operationDef GraphQL operation definition + * @param objectTypeName Object type (e.g.: "Query", "Mutation" or "Subscription") + * @return Freemarker data model of the GraphQL request + */ + public static Map map(MappingConfig mappingConfig, FieldDefinition operationDef, + String objectTypeName) { + Map dataModel = new HashMap<>(); + String packageName = MapperUtils.getModelPackageName(mappingConfig); + dataModel.put(PACKAGE, packageName); + dataModel.put(IMPORTS, MapperUtils.getImportsForRequests(mappingConfig, packageName)); + dataModel.put(CLASS_NAME, getClassName(operationDef.getName(), objectTypeName, mappingConfig.getRequestSuffix())); + dataModel.put(OPERATION_NAME, operationDef.getName()); + dataModel.put(OPERATION_TYPE, objectTypeName.toUpperCase()); + dataModel.put(FIELDS, InputValueDefinitionToParameterMapper.map(mappingConfig, operationDef.getInputValueDefinitions(), operationDef.getName())); + dataModel.put(EQUALS_AND_HASH_CODE, mappingConfig.getGenerateEqualsAndHashCode()); + dataModel.put(TO_STRING, mappingConfig.getGenerateToString()); + return dataModel; + } + + /** + * Examples: + * - 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; + } + } +} diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/GraphqlTypeToJavaTypeMapper.java b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/GraphqlTypeToJavaTypeMapper.java index 4070e7bd..fce2661f 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/GraphqlTypeToJavaTypeMapper.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/GraphqlTypeToJavaTypeMapper.java @@ -1,9 +1,11 @@ package com.kobylynskyi.graphql.codegen.mapper; import com.kobylynskyi.graphql.codegen.model.MappingConfig; -import com.kobylynskyi.graphql.codegen.model.ParameterDefinition; import com.kobylynskyi.graphql.codegen.utils.Utils; -import graphql.language.*; +import graphql.language.ListType; +import graphql.language.NonNullType; +import graphql.language.Type; +import graphql.language.TypeName; import java.util.ArrayList; import java.util.List; @@ -18,39 +20,6 @@ import static graphql.language.OperationDefinition.Operation; */ class GraphqlTypeToJavaTypeMapper { - /** - * Map GraphQL's FieldDefinition to a Freemarker-understandable format of parameter - * - * @param mappingConfig Global mapping configuration - * @param fieldDef GraphQL field definition - * @param parentTypeName Name of the parent type - * @return Freemarker-understandable format of parameter (field) - */ - public static ParameterDefinition map(MappingConfig mappingConfig, FieldDefinition fieldDef, String parentTypeName) { - ParameterDefinition parameter = new ParameterDefinition(); - parameter.setName(MapperUtils.capitalizeIfRestricted(fieldDef.getName())); - parameter.setType(getJavaType(mappingConfig, fieldDef.getType(), fieldDef.getName(), parentTypeName)); - parameter.setAnnotations(getAnnotations(mappingConfig, fieldDef.getType(), fieldDef.getName(), parentTypeName, false)); - return parameter; - } - - /** - * Map GraphQL's InputValueDefinition to a Freemarker-understandable format of operation - * - * @param mappingConfig Global mapping configuration - * @param inputValueDefinition GraphQL input value definition - * @param parentTypeName Name of the parent type - * @return Freemarker-understandable format of parameter (field) - */ - public static ParameterDefinition map(MappingConfig mappingConfig, InputValueDefinition inputValueDefinition, String parentTypeName) { - ParameterDefinition parameter = new ParameterDefinition(); - parameter.setName(MapperUtils.capitalizeIfRestricted(inputValueDefinition.getName())); - parameter.setType(getJavaType(mappingConfig, inputValueDefinition.getType())); - parameter.setDefaultValue(DefaultValueMapper.map(inputValueDefinition.getDefaultValue(), inputValueDefinition.getType())); - parameter.setAnnotations(getAnnotations(mappingConfig, inputValueDefinition.getType(), inputValueDefinition.getName(), parentTypeName)); - return parameter; - } - /** * Convert GraphQL type to a corresponding Java type * @@ -83,6 +52,27 @@ class GraphqlTypeToJavaTypeMapper { return null; } + /** + * Get nested type of GraphQL Type. Example: + * Event -> Event + * Event! -> Event + * [Event!]! -> Event + * [[Event]] -> Event + * + * @param graphqlType GraphQL type + * @return GraphQL type without List/NonNull wrapping + */ + static String getNestedTypeName(Type graphqlType) { + if (graphqlType instanceof TypeName) { + return ((TypeName) graphqlType).getName(); + } else if (graphqlType instanceof ListType) { + return getNestedTypeName(((ListType) graphqlType).getType()); + } else if (graphqlType instanceof NonNullType) { + return getNestedTypeName(((NonNullType) graphqlType).getType()); + } + return null; + } + /** * Convert GraphQL type to a corresponding Java type * @@ -99,20 +89,7 @@ class GraphqlTypeToJavaTypeMapper { } else if (customTypesMapping.containsKey(graphlType)) { return customTypesMapping.get(graphlType); } - switch (graphlType) { - case "ID": - return "String"; - case "Int": - return "Integer"; - case "Float": - return "Double"; - case "String": - case "Boolean": - return graphlType; - default: - // We need to refer other custom types/interfaces/unions with prefix and suffix - return MapperUtils.getClassNameWithPrefixAndSuffix(mappingConfig, graphlType); - } + return MapperUtils.getClassNameWithPrefixAndSuffix(mappingConfig, graphlType); } /** @@ -128,8 +105,8 @@ class GraphqlTypeToJavaTypeMapper { return getAnnotations(mappingConfig, graphlType, name, parentTypeName, false); } - private static List getAnnotations(MappingConfig mappingConfig, Type type, String name, String parentTypeName, - boolean mandatory) { + static List getAnnotations(MappingConfig mappingConfig, Type type, String name, String parentTypeName, + boolean mandatory) { if (type instanceof TypeName) { return getAnnotations(mappingConfig, ((TypeName) type).getName(), name, parentTypeName, mandatory); } else if (type instanceof ListType) { @@ -205,7 +182,7 @@ class GraphqlTypeToJavaTypeMapper { * @param parentTypeName Name of the parent type * @return Java type wrapped into the subscriptionReturnType */ - static String wrapIntoSubscriptionIfRequired(MappingConfig mappingConfig, String javaTypeName, String parentTypeName) { + private static String wrapIntoSubscriptionIfRequired(MappingConfig mappingConfig, String javaTypeName, String parentTypeName) { if (parentTypeName.equalsIgnoreCase(Operation.SUBSCRIPTION.name()) && !Utils.isBlank(mappingConfig.getSubscriptionReturnType())) { return String.format("%s<%s>", mappingConfig.getSubscriptionReturnType(), javaTypeName); diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/InputValueDefinitionToParameterMapper.java b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/InputValueDefinitionToParameterMapper.java index a61cf388..fc694ac6 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/InputValueDefinitionToParameterMapper.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/InputValueDefinitionToParameterMapper.java @@ -6,7 +6,10 @@ import graphql.language.InputValueDefinition; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; + +import static com.kobylynskyi.graphql.codegen.mapper.GraphqlTypeToJavaTypeMapper.getAnnotations; +import static com.kobylynskyi.graphql.codegen.mapper.GraphqlTypeToJavaTypeMapper.getJavaType; +import static java.util.stream.Collectors.toList; /** * Mapper from GraphQL's InputValueDefinition to a Freemarker-understandable format @@ -24,12 +27,26 @@ public class InputValueDefinitionToParameterMapper { * @return Freemarker data model of the GraphQL input value definition */ public static List map(MappingConfig mappingConfig, List valueDefinitions, String parentTypeName) { - if (valueDefinitions == null) { - return Collections.emptyList(); - } return valueDefinitions.stream() - .map(inputValueDefinition -> GraphqlTypeToJavaTypeMapper.map(mappingConfig, inputValueDefinition, parentTypeName)) - .collect(Collectors.toList()); + .map(inputValueDef -> map(mappingConfig, inputValueDef, parentTypeName)) + .collect(toList()); + } + + /** + * Map GraphQL's InputValueDefinition to a Freemarker-understandable format of operation + * + * @param mappingConfig Global mapping configuration + * @param inputValueDefinition GraphQL input value definition + * @param parentTypeName Name of the parent type + * @return Freemarker-understandable format of parameter (field) + */ + private static ParameterDefinition map(MappingConfig mappingConfig, InputValueDefinition inputValueDefinition, String parentTypeName) { + ParameterDefinition parameter = new ParameterDefinition(); + parameter.setName(MapperUtils.capitalizeIfRestricted(inputValueDefinition.getName())); + parameter.setType(getJavaType(mappingConfig, inputValueDefinition.getType())); + parameter.setDefaultValue(DefaultValueMapper.map(inputValueDefinition.getDefaultValue(), inputValueDefinition.getType())); + parameter.setAnnotations(getAnnotations(mappingConfig, inputValueDefinition.getType(), inputValueDefinition.getName(), parentTypeName)); + return parameter; } } diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/InterfaceDefinitionToDataModelMapper.java b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/InterfaceDefinitionToDataModelMapper.java index d1a61529..d36ad835 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/InterfaceDefinitionToDataModelMapper.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/InterfaceDefinitionToDataModelMapper.java @@ -28,7 +28,7 @@ public class InterfaceDefinitionToDataModelMapper { dataModel.put(PACKAGE, packageName); dataModel.put(IMPORTS, MapperUtils.getImports(mappingConfig, packageName)); dataModel.put(CLASS_NAME, MapperUtils.getClassNameWithPrefixAndSuffix(mappingConfig, typeDef)); - dataModel.put(FIELDS, FieldDefinitionToParameterMapper.map(mappingConfig, typeDef.getFieldDefinitions(), typeDef.getName())); + dataModel.put(FIELDS, FieldDefinitionToParameterMapper.mapFields(mappingConfig, typeDef.getFieldDefinitions(), typeDef.getName())); return dataModel; } diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/MapperUtils.java b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/MapperUtils.java index a8c3a4fb..125d89e0 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/MapperUtils.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/MapperUtils.java @@ -192,6 +192,19 @@ public class MapperUtils { return imports; } + /** + * Returns imports required for the request class. + * + * @param mappingConfig Global mapping configuration + * @param packageName Package name of the generated class which will be ignored + * @return all imports required for a generated request class + */ + static Set getImportsForRequests(MappingConfig mappingConfig, String packageName) { + Set imports = getImports(mappingConfig, packageName); + imports.add("graphql.language"); + return imports; + } + /** * Determines if the specified operation is an async query or mutation * diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/TypeDefinitionToDataModelMapper.java b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/TypeDefinitionToDataModelMapper.java index b241bca9..d0ebb5fe 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/TypeDefinitionToDataModelMapper.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/TypeDefinitionToDataModelMapper.java @@ -2,6 +2,8 @@ package com.kobylynskyi.graphql.codegen.mapper; import com.kobylynskyi.graphql.codegen.model.MappingConfig; import com.kobylynskyi.graphql.codegen.model.ParameterDefinition; +import com.kobylynskyi.graphql.codegen.model.ProjectionParameterDefinition; +import com.kobylynskyi.graphql.codegen.utils.Utils; import graphql.language.Document; import graphql.language.InterfaceTypeDefinition; import graphql.language.ObjectTypeDefinition; @@ -10,6 +12,7 @@ import java.util.*; import java.util.stream.Collectors; import static com.kobylynskyi.graphql.codegen.model.DataModelFields.*; +import static java.util.Collections.emptySet; /** * Map type definition to a Freemarker data model @@ -39,21 +42,79 @@ public class TypeDefinitionToDataModelMapper { .map(anImplement -> GraphqlTypeToJavaTypeMapper.getJavaType(mappingConfig, anImplement)) .forEach(allInterfaces::add); dataModel.put(IMPLEMENTS, allInterfaces); - - // Merge attributes from the type and attributes from the interface - Set allParameters = new LinkedHashSet<>(FieldDefinitionToParameterMapper - .map(mappingConfig, typeDefinition.getFieldDefinitions(), typeDefinition.getName())); - List interfaces = getInterfacesOfType(mappingConfig, typeDefinition, document); - interfaces.stream() - .map(i -> FieldDefinitionToParameterMapper.map(mappingConfig, i.getFieldDefinitions(), i.getName())) - .forEach(allParameters::addAll); - dataModel.put(FIELDS, allParameters); + dataModel.put(FIELDS, getFields(mappingConfig, typeDefinition, document)); dataModel.put(EQUALS_AND_HASH_CODE, mappingConfig.getGenerateEqualsAndHashCode()); dataModel.put(TO_STRING, mappingConfig.getGenerateToString()); + return dataModel; + } + + /** + * Map type definition to a Freemarker data model of Response Projection. + * + * @param mappingConfig Global mapping configuration + * @param typeDefinition GraphQL type definition + * @param document Parent GraphQL document + * @param typeNames Names of all GraphQL types + * @return Freemarker data model of the GraphQL Response Projection + */ + public static Map mapResponseProjection(MappingConfig mappingConfig, + ObjectTypeDefinition typeDefinition, Document document, + Set typeNames) { + Map dataModel = new HashMap<>(); + String packageName = MapperUtils.getModelPackageName(mappingConfig); + dataModel.put(PACKAGE, packageName); + dataModel.put(IMPORTS, MapperUtils.getImports(mappingConfig, packageName)); + dataModel.put(CLASS_NAME, Utils.capitalize(typeDefinition.getName()) + mappingConfig.getResponseProjectionSuffix()); + dataModel.put(FIELDS, getProjectionFields(mappingConfig, typeDefinition, document, typeNames)); + dataModel.put(EQUALS_AND_HASH_CODE, mappingConfig.getGenerateEqualsAndHashCode()); + // dataModel.put(TO_STRING, mappingConfig.getGenerateToString()); already generated for serialization purposes return dataModel; } + /** + * Get merged attributes from the type and attributes from the interface. + * + * @param mappingConfig Global mapping configuration + * @param typeDefinition GraphQL type definition + * @param document Parent GraphQL document + * @return Freemarker data model of the GraphQL type + */ + @SuppressWarnings("CollectionAddAllCanBeReplacedWithConstructor") + private static Set getFields(MappingConfig mappingConfig, ObjectTypeDefinition typeDefinition, + Document document) { + Set allParameters = new LinkedHashSet<>(); + allParameters.addAll(FieldDefinitionToParameterMapper.mapFields(mappingConfig, + typeDefinition.getFieldDefinitions(), typeDefinition.getName())); + List interfaces = getInterfacesOfType(mappingConfig, typeDefinition, document); + interfaces.stream().map(i -> FieldDefinitionToParameterMapper.mapFields(mappingConfig, + i.getFieldDefinitions(), i.getName())) + .forEach(allParameters::addAll); + return allParameters; + } + + /** + * Get merged attributes from the type and attributes from the interface. + * + * @param mappingConfig Global mapping configuration + * @param typeDefinition GraphQL type definition + * @param document Parent GraphQL document + * @param typeNames Names of all GraphQL types + * @return Freemarker data model of the GraphQL type + */ + @SuppressWarnings("CollectionAddAllCanBeReplacedWithConstructor") + private static Set getProjectionFields(MappingConfig mappingConfig, ObjectTypeDefinition typeDefinition, + Document document, Set typeNames) { + Set allParameters = new LinkedHashSet<>(); + allParameters.addAll(FieldDefinitionToParameterMapper.mapProjectionFields(mappingConfig, + typeDefinition.getFieldDefinitions(), typeDefinition.getName(), typeNames)); + List interfaces = getInterfacesOfType(mappingConfig, typeDefinition, document); + interfaces.stream().map(i -> FieldDefinitionToParameterMapper.mapProjectionFields(mappingConfig, + i.getFieldDefinitions(), i.getName(), typeNames)) + .forEach(allParameters::addAll); + return allParameters; + } + /** * Scan document and return all interfaces that given type implements. * diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/model/DataModelFields.java b/src/main/java/com/kobylynskyi/graphql/codegen/model/DataModelFields.java index 08b8d733..b84fb483 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/model/DataModelFields.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/model/DataModelFields.java @@ -16,5 +16,7 @@ public final class DataModelFields { public static final String OPERATIONS = "operations"; public static final String EQUALS_AND_HASH_CODE = "equalsAndHashCode"; public static final String TO_STRING = "toString"; + public static final String OPERATION_TYPE = "operationType"; + public static final String OPERATION_NAME = "operationName"; } diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/model/DefaultMappingConfigValues.java b/src/main/java/com/kobylynskyi/graphql/codegen/model/DefaultMappingConfigValues.java index 072aabc4..b77f75b8 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/model/DefaultMappingConfigValues.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/model/DefaultMappingConfigValues.java @@ -3,6 +3,9 @@ package com.kobylynskyi.graphql.codegen.model; public class DefaultMappingConfigValues { public static final String DEFAULT_VALIDATION_ANNOTATION = "javax.validation.constraints.NotNull"; + public static final boolean DEFAULT_GENERATE_REQUESTS = false; + public static final String DEFAULT_REQUEST_SUFFIX = "Request"; + public static final String DEFAULT_RESPONSE_PROJECTION_SUFFIX = "ResponseProjection"; public static final boolean DEFAULT_GENERATE_APIS = true; public static final boolean DEFAULT_EQUALS_AND_HASHCODE = false; public static final boolean DEFAULT_TO_STRING = false; 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 b4e015e6..d11c9757 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/model/MappingConfig.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/model/MappingConfig.java @@ -49,6 +49,11 @@ public class MappingConfig implements Combinable { */ private Set fieldsWithResolvers = new HashSet<>(); + // client-side codegen configs: + private Boolean generateRequests; + private String requestSuffix; + private String responseProjectionSuffix; + @Override public void combine(MappingConfig source) { if (source == null) { @@ -81,6 +86,9 @@ public class MappingConfig implements Combinable { } else if (this.fieldsWithResolvers == null) { this.fieldsWithResolvers = source.fieldsWithResolvers; } + this.generateRequests = source.generateRequests != null ? source.generateRequests : this.generateRequests; + this.requestSuffix = source.requestSuffix != null ? source.requestSuffix : this.requestSuffix; + this.responseProjectionSuffix = source.responseProjectionSuffix != null ? source.responseProjectionSuffix : this.responseProjectionSuffix; } /** diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/model/ProjectionParameterDefinition.java b/src/main/java/com/kobylynskyi/graphql/codegen/model/ProjectionParameterDefinition.java new file mode 100644 index 00000000..f68c2bfc --- /dev/null +++ b/src/main/java/com/kobylynskyi/graphql/codegen/model/ProjectionParameterDefinition.java @@ -0,0 +1,19 @@ +package com.kobylynskyi.graphql.codegen.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Freemarker-understandable format of parameter user in ResponseProjection + * + * @author kobylynskyi + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ProjectionParameterDefinition { + + private String type; + private String name; +} diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLOperationRequest.java b/src/main/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLOperationRequest.java new file mode 100644 index 00000000..2b0c204f --- /dev/null +++ b/src/main/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLOperationRequest.java @@ -0,0 +1,15 @@ +package com.kobylynskyi.graphql.codegen.model.request; + +import graphql.language.OperationDefinition; + +import java.util.Map; + +public interface GraphQLOperationRequest { + + OperationDefinition.Operation getOperationType(); + + String getOperationName(); + + Map getInput(); + +} diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLRequest.java b/src/main/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLRequest.java new file mode 100644 index 00000000..7aca6ba0 --- /dev/null +++ b/src/main/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLRequest.java @@ -0,0 +1,29 @@ +package com.kobylynskyi.graphql.codegen.model.request; + +public class GraphQLRequest { + + private final GraphQLOperationRequest request; + private final GraphQLResponseProjection responseProjection; + + public GraphQLRequest(GraphQLOperationRequest request) { + this(request, null); + } + + public GraphQLRequest(GraphQLOperationRequest request, GraphQLResponseProjection responseProjection) { + this.request = request; + this.responseProjection = responseProjection; + } + + public GraphQLOperationRequest getRequest() { + return request; + } + + public GraphQLResponseProjection getResponseProjection() { + return responseProjection; + } + + @Override + public String toString() { + return GraphQLRequestSerializer.serialize(this); + } +} diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLRequestSerializer.java b/src/main/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLRequestSerializer.java new file mode 100644 index 00000000..4ff87f64 --- /dev/null +++ b/src/main/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLRequestSerializer.java @@ -0,0 +1,60 @@ +package com.kobylynskyi.graphql.codegen.model.request; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.stream.Collectors; + +public class GraphQLRequestSerializer { + + public static String serialize(GraphQLRequest graphQLRequest) { + if (graphQLRequest == null || graphQLRequest.getRequest() == null) { + return null; + } + StringBuilder builder = new StringBuilder(); + builder.append("{\"query\":\""); + builder.append(graphQLRequest.getRequest().getOperationType().name().toLowerCase()); + builder.append(" { "); + builder.append(graphQLRequest.getRequest().getOperationName()); + Map input = graphQLRequest.getRequest().getInput(); + if (input != null && !input.isEmpty()) { + builder.append("("); + Iterator> inputEntryIterator = input.entrySet().iterator(); + while (inputEntryIterator.hasNext()) { + Map.Entry inputEntry = inputEntryIterator.next(); + if (inputEntry.getValue() != null) { + builder.append(inputEntry.getKey()); + builder.append(": "); + builder.append(getEntry(inputEntry.getValue())); + } + if (inputEntryIterator.hasNext()) { + builder.append(", "); + } + } + builder.append(")"); + } + if (graphQLRequest.getResponseProjection() != null) { + builder.append(graphQLRequest.getResponseProjection().toString()); + } + builder.append(" }"); + builder.append("\"}"); + return builder.toString(); + } + + private static String getEntry(Object input) { + if (input instanceof Collection) { + Collection inputCollection = (Collection) input; + return inputCollection.stream() + .map(GraphQLRequestSerializer::getEntry) + .collect(Collectors.joining(", ", "[ ", " ]")); + } + if (input instanceof Enum) { + return input.toString(); + } else if (input instanceof String) { + return "\"" + input.toString() + "\""; + } else { + return input.toString(); + } + } + +} diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLResponseProjection.java b/src/main/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLResponseProjection.java new file mode 100644 index 00000000..0ec2386d --- /dev/null +++ b/src/main/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLResponseProjection.java @@ -0,0 +1,5 @@ +package com.kobylynskyi.graphql.codegen.model.request; + +public interface GraphQLResponseProjection { + +} diff --git a/src/main/resources/templates/javaClassGraphqlEnum.ftl b/src/main/resources/templates/javaClassGraphqlEnum.ftl index 6c0604a6..84f23d5e 100644 --- a/src/main/resources/templates/javaClassGraphqlEnum.ftl +++ b/src/main/resources/templates/javaClassGraphqlEnum.ftl @@ -5,7 +5,7 @@ package ${package}; public enum ${className} { <#list fields as field> - ${field}<#if field_has_next>, + ${field}<#if field_has_next>, } \ No newline at end of file diff --git a/src/main/resources/templates/javaClassGraphqlRequest.ftl b/src/main/resources/templates/javaClassGraphqlRequest.ftl new file mode 100644 index 00000000..9592e294 --- /dev/null +++ b/src/main/resources/templates/javaClassGraphqlRequest.ftl @@ -0,0 +1,66 @@ +<#if package?has_content> +package ${package}; + + +<#list imports as import> +import ${import}.*; + + +public class ${className} implements com.kobylynskyi.graphql.codegen.model.request.GraphQLOperationRequest { + + private static final OperationDefinition.Operation OPERATION_TYPE = OperationDefinition.Operation.${operationType}; + private static final String OPERATION_NAME = "${operationName}"; + + private Map input = new LinkedHashMap<>(); + + public ${className}() { + } + +<#list fields as field> + public void set${field.name?cap_first}(${field.type} ${field.name}) { + this.input.put("${field.name}", ${field.name}); + } + + + @Override + public OperationDefinition.Operation getOperationType() { + return OPERATION_TYPE; + } + + @Override + public String getOperationName() { + return OPERATION_NAME; + } + + @Override + public Map getInput() { + return input; + } + +<#if equalsAndHashCode> + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final ${className} that = (${className}) obj; + return Objects.equals(getOperationType(), that.getOperationType()) && + Objects.equals(getOperationName(), that.getOperationName()) && + Objects.equals(input, that.input); + } + + @Override + public int hashCode() { + return Objects.hash(getOperationType(), getOperationName(), input); + } + +<#if toString> + @Override + public String toString() { + return Objects.toString(input); + } + +} diff --git a/src/main/resources/templates/javaClassGraphqlResponseProjection.ftl b/src/main/resources/templates/javaClassGraphqlResponseProjection.ftl new file mode 100644 index 00000000..31a32692 --- /dev/null +++ b/src/main/resources/templates/javaClassGraphqlResponseProjection.ftl @@ -0,0 +1,56 @@ +<#if package?has_content> +package ${package}; + + +<#list imports as import> +import ${import}.*; + + +public class ${className} implements com.kobylynskyi.graphql.codegen.model.request.GraphQLResponseProjection { + + private Map fields = new LinkedHashMap<>(); + + public ${className}() { + } + +<#list fields as field> + public ${className} ${field.name}(<#if field.type?has_content>${field.type} subProjection) { + fields.put("${field.name}", <#if field.type?has_content>subProjection<#else>null); + return this; + } + + +<#if equalsAndHashCode> + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final ${className} that = (${className}) obj; + return Objects.equals(fields, that.fields); + } + + @Override + public int hashCode() { + return Objects.hash(fields); + } + + + @Override + public String toString() { + if (fields.isEmpty()) { + return ""; + } + StringJoiner joiner = new StringJoiner(" ", "{ ", " }"); + for (Map.Entry property : fields.entrySet()) { + joiner.add(property.getKey()); + if (property.getValue() != null) { + joiner.add(" ").add(property.getValue().toString()); + } + } + return joiner.toString(); + } +} diff --git a/src/main/resources/templates/javaClassGraphqlType.ftl b/src/main/resources/templates/javaClassGraphqlType.ftl index 4b2a47ce..bf195fd3 100644 --- a/src/main/resources/templates/javaClassGraphqlType.ftl +++ b/src/main/resources/templates/javaClassGraphqlType.ftl @@ -61,13 +61,19 @@ public class ${className} <#if implements?has_content>implements <#list implemen <#if toString> @Override public String toString() { - return "${className}{" + StringJoiner joiner = new StringJoiner(", ", "{ ", " }"); <#if fields?has_content> <#list fields as field> - + "${field.name}='" + ${field.name} + "'<#if field_has_next>," + if (${field.name} != null) { +<#if field.type == "String"> + joiner.add("${field.name}: \"" + ${field.name} + "\""); +<#else> + joiner.add("${field.name}: " + ${field.name}); + + } - + "}"; + return joiner.toString(); } } diff --git a/src/test/java/com/kobylynskyi/graphql/codegen/GraphqlCodegenRequestTest.java b/src/test/java/com/kobylynskyi/graphql/codegen/GraphqlCodegenRequestTest.java new file mode 100644 index 00000000..81850768 --- /dev/null +++ b/src/test/java/com/kobylynskyi/graphql/codegen/GraphqlCodegenRequestTest.java @@ -0,0 +1,115 @@ +package com.kobylynskyi.graphql.codegen; + +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 java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Arrays; +import java.util.Objects; + +import static com.kobylynskyi.graphql.codegen.TestUtils.assertSameTrimmedContent; +import static java.util.Collections.singletonList; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class GraphqlCodegenRequestTest { + + private final File outputBuildDir = new File("build/generated"); + private final File outputJavaClassesDir = new File("build/generated/com/github/graphql"); + private final MappingConfig mappingConfig = new MappingConfig(); + + @BeforeEach + void init() { + mappingConfig.setPackageName("com.github.graphql"); + mappingConfig.setResponseProjectionSuffix("ResponseProjection"); + mappingConfig.setRequestSuffix("Request"); + mappingConfig.setGenerateRequests(true); + mappingConfig.setGenerateToString(false); // should be overridden to true + mappingConfig.setGenerateApis(false); + } + + @AfterEach + void cleanup() throws IOException { + Utils.deleteDir(new File("build/generated")); + } + + @Test + void generate_RequestAndResponseProjections() throws Exception { + new GraphqlCodegen(singletonList("src/test/resources/schemas/test.graphqls"), + outputBuildDir, mappingConfig).generate(); + + File[] files = Objects.requireNonNull(outputJavaClassesDir.listFiles()); + + assertSameTrimmedContent(new File("src/test/resources/expected-classes/request/EventResponseProjection.java.txt"), + getGeneratedFile(files, "EventResponseProjection.java")); + assertSameTrimmedContent(new File("src/test/resources/expected-classes/request/EventPropertyResponseProjection.java.txt"), + getGeneratedFile(files, "EventPropertyResponseProjection.java")); + assertSameTrimmedContent(new File("src/test/resources/expected-classes/request/EventsByCategoryAndStatusQueryRequest.java.txt"), + getGeneratedFile(files, "EventsByCategoryAndStatusQueryRequest.java")); + assertSameTrimmedContent(new File("src/test/resources/expected-classes/request/VersionQueryRequest.java.txt"), + getGeneratedFile(files, "VersionQueryRequest.java")); + assertSameTrimmedContent(new File("src/test/resources/expected-classes/request/EventsByIdsQueryRequest.java.txt"), + getGeneratedFile(files, "EventsByIdsQueryRequest.java")); + } + + @Test + void generate_WithModelSuffix() throws Exception { + mappingConfig.setModelNameSuffix("TO"); + new GraphqlCodegen(singletonList("src/test/resources/schemas/test.graphqls"), + outputBuildDir, mappingConfig).generate(); + + File[] files = Objects.requireNonNull(outputJavaClassesDir.listFiles()); + + assertSameTrimmedContent(new File("src/test/resources/expected-classes/request/EventStatusTO.java.txt"), + getGeneratedFile(files, "EventStatusTO.java")); + assertSameTrimmedContent(new File("src/test/resources/expected-classes/request/EventsByCategoryAndStatusQueryRequest_withModelSuffix.java.txt"), + getGeneratedFile(files, "EventsByCategoryAndStatusQueryRequest.java")); + } + + @Test + void generate_RequestAndResponseProjections_github() throws Exception { + new GraphqlCodegen(singletonList("src/test/resources/schemas/github.graphqls"), + outputBuildDir, mappingConfig).generate(); + + File[] files = Objects.requireNonNull(outputJavaClassesDir.listFiles()); + + assertSameTrimmedContent(new File("src/test/resources/expected-classes/request/CodeOfConductResponseProjection.java.txt"), + getGeneratedFile(files, "CodeOfConductResponseProjection.java")); + + assertSameTrimmedContent(new File("src/test/resources/expected-classes/request/UpdateRepositoryMutationRequest.java.txt"), + getGeneratedFile(files, "UpdateRepositoryMutationRequest.java")); + } + + @Test + void generate_ToStringIsEnabledForInput() throws Exception { + new GraphqlCodegen(singletonList("src/test/resources/schemas/github.graphqls"), + outputBuildDir, mappingConfig).generate(); + + File[] files = Objects.requireNonNull(outputJavaClassesDir.listFiles()); + + assertSameTrimmedContent(new File("src/test/resources/expected-classes/request/AcceptTopicSuggestionInput.java.txt"), + getGeneratedFile(files, "AcceptTopicSuggestionInput.java")); + } + + @Test + void generate_emptyRequestSuffix() throws Exception { + mappingConfig.setRequestSuffix(""); + new GraphqlCodegen(singletonList("src/test/resources/schemas/test.graphqls"), + outputBuildDir, mappingConfig).generate(); + + File[] files = Objects.requireNonNull(outputJavaClassesDir.listFiles()); + + assertNotNull(getGeneratedFile(files, "EventsByCategoryAndStatusQuery.java")); + } + + private static File getGeneratedFile(File[] files, String fileName) throws FileNotFoundException { + return Arrays.stream(files) + .filter(f -> f.getName().equalsIgnoreCase(fileName)) + .findFirst() + .orElseThrow(FileNotFoundException::new); + } +} \ No newline at end of file diff --git a/src/test/java/com/kobylynskyi/graphql/codegen/GraphqlCodegenTest.java b/src/test/java/com/kobylynskyi/graphql/codegen/GraphqlCodegenTest.java index 23920b24..71d2baba 100644 --- a/src/test/java/com/kobylynskyi/graphql/codegen/GraphqlCodegenTest.java +++ b/src/test/java/com/kobylynskyi/graphql/codegen/GraphqlCodegenTest.java @@ -17,6 +17,7 @@ import java.nio.file.NoSuchFileException; import java.util.*; import static com.kobylynskyi.graphql.codegen.TestUtils.assertSameTrimmedContent; +import static java.util.Collections.singletonMap; import static java.util.stream.Collectors.toList; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.*; @@ -48,11 +49,11 @@ class GraphqlCodegenTest { File[] files = Objects.requireNonNull(outputJavaClassesDir.listFiles()); List generatedFileNames = Arrays.stream(files).map(File::getName).sorted().collect(toList()); - assertEquals( - Arrays.asList("CreateEventMutation.java", "Event.java", "EventByIdQuery.java", - "EventProperty.java", "EventStatus.java", "EventsByCategoryAndStatusQuery.java", - "EventsCreatedSubscription.java", "Mutation.java", "Query.java", "Subscription.java", - "VersionQuery.java"), generatedFileNames); + assertEquals(Arrays.asList( + "CreateEventMutation.java", "Event.java", "EventByIdQuery.java", "EventProperty.java", + "EventStatus.java", "EventsByCategoryAndStatusQuery.java", "EventsByIdsQuery.java", + "EventsCreatedSubscription.java", "Mutation.java", "Query.java", "Subscription.java", + "VersionQuery.java"), generatedFileNames); for (File file : files) { File expected = new File(String.format("src/test/resources/expected-classes/%s.txt", file.getName())); @@ -62,7 +63,7 @@ class GraphqlCodegenTest { @Test void generate_CustomMappings() throws Exception { - mappingConfig.setCustomTypesMapping(Collections.singletonMap("DateTime", "java.util.Date")); + mappingConfig.setCustomTypesMapping(new HashMap<>(singletonMap("DateTime", "java.util.Date"))); generator.generate(); @@ -76,8 +77,7 @@ class GraphqlCodegenTest { @Test void generate_CustomMappings_Nested() throws Exception { - mappingConfig.setCustomTypesMapping( - new HashMap<>(Collections.singletonMap("EventProperty.intVal", "java.math.BigInteger"))); + mappingConfig.setCustomTypesMapping(new HashMap<>(singletonMap("EventProperty.intVal", "java.math.BigInteger"))); generator.generate(); @@ -121,9 +121,9 @@ class GraphqlCodegenTest { @Test void generate_CustomAnnotationMappings() throws Exception { mappingConfig.setCustomTypesMapping( - new HashMap<>(Collections.singletonMap("Event.createdDateTime", "org.joda.time.DateTime"))); + new HashMap<>(singletonMap("Event.createdDateTime", "org.joda.time.DateTime"))); - mappingConfig.setCustomAnnotationsMapping(new HashMap<>(Collections.singletonMap("Event.createdDateTime", + mappingConfig.setCustomAnnotationsMapping(new HashMap<>(singletonMap("Event.createdDateTime", "com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = com.example.json.DateTimeScalarDeserializer.class)"))); generator.generate(); @@ -139,9 +139,9 @@ class GraphqlCodegenTest { @Test void generate_CustomAnnotationMappings_Type() throws Exception { mappingConfig.setCustomTypesMapping( - new HashMap<>(Collections.singletonMap("DateTime", "org.joda.time.DateTime"))); + new HashMap<>(singletonMap("DateTime", "org.joda.time.DateTime"))); - mappingConfig.setCustomAnnotationsMapping(new HashMap<>(Collections.singletonMap("DateTime", + mappingConfig.setCustomAnnotationsMapping(new HashMap<>(singletonMap("DateTime", "com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = com.example.json.DateTimeScalarDeserializer.class)"))); generator.generate(); @@ -157,9 +157,9 @@ class GraphqlCodegenTest { @Test void generate_CustomAnnotationMappings_FieldType() throws Exception { mappingConfig - .setCustomTypesMapping(new HashMap<>(Collections.singletonMap("DateTime", "org.joda.time.DateTime"))); + .setCustomTypesMapping(new HashMap<>(singletonMap("DateTime", "org.joda.time.DateTime"))); - mappingConfig.setCustomAnnotationsMapping(new HashMap<>(Collections.singletonMap("Event.createdDateTime", + mappingConfig.setCustomAnnotationsMapping(new HashMap<>(singletonMap("Event.createdDateTime", "com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = com.example.json.DateTimeScalarDeserializer.class)"))); generator.generate(); @@ -207,7 +207,7 @@ class GraphqlCodegenTest { File[] apiFiles = Objects.requireNonNull(new File(outputJavaClassesDir, "api").listFiles()); List generatedApiFileNames = Arrays.stream(apiFiles).map(File::getName).sorted().collect(toList()); assertEquals(Arrays.asList("CreateEventMutation.java", "EventByIdQuery.java", - "EventsByCategoryAndStatusQuery.java", "EventsCreatedSubscription.java", "Mutation.java", "Query.java", + "EventsByCategoryAndStatusQuery.java", "EventsByIdsQuery.java", "EventsCreatedSubscription.java", "Mutation.java", "Query.java", "Subscription.java", "VersionQuery.java"), generatedApiFileNames); Arrays.stream(apiFiles).forEach(file -> { try { @@ -375,11 +375,11 @@ class GraphqlCodegenTest { File[] files = Objects.requireNonNull(outputJavaClassesDir.listFiles()); assertFileContainsElements(files, "CreateEventMutation.java", - "import java.util.concurrent","CompletableFuture createEvent("); + "import java.util.concurrent", "CompletableFuture createEvent("); } - private void assertFileContainsElements(File[] files, String fileName, String...elements) + private void assertFileContainsElements(File[] files, String fileName, String... elements) throws IOException { File file = getFile(files, fileName); diff --git a/src/test/java/com/kobylynskyi/graphql/codegen/model/MappingConfigTest.java b/src/test/java/com/kobylynskyi/graphql/codegen/model/MappingConfigTest.java index 5ed1f6a0..6a938f15 100644 --- a/src/test/java/com/kobylynskyi/graphql/codegen/model/MappingConfigTest.java +++ b/src/test/java/com/kobylynskyi/graphql/codegen/model/MappingConfigTest.java @@ -46,6 +46,9 @@ class MappingConfigTest { assertFalse(mappingConfig.getGenerateAsyncApi()); assertTrue(mappingConfig.getGenerateParameterizedFieldsResolvers()); assertEquals(singleton("5"), mappingConfig.getFieldsWithResolvers()); + assertEquals("6", mappingConfig.getRequestSuffix()); + assertEquals("7", mappingConfig.getResponseProjectionSuffix()); + assertFalse(mappingConfig.getGenerateRequests()); } @Test @@ -68,6 +71,9 @@ class MappingConfigTest { assertFalse(mappingConfig.getGenerateAsyncApi()); assertTrue(mappingConfig.getGenerateParameterizedFieldsResolvers()); assertEquals(singleton("5"), mappingConfig.getFieldsWithResolvers()); + assertEquals("6", mappingConfig.getRequestSuffix()); + assertEquals("7", mappingConfig.getResponseProjectionSuffix()); + assertFalse(mappingConfig.getGenerateRequests()); } @Test @@ -92,6 +98,9 @@ class MappingConfigTest { assertTrue(mappingConfig.getGenerateAsyncApi()); assertFalse(mappingConfig.getGenerateParameterizedFieldsResolvers()); assertEquals(new HashSet<>(Arrays.asList("5", "55")), mappingConfig.getFieldsWithResolvers()); + assertEquals("66", mappingConfig.getRequestSuffix()); + assertEquals("77", mappingConfig.getResponseProjectionSuffix()); + assertTrue(mappingConfig.getGenerateRequests()); } private static Map hashMap(AbstractMap.SimpleEntry... entries) { @@ -116,6 +125,9 @@ class MappingConfigTest { config.setGenerateAsyncApi(false); config.setGenerateParameterizedFieldsResolvers(true); config.setFieldsWithResolvers(new HashSet<>(singletonList("5"))); + config.setRequestSuffix("6"); + config.setResponseProjectionSuffix("7"); + config.setGenerateRequests(false); return config; } @@ -136,6 +148,9 @@ class MappingConfigTest { config.setGenerateAsyncApi(true); config.setGenerateParameterizedFieldsResolvers(false); config.setFieldsWithResolvers(singleton("55")); + config.setRequestSuffix("66"); + config.setResponseProjectionSuffix("77"); + config.setGenerateRequests(true); return config; } diff --git a/src/test/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLRequestSerializerTest.java b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLRequestSerializerTest.java new file mode 100644 index 00000000..ff4cecb7 --- /dev/null +++ b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/GraphQLRequestSerializerTest.java @@ -0,0 +1,93 @@ +package com.kobylynskyi.graphql.codegen.model.request; + +import com.kobylynskyi.graphql.codegen.model.request.data.*; +import com.kobylynskyi.graphql.codegen.utils.Utils; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class GraphQLRequestSerializerTest { + + @Test + void serialize_Null() { + assertNull(GraphQLRequestSerializer.serialize(null)); + } + + @Test + void serialize_Empty() { + assertNull(GraphQLRequestSerializer.serialize(new GraphQLRequest(null))); + } + + @Test + void serialize_noResponseProjection() throws IOException { + String fileContent = getExpectedQueryString("versionQuery.txt"); + GraphQLRequest graphQLRequest = new GraphQLRequest(new VersionQueryRequest()); + String serializedQuery = graphQLRequest.toString().replaceAll(" +", " ").trim(); + assertEquals(fileContent, serializedQuery); + } + + @Test + void serialize_withResponseProjection() throws IOException { + String fileContent = getExpectedQueryString("eventsByCategoryAndStatusQuery.txt"); + EventsByCategoryAndStatusQueryRequest request = new EventsByCategoryAndStatusQueryRequest(); + request.setCategoryId("categoryIdValue1"); + request.setStatus(Status.OPEN); + GraphQLRequest graphQLRequest = new GraphQLRequest(request, + new EventResponseProjection() + .id() + .active() + .properties(new EventPropertyResponseProjection() + .floatVal() + .child(new EventPropertyResponseProjection() + .intVal() + .parent(new EventResponseProjection() + .id())) + .booleanVal()) + .status() + ); + String serializedQuery = graphQLRequest.toString().replaceAll(" +", " ").trim(); + assertEquals(fileContent, serializedQuery); + } + + @Test + void serialize_complexRequestWithDefaultData() throws IOException { + String fileContent = getExpectedQueryString("updateIssueMutation.txt"); + UpdateIssueMutationRequest requestWithDefaultData = new UpdateIssueMutationRequest(); + requestWithDefaultData.setInput(new UpdateIssueInput()); + GraphQLRequest graphQLRequest = new GraphQLRequest(requestWithDefaultData, + new UpdateIssuePayloadResponseProjection() + .clientMutationId() + .issue(new IssueResponseProjection() + .activeLockReason()) + ); + String serializedQuery = graphQLRequest.toString().replaceAll(" +", " ").trim(); + assertEquals(fileContent, serializedQuery); + } + + @Test + void serialize_collectionRequest() throws IOException { + String fileContent = getExpectedQueryString("eventsByIdsQuery.txt"); + EventsByIdsQueryRequest request = new EventsByIdsQueryRequest(); + request.setIds(Arrays.asList("4", "5", "6")); + GraphQLRequest graphQLRequest = new GraphQLRequest(request, + new EventResponseProjection() + .id() + ); + String serializedQuery = graphQLRequest.toString().replaceAll(" +", " ").trim(); + assertEquals(fileContent, serializedQuery); + } + + private static String getExpectedQueryString(final String fileName) throws IOException { + String trimmedContent = Utils.getFileContent( + new File("src/test/resources/expected-classes/request/graphql-query/" + fileName).getPath()) + .replaceAll(System.lineSeparator(), " ") + .replaceAll(" +", " ").trim(); + return String.format("{\"query\":\"%s\"}", trimmedContent); + } + +} \ No newline at end of file diff --git a/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/EventPropertyResponseProjection.java b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/EventPropertyResponseProjection.java new file mode 100644 index 00000000..8f9b59a1 --- /dev/null +++ b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/EventPropertyResponseProjection.java @@ -0,0 +1,59 @@ +package com.kobylynskyi.graphql.codegen.model.request.data; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.StringJoiner; + +public class EventPropertyResponseProjection implements com.kobylynskyi.graphql.codegen.model.request.GraphQLResponseProjection { + + private Map fields = new LinkedHashMap<>(); + + public EventPropertyResponseProjection() { + } + + public EventPropertyResponseProjection floatVal() { + fields.put("floatVal", null); + return this; + } + + public EventPropertyResponseProjection booleanVal() { + fields.put("booleanVal", null); + return this; + } + + public EventPropertyResponseProjection intVal() { + fields.put("intVal", null); + return this; + } + + public EventPropertyResponseProjection stringVal() { + fields.put("stringVal", null); + return this; + } + + public EventPropertyResponseProjection child(EventPropertyResponseProjection subProjection) { + fields.put("child", subProjection); + return this; + } + + public EventPropertyResponseProjection parent(EventResponseProjection subProjection) { + fields.put("parent", subProjection); + return this; + } + + + @Override + public String toString() { + if (fields.isEmpty()) { + return ""; + } + StringJoiner joiner = new StringJoiner(" ", "{ ", " }"); + for (Map.Entry property : fields.entrySet()) { + joiner.add(property.getKey()); + if (property.getValue() != null) { + joiner.add(" ").add(property.getValue().toString()); + } + } + return joiner.toString(); + } +} \ No newline at end of file diff --git a/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/EventResponseProjection.java b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/EventResponseProjection.java new file mode 100644 index 00000000..88560352 --- /dev/null +++ b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/EventResponseProjection.java @@ -0,0 +1,67 @@ +package com.kobylynskyi.graphql.codegen.model.request.data; + +import java.util.*; + +public class EventResponseProjection implements com.kobylynskyi.graphql.codegen.model.request.GraphQLResponseProjection { + + private Map fields = new LinkedHashMap<>(); + + public EventResponseProjection() { + } + + public EventResponseProjection id() { + fields.put("id", null); + return this; + } + + public EventResponseProjection categoryId() { + fields.put("categoryId", null); + return this; + } + + public EventResponseProjection properties(EventPropertyResponseProjection subProjection) { + fields.put("properties", subProjection); + return this; + } + + public EventResponseProjection status() { + fields.put("status", null); + return this; + } + + public EventResponseProjection createdBy() { + fields.put("createdBy", null); + return this; + } + + public EventResponseProjection createdDateTime() { + fields.put("createdDateTime", null); + return this; + } + + public EventResponseProjection active() { + fields.put("active", null); + return this; + } + + public EventResponseProjection rating() { + fields.put("rating", null); + return this; + } + + + @Override + public String toString() { + if (fields.isEmpty()) { + return ""; + } + StringJoiner joiner = new StringJoiner(" ", "{ ", " }"); + for (Map.Entry property : fields.entrySet()) { + joiner.add(property.getKey()); + if (property.getValue() != null) { + joiner.add(" ").add(property.getValue().toString()); + } + } + return joiner.toString(); + } +} \ No newline at end of file diff --git a/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/EventsByCategoryAndStatusQueryRequest.java b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/EventsByCategoryAndStatusQueryRequest.java new file mode 100644 index 00000000..098b264a --- /dev/null +++ b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/EventsByCategoryAndStatusQueryRequest.java @@ -0,0 +1,39 @@ +package com.kobylynskyi.graphql.codegen.model.request.data; + +import java.util.*; +import graphql.language.*; + +public class EventsByCategoryAndStatusQueryRequest implements com.kobylynskyi.graphql.codegen.model.request.GraphQLOperationRequest { + + private static final OperationDefinition.Operation OPERATION_TYPE = OperationDefinition.Operation.QUERY; + private static final String OPERATION_NAME = "eventsByCategoryAndStatus"; + + private Map input = new LinkedHashMap<>(); + + public EventsByCategoryAndStatusQueryRequest() { + } + + public void setCategoryId(String categoryId) { + this.input.put("categoryId", categoryId); + } + + public void setStatus(Status status) { + this.input.put("status", status); + } + + @Override + public OperationDefinition.Operation getOperationType() { + return OPERATION_TYPE; + } + + @Override + public String getOperationName() { + return OPERATION_NAME; + } + + @Override + public Map getInput() { + return input; + } + +} \ No newline at end of file diff --git a/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/EventsByIdsQueryRequest.java b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/EventsByIdsQueryRequest.java new file mode 100644 index 00000000..df03939d --- /dev/null +++ b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/EventsByIdsQueryRequest.java @@ -0,0 +1,39 @@ +package com.kobylynskyi.graphql.codegen.model.request.data; + +import java.util.*; +import graphql.language.*; + +public class EventsByIdsQueryRequest implements com.kobylynskyi.graphql.codegen.model.request.GraphQLOperationRequest { + + private static final OperationDefinition.Operation OPERATION_TYPE = OperationDefinition.Operation.QUERY; + private static final String OPERATION_NAME = "eventsByIds"; + + private Map input = new LinkedHashMap<>(); + + public EventsByIdsQueryRequest() { + } + + public void setIds(Collection ids) { + this.input.put("ids", ids); + } + + @Override + public OperationDefinition.Operation getOperationType() { + return OPERATION_TYPE; + } + + @Override + public String getOperationName() { + return OPERATION_NAME; + } + + @Override + public Map getInput() { + return input; + } + + @Override + public String toString() { + return Objects.toString(input); + } +} \ No newline at end of file diff --git a/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/IssueResponseProjection.java b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/IssueResponseProjection.java new file mode 100644 index 00000000..b021a85a --- /dev/null +++ b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/IssueResponseProjection.java @@ -0,0 +1,35 @@ +package com.kobylynskyi.graphql.codegen.model.request.data; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.StringJoiner; + +public class IssueResponseProjection implements com.kobylynskyi.graphql.codegen.model.request.GraphQLResponseProjection { + + private Map fields = new LinkedHashMap<>(); + + public IssueResponseProjection() { + } + + public IssueResponseProjection activeLockReason() { + fields.put("activeLockReason", null); + return this; + } + + // REST OF THE STUFF WAS REMOVED + + @Override + public String toString() { + if (fields.isEmpty()) { + return ""; + } + StringJoiner joiner = new StringJoiner(" ", "{ ", " }"); + for (Map.Entry property : fields.entrySet()) { + joiner.add(property.getKey()); + if (property.getValue() != null) { + joiner.add(" ").add(property.getValue().toString()); + } + } + return joiner.toString(); + } +} diff --git a/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/Status.java b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/Status.java new file mode 100644 index 00000000..ba11cfa5 --- /dev/null +++ b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/Status.java @@ -0,0 +1,5 @@ +package com.kobylynskyi.graphql.codegen.model.request.data; + +public enum Status { + OPEN +} diff --git a/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/UpdateIssueInput.java b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/UpdateIssueInput.java new file mode 100644 index 00000000..c49abe57 --- /dev/null +++ b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/UpdateIssueInput.java @@ -0,0 +1,52 @@ +package com.kobylynskyi.graphql.codegen.model.request.data; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.StringJoiner; + +public class UpdateIssueInput { + + private Double floatVal = 1.23; + private Boolean booleanVal = false; + private Integer intVal = 42; + private String stringVal = "my-default"; + private Status enumVal = Status.OPEN; + private UpdateIssueInput objectWithNullDefault = null; + private Collection intList = Arrays.asList(1, 2, 3); + private Collection intListEmptyDefault = Collections.emptyList(); + + public UpdateIssueInput() { + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner(", ", "{ ", " }"); + if (floatVal != null) { + joiner.add("floatVal: " + floatVal); + } + if (booleanVal != null) { + joiner.add("booleanVal: " + booleanVal); + } + if (intVal != null) { + joiner.add("intVal: " + intVal); + } + if (stringVal != null) { + joiner.add("stringVal: \"" + stringVal + "\""); + } + if (enumVal != null) { + joiner.add("enumVal: " + enumVal); + } + if (objectWithNullDefault != null) { + joiner.add("objectWithNullDefault: " + objectWithNullDefault); + } + if (intList != null) { + joiner.add("intList: " + intList); + } + if (intListEmptyDefault != null) { + joiner.add("intListEmptyDefault: " + intListEmptyDefault); + } + return joiner.toString(); + } + +} diff --git a/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/UpdateIssueMutationRequest.java b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/UpdateIssueMutationRequest.java new file mode 100644 index 00000000..86e3fbc0 --- /dev/null +++ b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/UpdateIssueMutationRequest.java @@ -0,0 +1,37 @@ +package com.kobylynskyi.graphql.codegen.model.request.data; + +import graphql.language.OperationDefinition; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class UpdateIssueMutationRequest implements com.kobylynskyi.graphql.codegen.model.request.GraphQLOperationRequest { + + private static final OperationDefinition.Operation OPERATION_TYPE = OperationDefinition.Operation.MUTATION; + private static final String OPERATION_NAME = "updateIssue"; + + private Map input = new LinkedHashMap<>(); + + public UpdateIssueMutationRequest() { + } + + public void setInput(UpdateIssueInput input) { + this.input.put("input", input); + } + + @Override + public OperationDefinition.Operation getOperationType() { + return OPERATION_TYPE; + } + + @Override + public String getOperationName() { + return OPERATION_NAME; + } + + @Override + public Map getInput() { + return input; + } + +} diff --git a/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/UpdateIssuePayloadResponseProjection.java b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/UpdateIssuePayloadResponseProjection.java new file mode 100644 index 00000000..52508955 --- /dev/null +++ b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/UpdateIssuePayloadResponseProjection.java @@ -0,0 +1,37 @@ +package com.kobylynskyi.graphql.codegen.model.request.data; + +import java.util.*; + +public class UpdateIssuePayloadResponseProjection implements com.kobylynskyi.graphql.codegen.model.request.GraphQLResponseProjection { + + private Map fields = new LinkedHashMap<>(); + + public UpdateIssuePayloadResponseProjection() { + } + + public UpdateIssuePayloadResponseProjection clientMutationId() { + fields.put("clientMutationId", null); + return this; + } + + public UpdateIssuePayloadResponseProjection issue(IssueResponseProjection subProjection) { + fields.put("issue", subProjection); + return this; + } + + + @Override + public String toString() { + if (fields.isEmpty()) { + return ""; + } + StringJoiner joiner = new StringJoiner(" ", "{ ", " }"); + for (Map.Entry property : fields.entrySet()) { + joiner.add(property.getKey()); + if (property.getValue() != null) { + joiner.add(" ").add(property.getValue().toString()); + } + } + return joiner.toString(); + } +} diff --git a/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/VersionQueryRequest.java b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/VersionQueryRequest.java new file mode 100644 index 00000000..878fbd60 --- /dev/null +++ b/src/test/java/com/kobylynskyi/graphql/codegen/model/request/data/VersionQueryRequest.java @@ -0,0 +1,31 @@ +package com.kobylynskyi.graphql.codegen.model.request.data; + +import java.util.*; +import graphql.language.*; + +public class VersionQueryRequest implements com.kobylynskyi.graphql.codegen.model.request.GraphQLOperationRequest { + + private static final OperationDefinition.Operation OPERATION_TYPE = OperationDefinition.Operation.QUERY; + private static final String OPERATION_NAME = "version"; + + private Map input = new LinkedHashMap<>(); + + public VersionQueryRequest() { + } + + @Override + public OperationDefinition.Operation getOperationType() { + return OPERATION_TYPE; + } + + @Override + public String getOperationName() { + return OPERATION_NAME; + } + + @Override + public Map getInput() { + return input; + } + +} \ No newline at end of file diff --git a/src/test/resources/expected-classes/EventStatus.java.txt b/src/test/resources/expected-classes/EventStatus.java.txt index d4f9d10b..d9752989 100644 --- a/src/test/resources/expected-classes/EventStatus.java.txt +++ b/src/test/resources/expected-classes/EventStatus.java.txt @@ -2,8 +2,8 @@ package com.kobylynskyi.graphql.test1; public enum EventStatus { - OPEN, - IN_PROGRESS, + OPEN, + IN_PROGRESS, LOGGED } \ No newline at end of file diff --git a/src/test/resources/expected-classes/EventsByIdsQuery.java.txt b/src/test/resources/expected-classes/EventsByIdsQuery.java.txt new file mode 100644 index 00000000..473dbb14 --- /dev/null +++ b/src/test/resources/expected-classes/EventsByIdsQuery.java.txt @@ -0,0 +1,10 @@ +package com.kobylynskyi.graphql.test1; + +import java.util.*; + +public interface EventsByIdsQuery { + + @javax.validation.constraints.NotNull + Collection eventsByIds(Collection ids) throws Exception; + +} \ No newline at end of file diff --git a/src/test/resources/expected-classes/MyEnum.java.txt b/src/test/resources/expected-classes/MyEnum.java.txt index e112c54b..9a1bf63d 100644 --- a/src/test/resources/expected-classes/MyEnum.java.txt +++ b/src/test/resources/expected-classes/MyEnum.java.txt @@ -2,8 +2,8 @@ package com.kobylynskyi.graphql.testdefaults; public enum MyEnum { - ONE, - TWO, + ONE, + TWO, THREE } \ No newline at end of file diff --git a/src/test/resources/expected-classes/Query.java.txt b/src/test/resources/expected-classes/Query.java.txt index dc5d9293..d9938751 100644 --- a/src/test/resources/expected-classes/Query.java.txt +++ b/src/test/resources/expected-classes/Query.java.txt @@ -13,4 +13,7 @@ public interface Query { @javax.validation.constraints.NotNull Event eventById(String id) throws Exception; + @javax.validation.constraints.NotNull + Collection eventsByIds(Collection ids) throws Exception; + } \ No newline at end of file diff --git a/src/test/resources/expected-classes/request/AcceptTopicSuggestionInput.java.txt b/src/test/resources/expected-classes/request/AcceptTopicSuggestionInput.java.txt new file mode 100644 index 00000000..a4f3e3fc --- /dev/null +++ b/src/test/resources/expected-classes/request/AcceptTopicSuggestionInput.java.txt @@ -0,0 +1,57 @@ +package com.github.graphql; + +import java.util.*; + +public class AcceptTopicSuggestionInput { + + private String clientMutationId; + @javax.validation.constraints.NotNull + private String name; + @javax.validation.constraints.NotNull + private String repositoryId; + + public AcceptTopicSuggestionInput() { + } + + public AcceptTopicSuggestionInput(String clientMutationId, String name, String repositoryId) { + this.clientMutationId = clientMutationId; + this.name = name; + this.repositoryId = repositoryId; + } + + public String getClientMutationId() { + return clientMutationId; + } + public void setClientMutationId(String clientMutationId) { + this.clientMutationId = clientMutationId; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + public String getRepositoryId() { + return repositoryId; + } + public void setRepositoryId(String repositoryId) { + this.repositoryId = repositoryId; + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner(", ", "{ ", " }"); + if (clientMutationId != null) { + joiner.add("clientMutationId: \"" + clientMutationId + "\""); + } + if (name != null) { + joiner.add("name: \"" + name + "\""); + } + if (repositoryId != null) { + joiner.add("repositoryId: \"" + repositoryId + "\""); + } + return joiner.toString(); + } +} \ No newline at end of file diff --git a/src/test/resources/expected-classes/request/CodeOfConductResponseProjection.java.txt b/src/test/resources/expected-classes/request/CodeOfConductResponseProjection.java.txt new file mode 100644 index 00000000..4828028a --- /dev/null +++ b/src/test/resources/expected-classes/request/CodeOfConductResponseProjection.java.txt @@ -0,0 +1,57 @@ +package com.github.graphql; + +import java.util.*; + +public class CodeOfConductResponseProjection implements com.kobylynskyi.graphql.codegen.model.request.GraphQLResponseProjection { + + private Map fields = new LinkedHashMap<>(); + + public CodeOfConductResponseProjection() { + } + + public CodeOfConductResponseProjection body() { + fields.put("body", null); + return this; + } + + public CodeOfConductResponseProjection id() { + fields.put("id", null); + return this; + } + + public CodeOfConductResponseProjection key() { + fields.put("key", null); + return this; + } + + public CodeOfConductResponseProjection name() { + fields.put("name", null); + return this; + } + + public CodeOfConductResponseProjection resourcePath() { + fields.put("resourcePath", null); + return this; + } + + public CodeOfConductResponseProjection url() { + fields.put("url", null); + return this; + } + + + @Override + public String toString() { + if (fields.isEmpty()) { + return ""; + } + StringJoiner joiner = new StringJoiner(" ", "{ ", " }"); + for (Map.Entry property : fields.entrySet()) { + joiner.add(property.getKey()); + if (property.getValue() != null) { + joiner.add(" ").add(property.getValue().toString()); + } + } + return joiner.toString(); + } +} \ No newline at end of file diff --git a/src/test/resources/expected-classes/request/EventPropertyResponseProjection.java.txt b/src/test/resources/expected-classes/request/EventPropertyResponseProjection.java.txt new file mode 100644 index 00000000..6f5c61ec --- /dev/null +++ b/src/test/resources/expected-classes/request/EventPropertyResponseProjection.java.txt @@ -0,0 +1,57 @@ +package com.github.graphql; + +import java.util.*; + +public class EventPropertyResponseProjection implements com.kobylynskyi.graphql.codegen.model.request.GraphQLResponseProjection { + + private Map fields = new LinkedHashMap<>(); + + public EventPropertyResponseProjection() { + } + + public EventPropertyResponseProjection floatVal() { + fields.put("floatVal", null); + return this; + } + + public EventPropertyResponseProjection booleanVal() { + fields.put("booleanVal", null); + return this; + } + + public EventPropertyResponseProjection intVal() { + fields.put("intVal", null); + return this; + } + + public EventPropertyResponseProjection stringVal() { + fields.put("stringVal", null); + return this; + } + + public EventPropertyResponseProjection child(EventPropertyResponseProjection subProjection) { + fields.put("child", subProjection); + return this; + } + + public EventPropertyResponseProjection parent(EventResponseProjection subProjection) { + fields.put("parent", subProjection); + return this; + } + + + @Override + public String toString() { + if (fields.isEmpty()) { + return ""; + } + StringJoiner joiner = new StringJoiner(" ", "{ ", " }"); + for (Map.Entry property : fields.entrySet()) { + joiner.add(property.getKey()); + if (property.getValue() != null) { + joiner.add(" ").add(property.getValue().toString()); + } + } + return joiner.toString(); + } +} \ No newline at end of file diff --git a/src/test/resources/expected-classes/request/EventResponseProjection.java.txt b/src/test/resources/expected-classes/request/EventResponseProjection.java.txt new file mode 100644 index 00000000..5ede25c0 --- /dev/null +++ b/src/test/resources/expected-classes/request/EventResponseProjection.java.txt @@ -0,0 +1,67 @@ +package com.github.graphql; + +import java.util.*; + +public class EventResponseProjection implements com.kobylynskyi.graphql.codegen.model.request.GraphQLResponseProjection { + + private Map fields = new LinkedHashMap<>(); + + public EventResponseProjection() { + } + + public EventResponseProjection id() { + fields.put("id", null); + return this; + } + + public EventResponseProjection categoryId() { + fields.put("categoryId", null); + return this; + } + + public EventResponseProjection properties(EventPropertyResponseProjection subProjection) { + fields.put("properties", subProjection); + return this; + } + + public EventResponseProjection status() { + fields.put("status", null); + return this; + } + + public EventResponseProjection createdBy() { + fields.put("createdBy", null); + return this; + } + + public EventResponseProjection createdDateTime() { + fields.put("createdDateTime", null); + return this; + } + + public EventResponseProjection active() { + fields.put("active", null); + return this; + } + + public EventResponseProjection rating() { + fields.put("rating", null); + return this; + } + + + @Override + public String toString() { + if (fields.isEmpty()) { + return ""; + } + StringJoiner joiner = new StringJoiner(" ", "{ ", " }"); + for (Map.Entry property : fields.entrySet()) { + joiner.add(property.getKey()); + if (property.getValue() != null) { + joiner.add(" ").add(property.getValue().toString()); + } + } + return joiner.toString(); + } +} \ No newline at end of file diff --git a/src/test/resources/expected-classes/request/EventStatusTO.java.txt b/src/test/resources/expected-classes/request/EventStatusTO.java.txt new file mode 100644 index 00000000..43fd3166 --- /dev/null +++ b/src/test/resources/expected-classes/request/EventStatusTO.java.txt @@ -0,0 +1,9 @@ +package com.github.graphql; + +public enum EventStatusTO { + + OPEN, + IN_PROGRESS, + LOGGED + +} \ No newline at end of file diff --git a/src/test/resources/expected-classes/request/EventsByCategoryAndStatusQueryRequest.java.txt b/src/test/resources/expected-classes/request/EventsByCategoryAndStatusQueryRequest.java.txt new file mode 100644 index 00000000..58d54352 --- /dev/null +++ b/src/test/resources/expected-classes/request/EventsByCategoryAndStatusQueryRequest.java.txt @@ -0,0 +1,43 @@ +package com.github.graphql; + +import java.util.*; +import graphql.language.*; + +public class EventsByCategoryAndStatusQueryRequest implements com.kobylynskyi.graphql.codegen.model.request.GraphQLOperationRequest { + + private static final OperationDefinition.Operation OPERATION_TYPE = OperationDefinition.Operation.QUERY; + private static final String OPERATION_NAME = "eventsByCategoryAndStatus"; + + private Map input = new LinkedHashMap<>(); + + public EventsByCategoryAndStatusQueryRequest() { + } + + public void setCategoryId(String categoryId) { + this.input.put("categoryId", categoryId); + } + + public void setStatus(EventStatus status) { + this.input.put("status", status); + } + + @Override + public OperationDefinition.Operation getOperationType() { + return OPERATION_TYPE; + } + + @Override + public String getOperationName() { + return OPERATION_NAME; + } + + @Override + public Map getInput() { + return input; + } + + @Override + public String toString() { + return Objects.toString(input); + } +} \ No newline at end of file diff --git a/src/test/resources/expected-classes/request/EventsByCategoryAndStatusQueryRequest_withModelSuffix.java.txt b/src/test/resources/expected-classes/request/EventsByCategoryAndStatusQueryRequest_withModelSuffix.java.txt new file mode 100644 index 00000000..d9a5f6b9 --- /dev/null +++ b/src/test/resources/expected-classes/request/EventsByCategoryAndStatusQueryRequest_withModelSuffix.java.txt @@ -0,0 +1,43 @@ +package com.github.graphql; + +import java.util.*; +import graphql.language.*; + +public class EventsByCategoryAndStatusQueryRequest implements com.kobylynskyi.graphql.codegen.model.request.GraphQLOperationRequest { + + private static final OperationDefinition.Operation OPERATION_TYPE = OperationDefinition.Operation.QUERY; + private static final String OPERATION_NAME = "eventsByCategoryAndStatus"; + + private Map input = new LinkedHashMap<>(); + + public EventsByCategoryAndStatusQueryRequest() { + } + + public void setCategoryId(String categoryId) { + this.input.put("categoryId", categoryId); + } + + public void setStatus(EventStatusTO status) { + this.input.put("status", status); + } + + @Override + public OperationDefinition.Operation getOperationType() { + return OPERATION_TYPE; + } + + @Override + public String getOperationName() { + return OPERATION_NAME; + } + + @Override + public Map getInput() { + return input; + } + + @Override + public String toString() { + return Objects.toString(input); + } +} \ No newline at end of file diff --git a/src/test/resources/expected-classes/request/EventsByIdsQueryRequest.java.txt b/src/test/resources/expected-classes/request/EventsByIdsQueryRequest.java.txt new file mode 100644 index 00000000..a4156b71 --- /dev/null +++ b/src/test/resources/expected-classes/request/EventsByIdsQueryRequest.java.txt @@ -0,0 +1,39 @@ +package com.github.graphql; + +import java.util.*; +import graphql.language.*; + +public class EventsByIdsQueryRequest implements com.kobylynskyi.graphql.codegen.model.request.GraphQLOperationRequest { + + private static final OperationDefinition.Operation OPERATION_TYPE = OperationDefinition.Operation.QUERY; + private static final String OPERATION_NAME = "eventsByIds"; + + private Map input = new LinkedHashMap<>(); + + public EventsByIdsQueryRequest() { + } + + public void setIds(Collection ids) { + this.input.put("ids", ids); + } + + @Override + public OperationDefinition.Operation getOperationType() { + return OPERATION_TYPE; + } + + @Override + public String getOperationName() { + return OPERATION_NAME; + } + + @Override + public Map getInput() { + return input; + } + + @Override + public String toString() { + return Objects.toString(input); + } +} \ No newline at end of file diff --git a/src/test/resources/expected-classes/request/UpdateRepositoryMutationRequest.java.txt b/src/test/resources/expected-classes/request/UpdateRepositoryMutationRequest.java.txt new file mode 100644 index 00000000..3a0b7e68 --- /dev/null +++ b/src/test/resources/expected-classes/request/UpdateRepositoryMutationRequest.java.txt @@ -0,0 +1,39 @@ +package com.github.graphql; + +import java.util.*; +import graphql.language.*; + +public class UpdateRepositoryMutationRequest implements com.kobylynskyi.graphql.codegen.model.request.GraphQLOperationRequest { + + private static final OperationDefinition.Operation OPERATION_TYPE = OperationDefinition.Operation.MUTATION; + private static final String OPERATION_NAME = "updateRepository"; + + private Map input = new LinkedHashMap<>(); + + public UpdateRepositoryMutationRequest() { + } + + public void setInput(UpdateRepositoryInput input) { + this.input.put("input", input); + } + + @Override + public OperationDefinition.Operation getOperationType() { + return OPERATION_TYPE; + } + + @Override + public String getOperationName() { + return OPERATION_NAME; + } + + @Override + public Map getInput() { + return input; + } + + @Override + public String toString() { + return Objects.toString(input); + } +} \ No newline at end of file diff --git a/src/test/resources/expected-classes/request/VersionQueryRequest.java.txt b/src/test/resources/expected-classes/request/VersionQueryRequest.java.txt new file mode 100644 index 00000000..9199d299 --- /dev/null +++ b/src/test/resources/expected-classes/request/VersionQueryRequest.java.txt @@ -0,0 +1,35 @@ +package com.github.graphql; + +import java.util.*; +import graphql.language.*; + +public class VersionQueryRequest implements com.kobylynskyi.graphql.codegen.model.request.GraphQLOperationRequest { + + private static final OperationDefinition.Operation OPERATION_TYPE = OperationDefinition.Operation.QUERY; + private static final String OPERATION_NAME = "version"; + + private Map input = new LinkedHashMap<>(); + + public VersionQueryRequest() { + } + + @Override + public OperationDefinition.Operation getOperationType() { + return OPERATION_TYPE; + } + + @Override + public String getOperationName() { + return OPERATION_NAME; + } + + @Override + public Map getInput() { + return input; + } + + @Override + public String toString() { + return Objects.toString(input); + } +} \ No newline at end of file diff --git a/src/test/resources/expected-classes/request/graphql-query/eventsByCategoryAndStatusQuery.txt b/src/test/resources/expected-classes/request/graphql-query/eventsByCategoryAndStatusQuery.txt new file mode 100644 index 00000000..a762b09e --- /dev/null +++ b/src/test/resources/expected-classes/request/graphql-query/eventsByCategoryAndStatusQuery.txt @@ -0,0 +1,17 @@ +query { + eventsByCategoryAndStatus(categoryId: "categoryIdValue1", status: OPEN){ + id + active + properties { + floatVal + child { + intVal + parent { + id + } + } + booleanVal + } + status + } +} \ No newline at end of file diff --git a/src/test/resources/expected-classes/request/graphql-query/eventsByIdsQuery.txt b/src/test/resources/expected-classes/request/graphql-query/eventsByIdsQuery.txt new file mode 100644 index 00000000..52e54786 --- /dev/null +++ b/src/test/resources/expected-classes/request/graphql-query/eventsByIdsQuery.txt @@ -0,0 +1,5 @@ +query { + eventsByIds(ids: [ "4", "5", "6" ]){ + id + } +} \ No newline at end of file diff --git a/src/test/resources/expected-classes/request/graphql-query/updateIssueMutation.txt b/src/test/resources/expected-classes/request/graphql-query/updateIssueMutation.txt new file mode 100644 index 00000000..0af13e5a --- /dev/null +++ b/src/test/resources/expected-classes/request/graphql-query/updateIssueMutation.txt @@ -0,0 +1,16 @@ +mutation { + updateIssue(input: { + floatVal: 1.23, + booleanVal: false, + intVal: 42, + stringVal: "my-default", + enumVal: OPEN, + intList: [1, 2, 3], + intListEmptyDefault: [] + }){ + clientMutationId + issue { + activeLockReason + } + } +} diff --git a/src/test/resources/expected-classes/request/graphql-query/versionQuery.txt b/src/test/resources/expected-classes/request/graphql-query/versionQuery.txt new file mode 100644 index 00000000..3769467a --- /dev/null +++ b/src/test/resources/expected-classes/request/graphql-query/versionQuery.txt @@ -0,0 +1 @@ +query { version } \ No newline at end of file diff --git a/src/test/resources/schemas/test.graphqls b/src/test/resources/schemas/test.graphqls index 198b6467..6912cba3 100644 --- a/src/test/resources/schemas/test.graphqls +++ b/src/test/resources/schemas/test.graphqls @@ -19,6 +19,9 @@ type Query { # Single event by ID. eventById(id: ID!): Event! + + # Events by IDs. + eventsByIds(ids: [ID!]!): [Event!]! } type Mutation { -- GitLab