diff --git a/docs/codegen-options.md b/docs/codegen-options.md index ebae251232dbdb4c5fc044105ad7cd77d66dee57..bc43da2a2e632d0c94942d891c1ad7b01b54f37c 100644 --- a/docs/codegen-options.md +++ b/docs/codegen-options.md @@ -60,6 +60,9 @@ See [DirectiveAnnotationsMapping](#option-directiveannotationsmapping)* | | `generateSealedInterfaces` | Boolean | false | This applies to generated interfaces on unions and interfaces. If true, generate sealed interfaces, else generate normal ones. It is only supported in Kotlin. | | `typesAsInterfaces` | Set(String) | Empty | Types that must generated as interfaces should be defined here in format: `TypeName` or `@directive`. E.g.: `User`, `@asInterface`. | | `useObjectMapperForRequestSerialization` | Set(String) | Empty | Fields that require serialization using `com.fasterxml.jackson.databind.ObjectMapper#writeValueAsString(Object)`. Values should be defined here in the following format: `GraphqlObjectName.fieldName` or `GraphqlTypeName`. If just type is specified, then all fields of this type will be serialized using ObjectMapper. E.g.: `["Person.createdDateTime", ZonedDateTime"]` | +| `supportUnknownFields` | Boolean | false | Specifies whether api classes should support unknown fields during serialization or deserialization. If `true`, classes will include a property of type [`java.util.Map`](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/Map.html) that will store unknown fields.| +| `unknownFieldsPropertyName` | String | userDefinedFields | Specifies the name of the property to be included in api classes to support unknown fields during serialization or deserialization| + ### Option `graphqlSchemas` diff --git a/plugins/gradle/example-client-kotlin/build.gradle b/plugins/gradle/example-client-kotlin/build.gradle index 4a65fc205efb57f85452c942df3350d27bcefe62..8fb52084ebb3992e27a47caf683cba2cd691f369 100644 --- a/plugins/gradle/example-client-kotlin/build.gradle +++ b/plugins/gradle/example-client-kotlin/build.gradle @@ -65,4 +65,5 @@ task graphqlCodegenKotlinService(type: GraphQLCodegenGradleTask) { " com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = DroidTO::class, name = \"Droid\")))"], ] modelNameSuffix = "TO" + supportUnknownFields = true } \ No newline at end of file diff --git a/plugins/gradle/example-server/build.gradle b/plugins/gradle/example-server/build.gradle index 2479e6e6d92634ecf6ee0683cc5a756f28564569..457d0cd3f2f6cad08adff1b174cc1742f49fc3c7 100644 --- a/plugins/gradle/example-server/build.gradle +++ b/plugins/gradle/example-server/build.gradle @@ -48,6 +48,9 @@ graphqlCodegen { } modelNameSuffix = "TO" generateApis = true + supportUnknownFields = true + unknownFieldsPropertyName = "additionalFields" + } repositories { diff --git a/plugins/gradle/example-server/src/main/java/io/github/kobylynskyi/product/Application.java b/plugins/gradle/example-server/src/main/java/io/github/kobylynskyi/product/Application.java index 932bb676d7f54a2d1a8fa0132663996565655d2a..8f8ec98e098ad82883b6e64bbb97752e726ef48d 100644 --- a/plugins/gradle/example-server/src/main/java/io/github/kobylynskyi/product/Application.java +++ b/plugins/gradle/example-server/src/main/java/io/github/kobylynskyi/product/Application.java @@ -6,6 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { + public static void main(String[] args) { SpringApplication.run(Application.class, args); } diff --git a/plugins/gradle/graphql-java-codegen-gradle-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/gradle/GraphQLCodegenGradleTask.java b/plugins/gradle/graphql-java-codegen-gradle-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/gradle/GraphQLCodegenGradleTask.java index 14c1ca20252f51a5702a8a696deb4646a344448e..4b032aec078780e829a34ea4ea06d7e2ec5eabdc 100644 --- a/plugins/gradle/graphql-java-codegen-gradle-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/gradle/GraphQLCodegenGradleTask.java +++ b/plugins/gradle/graphql-java-codegen-gradle-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/gradle/GraphQLCodegenGradleTask.java @@ -106,6 +106,9 @@ public class GraphQLCodegenGradleTask extends DefaultTask implements GraphQLCode private Boolean initializeNullableTypes = MappingConfigConstants.DEFAULT_INITIALIZE_NULLABLE_TYPES; private Boolean generateSealedInterfaces = MappingConfigConstants.DEFAULT_GENERATE_SEALED_INTERFACES; + private Boolean supportUnknownFields = MappingConfigConstants.DEFAULT_SUPPORT_UNKNOWN_FIELDS; + private String unknownFieldsPropertyName = MappingConfigConstants.DEFAULT_UNKNOWN_FIELDS_PROPERTY_NAME; + public GraphQLCodegenGradleTask() { setGroup("codegen"); setDescription("Generates Java POJOs and interfaces based on GraphQL schemas"); @@ -182,6 +185,9 @@ public class GraphQLCodegenGradleTask extends DefaultTask implements GraphQLCode mappingConfig.setGenerateModelOpenClasses(generateModelOpenClasses); mappingConfig.setInitializeNullableTypes(initializeNullableTypes); + mappingConfig.setSupportUnknownFields(isSupportUnknownFields()); + mappingConfig.setUnknownFieldsPropertyName(getUnknownFieldsPropertyName()); + instantiateCodegen(mappingConfig).generate(); } @@ -869,4 +875,28 @@ public class GraphQLCodegenGradleTask extends DefaultTask implements GraphQLCode public void setGenerateSealedInterfaces(Boolean generateSealedInterfaces) { this.generateSealedInterfaces = generateSealedInterfaces; } + + @Input + @Optional + @Override + public Boolean isSupportUnknownFields() { + return supportUnknownFields; + } + + public void setSupportUnknownFields(boolean supportUnknownFields) { + this.supportUnknownFields = supportUnknownFields; + } + + @Input + @Optional + @Override + public String getUnknownFieldsPropertyName() { + return unknownFieldsPropertyName; + } + + public void setUnknownFieldsPropertyName(String unknownFieldsPropertyName) { + this.unknownFieldsPropertyName = unknownFieldsPropertyName; + } + + } diff --git a/plugins/maven/graphql-java-codegen-maven-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/GraphQLCodegenMojo.java b/plugins/maven/graphql-java-codegen-maven-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/GraphQLCodegenMojo.java index 5dcc13521b476c5c80f7f2d015877d74fa9e1590..e2982fc29dbabfb0bba2786ff9082214da4abb45 100644 --- a/plugins/maven/graphql-java-codegen-maven-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/GraphQLCodegenMojo.java +++ b/plugins/maven/graphql-java-codegen-maven-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/GraphQLCodegenMojo.java @@ -200,6 +200,12 @@ public class GraphQLCodegenMojo extends AbstractMojo implements GraphQLCodegenCo @Parameter private String[] configurationFiles; + @Parameter(defaultValue = MappingConfigConstants.DEFAULT_SUPPORT_UNKNOWN_FIELDS_STRING) + private boolean supportUnknownFields; + + @Parameter(defaultValue = MappingConfigConstants.DEFAULT_UNKNOWN_FIELDS_PROPERTY_NAME) + private String unknownFieldsPropertyName; + /** * The project being built. */ @@ -276,6 +282,9 @@ public class GraphQLCodegenMojo extends AbstractMojo implements GraphQLCodegenCo mappingConfig.setInitializeNullableTypes(isInitializeNullableTypes()); mappingConfig.setGenerateSealedInterfaces(isGenerateSealedInterfaces()); + mappingConfig.setSupportUnknownFields(isSupportUnknownFields()); + mappingConfig.setUnknownFieldsPropertyName(getUnknownFieldsPropertyName()); + try { instantiateCodegen(mappingConfig).generate(); } catch (Exception e) { @@ -629,6 +638,24 @@ public class GraphQLCodegenMojo extends AbstractMojo implements GraphQLCodegenCo return configurationFiles; } + @Override + public Boolean isSupportUnknownFields() { + return supportUnknownFields; + } + + public void setSupportUnknownFields(boolean supportUnknownFields) { + this.supportUnknownFields = supportUnknownFields; + } + + @Override + public String getUnknownFieldsPropertyName() { + return unknownFieldsPropertyName; + } + + public void setUnknownFieldsPropertyName(String unknownFieldsPropertyName) { + this.unknownFieldsPropertyName = unknownFieldsPropertyName; + } + private static Map> convertToListsMap(Map sourceMap) { if (sourceMap == null) { return new HashMap<>(); diff --git a/plugins/sbt/graphql-java-codegen-sbt-plugin/src/main/scala/io/github/dreamylost/graphql/codegen/GraphQLCodegenKeys.scala b/plugins/sbt/graphql-java-codegen-sbt-plugin/src/main/scala/io/github/dreamylost/graphql/codegen/GraphQLCodegenKeys.scala index 945702c058372acd9d8e2e3566097ab1fddfe17e..a62a58f6daf4ce9a96df82e6359ebdc7f535b5ac 100644 --- a/plugins/sbt/graphql-java-codegen-sbt-plugin/src/main/scala/io/github/dreamylost/graphql/codegen/GraphQLCodegenKeys.scala +++ b/plugins/sbt/graphql-java-codegen-sbt-plugin/src/main/scala/io/github/dreamylost/graphql/codegen/GraphQLCodegenKeys.scala @@ -135,4 +135,7 @@ trait GraphQLCodegenKeys { //some others for sbt val generateCodegenTargetPath = settingKey[File]("Where to store generated files and add the generated code to the classpath, so that they can be referenced.") + val supportUnknownFields = settingKey[Boolean]("supportUnknownFields") + val unknownFieldsPropertyName = settingKey[String]("unknownFieldsPropertyName") + } diff --git a/plugins/sbt/graphql-java-codegen-sbt-plugin/src/main/scala/io/github/dreamylost/graphql/codegen/GraphQLCodegenPlugin.scala b/plugins/sbt/graphql-java-codegen-sbt-plugin/src/main/scala/io/github/dreamylost/graphql/codegen/GraphQLCodegenPlugin.scala index 2a252c0e38376b4bf1d8fadd71de4275a6db2640..1e2833fa406b58cd61d5ce897367bb6f5b449533 100644 --- a/plugins/sbt/graphql-java-codegen-sbt-plugin/src/main/scala/io/github/dreamylost/graphql/codegen/GraphQLCodegenPlugin.scala +++ b/plugins/sbt/graphql-java-codegen-sbt-plugin/src/main/scala/io/github/dreamylost/graphql/codegen/GraphQLCodegenPlugin.scala @@ -117,7 +117,10 @@ class GraphQLCodegenPlugin(configuration: Configuration, private[codegen] val co // parent interfaces configs: parentInterfaces := parentInterfacesConfig, generateAllMethodInProjection := MappingConfigConstants.DEFAULT_GENERATE_ALL_METHOD, - responseProjectionMaxDepth := MappingConfigConstants.DEFAULT_RESPONSE_PROJECTION_MAX_DEPTH + responseProjectionMaxDepth := MappingConfigConstants.DEFAULT_RESPONSE_PROJECTION_MAX_DEPTH, + + supportUnknownFields := MappingConfigConstants.DEFAULT_SUPPORT_UNKNOWN_FIELDS, + unknownFieldsPropertyName := MappingConfigConstants.DEFAULT_UNKNOWN_FIELDS_PROPERTY_NAME ) private def getMappingConfig(): Def.Initialize[MappingConfig] = Def.setting { @@ -172,6 +175,10 @@ class GraphQLCodegenPlugin(configuration: Configuration, private[codegen] val co mappingConfig.setGeneratedLanguage((generatedLanguage in GraphQLCodegenConfig).value) mappingConfig.setGenerateModelOpenClasses((generateModelOpenClasses in GraphQLCodegenConfig).value) mappingConfig.setGenerateJacksonTypeIdResolver((generateJacksonTypeIdResolver in GraphQLCodegenConfig).value); + + mappingConfig.setSupportUnknownFields((supportUnknownFields in GraphQLCodegenConfig).value) + mappingConfig.setUnknownFieldsPropertyName((unknownFieldsPropertyName in GraphQLCodegenConfig).value) + mappingConfig } diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/InputDefinitionToDataModelMapper.java b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/InputDefinitionToDataModelMapper.java index f587095cb7c04cba8b97a8f9fce5458dbd98253c..ffe6f5819026533dc0af14ea0b7f44aef0244588 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/InputDefinitionToDataModelMapper.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/InputDefinitionToDataModelMapper.java @@ -1,14 +1,15 @@ package com.kobylynskyi.graphql.codegen.mapper; import com.kobylynskyi.graphql.codegen.model.MappingContext; +import com.kobylynskyi.graphql.codegen.model.ParameterDefinition; import com.kobylynskyi.graphql.codegen.model.builders.JavaDocBuilder; import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedInputObjectTypeDefinition; import java.util.HashMap; +import java.util.List; import java.util.Map; import static com.kobylynskyi.graphql.codegen.model.DataModelFields.ANNOTATIONS; -import static com.kobylynskyi.graphql.codegen.model.DataModelFields.INITIALIZE_NULLABLE_TYPES; import static com.kobylynskyi.graphql.codegen.model.DataModelFields.BUILDER; import static com.kobylynskyi.graphql.codegen.model.DataModelFields.CLASS_NAME; import static com.kobylynskyi.graphql.codegen.model.DataModelFields.ENUM_IMPORT_IT_SELF_IN_SCALA; @@ -18,18 +19,21 @@ import static com.kobylynskyi.graphql.codegen.model.DataModelFields.GENERATED_AN import static com.kobylynskyi.graphql.codegen.model.DataModelFields.GENERATED_INFO; import static com.kobylynskyi.graphql.codegen.model.DataModelFields.GENERATE_MODEL_OPEN_CLASSES; import static com.kobylynskyi.graphql.codegen.model.DataModelFields.IMMUTABLE_MODELS; +import static com.kobylynskyi.graphql.codegen.model.DataModelFields.INITIALIZE_NULLABLE_TYPES; import static com.kobylynskyi.graphql.codegen.model.DataModelFields.JAVA_DOC; import static com.kobylynskyi.graphql.codegen.model.DataModelFields.NAME; import static com.kobylynskyi.graphql.codegen.model.DataModelFields.PACKAGE; +import static com.kobylynskyi.graphql.codegen.model.DataModelFields.SUPPORT_UNKNOWN_FIELDS; import static com.kobylynskyi.graphql.codegen.model.DataModelFields.TO_STRING; import static com.kobylynskyi.graphql.codegen.model.DataModelFields.TO_STRING_FOR_REQUEST; +import static com.kobylynskyi.graphql.codegen.model.DataModelFields.UNKNOWN_FIELDS_PROPERTY_NAME; /** * Map input type definition to a Freemarker data model * * @author kobylynskyi */ -public class InputDefinitionToDataModelMapper { +public class InputDefinitionToDataModelMapper implements UnknownFieldsSupport { private final AnnotationsMapper annotationsMapper; private final DataModelMapper dataModelMapper; @@ -50,14 +54,17 @@ public class InputDefinitionToDataModelMapper { * @return Freemarker data model of the GraphQL type */ public Map map(MappingContext mappingContext, ExtendedInputObjectTypeDefinition definition) { + List fields = inputValueDefinitionToParameterMapper + .map(mappingContext, definition.getValueDefinitions(), definition.getName()); + createUnknownFields(mappingContext).ifPresent(fields::add); + Map dataModel = new HashMap<>(); // type/enum/input/interface/union classes do not require any imports dataModel.put(PACKAGE, DataModelMapper.getModelPackageName(mappingContext)); dataModel.put(CLASS_NAME, dataModelMapper.getModelClassNameWithPrefixAndSuffix(mappingContext, definition)); dataModel.put(JAVA_DOC, JavaDocBuilder.build(definition)); dataModel.put(NAME, definition.getName()); - dataModel.put(FIELDS, inputValueDefinitionToParameterMapper - .map(mappingContext, definition.getValueDefinitions(), definition.getName())); + dataModel.put(FIELDS, fields); dataModel.put(ANNOTATIONS, annotationsMapper.getAnnotations(mappingContext, definition)); dataModel.put(BUILDER, mappingContext.getGenerateBuilder()); dataModel.put(EQUALS_AND_HASH_CODE, mappingContext.getGenerateEqualsAndHashCode()); @@ -69,6 +76,8 @@ public class InputDefinitionToDataModelMapper { dataModel.put(ENUM_IMPORT_IT_SELF_IN_SCALA, mappingContext.getEnumImportItSelfInScala()); dataModel.put(GENERATE_MODEL_OPEN_CLASSES, mappingContext.isGenerateModelOpenClasses()); dataModel.put(INITIALIZE_NULLABLE_TYPES, mappingContext.isInitializeNullableTypes()); + dataModel.put(SUPPORT_UNKNOWN_FIELDS, mappingContext.isSupportUnknownFields()); + dataModel.put(UNKNOWN_FIELDS_PROPERTY_NAME, mappingContext.getUnknownFieldsPropertyName()); return dataModel; } 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 e91ded2a5f04d809e12548a58567ad71c76b0b98..8936d5f30b6f09638dc446dcf8a7e522d8541dfb 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/TypeDefinitionToDataModelMapper.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/TypeDefinitionToDataModelMapper.java @@ -33,15 +33,17 @@ import static com.kobylynskyi.graphql.codegen.model.DataModelFields.INITIALIZE_N import static com.kobylynskyi.graphql.codegen.model.DataModelFields.JAVA_DOC; import static com.kobylynskyi.graphql.codegen.model.DataModelFields.PACKAGE; import static com.kobylynskyi.graphql.codegen.model.DataModelFields.PARENT_INTERFACE_PROPERTIES; +import static com.kobylynskyi.graphql.codegen.model.DataModelFields.SUPPORT_UNKNOWN_FIELDS; import static com.kobylynskyi.graphql.codegen.model.DataModelFields.TO_STRING; import static com.kobylynskyi.graphql.codegen.model.DataModelFields.TO_STRING_FOR_REQUEST; +import static com.kobylynskyi.graphql.codegen.model.DataModelFields.UNKNOWN_FIELDS_PROPERTY_NAME; /** * Map type definition to a Freemarker data model * * @author kobylynskyi */ -public class TypeDefinitionToDataModelMapper { +public class TypeDefinitionToDataModelMapper implements UnknownFieldsSupport { private final GraphQLTypeMapper graphQLTypeMapper; private final AnnotationsMapper annotationsMapper; @@ -106,6 +108,8 @@ public class TypeDefinitionToDataModelMapper { dataModel.put(GENERATE_MODEL_OPEN_CLASSES, mappingContext.isGenerateModelOpenClasses()); dataModel.put(INITIALIZE_NULLABLE_TYPES, mappingContext.isInitializeNullableTypes()); dataModel.put(GENERATE_SEALED_INTERFACES, mappingContext.isGenerateSealedInterfaces()); + dataModel.put(SUPPORT_UNKNOWN_FIELDS, mappingContext.isSupportUnknownFields()); + dataModel.put(UNKNOWN_FIELDS_PROPERTY_NAME, mappingContext.getUnknownFieldsPropertyName()); return dataModel; } @@ -132,6 +136,12 @@ public class TypeDefinitionToDataModelMapper { .flatMap(Collection::stream) .forEach(paramDef -> allParameters .merge(paramDef.getName(), paramDef, TypeDefinitionToDataModelMapper::merge)); + + + createUnknownFields(mappingContext).ifPresent( + unknownFields -> allParameters.put(mappingContext.getUnknownFieldsPropertyName(), unknownFields) + ); + return allParameters.values(); } diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/mapper/UnknownFieldsSupport.java b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/UnknownFieldsSupport.java new file mode 100644 index 0000000000000000000000000000000000000000..9618ec645eb332827fc101ecca9d42ebbf466487 --- /dev/null +++ b/src/main/java/com/kobylynskyi/graphql/codegen/mapper/UnknownFieldsSupport.java @@ -0,0 +1,40 @@ +package com.kobylynskyi.graphql.codegen.mapper; + +import com.kobylynskyi.graphql.codegen.model.MappingContext; +import com.kobylynskyi.graphql.codegen.model.ParameterDefinition; + +import java.util.Arrays; +import java.util.Optional; + +/** + * Utility interface that provides convenience methods to handle unknown fields during the marshaling + * and unmarshalling of a JSON document + * + * @author aldib + */ +public interface UnknownFieldsSupport { + + /** + * Creates an instance of {@link ParameterDefinition} that can be used to generate + * a field of type {@link java.util.Map} to store unknown fields during the marshaling + * and unmarshalling of a JSON document + * + * @param mappingContext The context of the mapping process. + * @return If {@link MappingContext#isSupportUnknownFields()} is true, it returns a monad containing + * the instance of {@link ParameterDefinition}. {@link Optional#empty()} otherwise. + */ + default Optional createUnknownFields(MappingContext mappingContext) { + if (mappingContext.isSupportUnknownFields()) { + ParameterDefinition unknownFields = new ParameterDefinition(); + unknownFields.setName(mappingContext.getUnknownFieldsPropertyName()); + unknownFields.setOriginalName(mappingContext.getUnknownFieldsPropertyName()); + unknownFields.setType("java.util.Map"); + unknownFields.setAnnotations(Arrays.asList( + "com.fasterxml.jackson.annotation.JsonAnyGetter", + "com.fasterxml.jackson.annotation.JsonAnySetter" + )); + return Optional.of(unknownFields); + } + return Optional.empty(); + } +} 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 ec234d2c639ba134a1212dfa0475d3d7506c72a4..bfe2f95f934dddf4ad93c2bbb8114c327a6921a6 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/model/DataModelFields.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/model/DataModelFields.java @@ -38,6 +38,8 @@ public final class DataModelFields { public static final String GENERATE_MODEL_OPEN_CLASSES = "generateModelOpenClasses"; public static final String INITIALIZE_NULLABLE_TYPES = "initializeNullableTypes"; public static final String GENERATE_SEALED_INTERFACES = "generateSealedInterfaces"; + public static final String SUPPORT_UNKNOWN_FIELDS = "supportUnknownFields"; + public static final String UNKNOWN_FIELDS_PROPERTY_NAME = "unknownFieldsPropertyName"; private DataModelFields() { } diff --git a/src/main/java/com/kobylynskyi/graphql/codegen/model/GraphQLCodegenConfiguration.java b/src/main/java/com/kobylynskyi/graphql/codegen/model/GraphQLCodegenConfiguration.java index 81ca1ca1e02bffc8e2aa73fb820f9e2406ddb9e8..efc0c4a5f1da7d5d83b18dd4dd361f7041224235 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/model/GraphQLCodegenConfiguration.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/model/GraphQLCodegenConfiguration.java @@ -461,4 +461,22 @@ public interface GraphQLCodegenConfiguration { */ Boolean isGenerateSealedInterfaces(); + /* + * Specifies whether api classes should support unknown fields during serialization or deserialization. + * + * @return true classes will include a property of type {@link java.util.Map} that will store unknown fields. + * @see com.fasterxml.jackson.annotation.JsonAnyGetter + * @see com.fasterxml.jackson.annotation.JsonAnySetter + */ + Boolean isSupportUnknownFields(); + + /** + * Specifies the name of the property to be included in api classes to support unknown + * fields during serialization or deserialization + * + * @return The name of the property to store unknown fields + */ + String getUnknownFieldsPropertyName(); + + } 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 f8c8d3a9b96d91fba57b331c6ee4af2c00e60270..128901e5267a8118a1d26f2b7a3c1ec232d57fbd 100644 --- a/src/main/java/com/kobylynskyi/graphql/codegen/model/MappingConfig.java +++ b/src/main/java/com/kobylynskyi/graphql/codegen/model/MappingConfig.java @@ -39,6 +39,7 @@ public class MappingConfig implements GraphQLCodegenConfiguration, Combinable fieldsWithResolvers = new HashSet<>(); @@ -191,6 +193,10 @@ public class MappingConfig implements GraphQLCodegenConfiguration, Combinable T getValueOrDefaultToThis(MappingConfig source, Function getValueFunction) { @@ -649,6 +655,24 @@ public class MappingConfig implements GraphQLCodegenConfiguration, Combinable generatedFileNames = Arrays.stream(files).map(File::getName).sorted().collect(toList()); + assertEquals(asList("InputWithDefaults.java", "MyEnum.java", "SomeObject.java"), generatedFileNames); + + for (File file : files) { + assertSameTrimmedContent(new File(String.format("src/test/resources/expected-classes/unknown-fields/%s.txt", + file.getName())), + file); + } + } + @Test void generate_CheckFiles_WithPrefixSuffix() throws Exception { mappingConfig.setModelNameSuffix("TO"); diff --git a/src/test/resources/expected-classes/unknown-fields/InputWithDefaults.java.txt b/src/test/resources/expected-classes/unknown-fields/InputWithDefaults.java.txt new file mode 100644 index 0000000000000000000000000000000000000000..0629ea8a01458ee8aaca2ded5fa77b1a6b13cbb2 --- /dev/null +++ b/src/test/resources/expected-classes/unknown-fields/InputWithDefaults.java.txt @@ -0,0 +1,224 @@ +package com.kobylynskyi.graphql.testdefaults; + + +/** + * This input has all possible types + */ +@javax.annotation.Generated( + value = "com.kobylynskyi.graphql.codegen.GraphQLCodegen", + date = "2020-12-31T23:59:59-0500" +) +public class InputWithDefaults implements java.io.Serializable { + + private static final long serialVersionUID = 1L; + + private Double floatVal = 1.23; + private Boolean booleanVal = false; + private Integer intVal = 42; + private String stringVal = "my-default"; + private MyEnum enumVal = MyEnum.ONE; + @javax.validation.constraints.NotNull + private MyEnum nonNullEnumVal = MyEnum.TWO; + private SomeObject objectWithNullDefault = null; + private SomeObject objectWithNonNullDefault; + private java.util.List intList = java.util.Arrays.asList(1, 2, 3); + private java.util.List intListEmptyDefault = java.util.Collections.emptyList(); + @javax.validation.constraints.NotNull + private java.util.List objectListEmptyDefault = java.util.Collections.emptyList(); + @com.fasterxml.jackson.annotation.JsonAnyGetter + @com.fasterxml.jackson.annotation.JsonAnySetter + private java.util.Map userDefinedFields; + + public InputWithDefaults() { + } + + public InputWithDefaults(Double floatVal, Boolean booleanVal, Integer intVal, String stringVal, MyEnum enumVal, MyEnum nonNullEnumVal, SomeObject objectWithNullDefault, SomeObject objectWithNonNullDefault, java.util.List intList, java.util.List intListEmptyDefault, java.util.List objectListEmptyDefault, java.util.Map userDefinedFields) { + this.floatVal = floatVal; + this.booleanVal = booleanVal; + this.intVal = intVal; + this.stringVal = stringVal; + this.enumVal = enumVal; + this.nonNullEnumVal = nonNullEnumVal; + this.objectWithNullDefault = objectWithNullDefault; + this.objectWithNonNullDefault = objectWithNonNullDefault; + this.intList = intList; + this.intListEmptyDefault = intListEmptyDefault; + this.objectListEmptyDefault = objectListEmptyDefault; + this.userDefinedFields = userDefinedFields; + } + + public Double getFloatVal() { + return floatVal; + } + public void setFloatVal(Double floatVal) { + this.floatVal = floatVal; + } + + public Boolean getBooleanVal() { + return booleanVal; + } + public void setBooleanVal(Boolean booleanVal) { + this.booleanVal = booleanVal; + } + + public Integer getIntVal() { + return intVal; + } + public void setIntVal(Integer intVal) { + this.intVal = intVal; + } + + public String getStringVal() { + return stringVal; + } + public void setStringVal(String stringVal) { + this.stringVal = stringVal; + } + + public MyEnum getEnumVal() { + return enumVal; + } + public void setEnumVal(MyEnum enumVal) { + this.enumVal = enumVal; + } + + public MyEnum getNonNullEnumVal() { + return nonNullEnumVal; + } + public void setNonNullEnumVal(MyEnum nonNullEnumVal) { + this.nonNullEnumVal = nonNullEnumVal; + } + + public SomeObject getObjectWithNullDefault() { + return objectWithNullDefault; + } + public void setObjectWithNullDefault(SomeObject objectWithNullDefault) { + this.objectWithNullDefault = objectWithNullDefault; + } + + public SomeObject getObjectWithNonNullDefault() { + return objectWithNonNullDefault; + } + public void setObjectWithNonNullDefault(SomeObject objectWithNonNullDefault) { + this.objectWithNonNullDefault = objectWithNonNullDefault; + } + + public java.util.List getIntList() { + return intList; + } + public void setIntList(java.util.List intList) { + this.intList = intList; + } + + public java.util.List getIntListEmptyDefault() { + return intListEmptyDefault; + } + public void setIntListEmptyDefault(java.util.List intListEmptyDefault) { + this.intListEmptyDefault = intListEmptyDefault; + } + + public java.util.List getObjectListEmptyDefault() { + return objectListEmptyDefault; + } + public void setObjectListEmptyDefault(java.util.List objectListEmptyDefault) { + this.objectListEmptyDefault = objectListEmptyDefault; + } + + public java.util.Map getUserDefinedFields() { + return userDefinedFields; + } + public void setUserDefinedFields(java.util.Map userDefinedFields) { + this.userDefinedFields = userDefinedFields; + } + + + + public static InputWithDefaults.Builder builder() { + return new InputWithDefaults.Builder(); + } + + public static class Builder { + + private Double floatVal = 1.23; + private Boolean booleanVal = false; + private Integer intVal = 42; + private String stringVal = "my-default"; + private MyEnum enumVal = MyEnum.ONE; + private MyEnum nonNullEnumVal = MyEnum.TWO; + private SomeObject objectWithNullDefault = null; + private SomeObject objectWithNonNullDefault; + private java.util.List intList = java.util.Arrays.asList(1, 2, 3); + private java.util.List intListEmptyDefault = java.util.Collections.emptyList(); + private java.util.List objectListEmptyDefault = java.util.Collections.emptyList(); + private java.util.Map userDefinedFields; + + public Builder() { + } + + public Builder setFloatVal(Double floatVal) { + this.floatVal = floatVal; + return this; + } + + public Builder setBooleanVal(Boolean booleanVal) { + this.booleanVal = booleanVal; + return this; + } + + public Builder setIntVal(Integer intVal) { + this.intVal = intVal; + return this; + } + + public Builder setStringVal(String stringVal) { + this.stringVal = stringVal; + return this; + } + + public Builder setEnumVal(MyEnum enumVal) { + this.enumVal = enumVal; + return this; + } + + public Builder setNonNullEnumVal(MyEnum nonNullEnumVal) { + this.nonNullEnumVal = nonNullEnumVal; + return this; + } + + public Builder setObjectWithNullDefault(SomeObject objectWithNullDefault) { + this.objectWithNullDefault = objectWithNullDefault; + return this; + } + + public Builder setObjectWithNonNullDefault(SomeObject objectWithNonNullDefault) { + this.objectWithNonNullDefault = objectWithNonNullDefault; + return this; + } + + public Builder setIntList(java.util.List intList) { + this.intList = intList; + return this; + } + + public Builder setIntListEmptyDefault(java.util.List intListEmptyDefault) { + this.intListEmptyDefault = intListEmptyDefault; + return this; + } + + public Builder setObjectListEmptyDefault(java.util.List objectListEmptyDefault) { + this.objectListEmptyDefault = objectListEmptyDefault; + return this; + } + + public Builder setUserDefinedFields(java.util.Map userDefinedFields) { + this.userDefinedFields = userDefinedFields; + return this; + } + + + public InputWithDefaults build() { + return new InputWithDefaults(floatVal, booleanVal, intVal, stringVal, enumVal, nonNullEnumVal, objectWithNullDefault, objectWithNonNullDefault, intList, intListEmptyDefault, objectListEmptyDefault, userDefinedFields); + } + + } +} \ No newline at end of file diff --git a/src/test/resources/expected-classes/unknown-fields/MyEnum.java.txt b/src/test/resources/expected-classes/unknown-fields/MyEnum.java.txt new file mode 100644 index 0000000000000000000000000000000000000000..489176b8f23fccb0f6e5676019a81f2f96361c85 --- /dev/null +++ b/src/test/resources/expected-classes/unknown-fields/MyEnum.java.txt @@ -0,0 +1,24 @@ +package com.kobylynskyi.graphql.testdefaults; + +@javax.annotation.Generated( + value = "com.kobylynskyi.graphql.codegen.GraphQLCodegen", + date = "2020-12-31T23:59:59-0500" +) +public enum MyEnum { + + ONE("ONE"), + TWO("TWO"), + THREE("THREE"); + + private final String graphqlName; + + private MyEnum(String graphqlName) { + this.graphqlName = graphqlName; + } + + @Override + public String toString() { + return this.graphqlName; + } + +} \ No newline at end of file diff --git a/src/test/resources/expected-classes/unknown-fields/SomeObject.java.txt b/src/test/resources/expected-classes/unknown-fields/SomeObject.java.txt new file mode 100644 index 0000000000000000000000000000000000000000..0b6ecfb6546a2da6dd0dda676338015d341de115 --- /dev/null +++ b/src/test/resources/expected-classes/unknown-fields/SomeObject.java.txt @@ -0,0 +1,70 @@ +package com.kobylynskyi.graphql.testdefaults; + + +@javax.annotation.Generated( + value = "com.kobylynskyi.graphql.codegen.GraphQLCodegen", + date = "2020-12-31T23:59:59-0500" +) +public class SomeObject implements java.io.Serializable { + + private static final long serialVersionUID = 1L; + + @javax.validation.constraints.NotNull + private String name; + @com.fasterxml.jackson.annotation.JsonAnyGetter + @com.fasterxml.jackson.annotation.JsonAnySetter + private java.util.Map userDefinedFields; + + public SomeObject() { + } + + public SomeObject(String name, java.util.Map userDefinedFields) { + this.name = name; + this.userDefinedFields = userDefinedFields; + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + public java.util.Map getUserDefinedFields() { + return userDefinedFields; + } + public void setUserDefinedFields(java.util.Map userDefinedFields) { + this.userDefinedFields = userDefinedFields; + } + + + + public static SomeObject.Builder builder() { + return new SomeObject.Builder(); + } + + public static class Builder { + + private String name; + private java.util.Map userDefinedFields; + + public Builder() { + } + + public Builder setName(String name) { + this.name = name; + return this; + } + + public Builder setUserDefinedFields(java.util.Map userDefinedFields) { + this.userDefinedFields = userDefinedFields; + return this; + } + + + public SomeObject build() { + return new SomeObject(name, userDefinedFields); + } + + } +} \ No newline at end of file