Skip to content

Commit d6f7d73

Browse files
authored
When using GraphQL federation, external parties can extend types and … (#866)
1 parent 323efd0 commit d6f7d73

File tree

21 files changed

+542
-6
lines changed

21 files changed

+542
-6
lines changed

docs/codegen-options.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ See [DirectiveAnnotationsMapping](#option-directiveannotationsmapping)* |
6060
| `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. |
6161
| `typesAsInterfaces` | Set(String) | Empty | Types that must generated as interfaces should be defined here in format: `TypeName` or `@directive`. E.g.: `User`, `@asInterface`. |
6262
| `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"]` |
63+
| `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<String,Object>`](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/Map.html) that will store unknown fields.|
64+
| `unknownFieldsPropertyName` | String | userDefinedFields | Specifies the name of the property to be included in api classes to support unknown fields during serialization or deserialization|
65+
6366

6467
### Option `graphqlSchemas`
6568

plugins/gradle/example-client-kotlin/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,5 @@ task graphqlCodegenKotlinService(type: GraphQLCodegenGradleTask) {
6565
" com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = DroidTO::class, name = \"Droid\")))"],
6666
]
6767
modelNameSuffix = "TO"
68+
supportUnknownFields = true
6869
}

plugins/gradle/example-server/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ graphqlCodegen {
4848
}
4949
modelNameSuffix = "TO"
5050
generateApis = true
51+
supportUnknownFields = true
52+
unknownFieldsPropertyName = "additionalFields"
53+
5154
}
5255

5356
repositories {

plugins/gradle/example-server/src/main/java/io/github/kobylynskyi/product/Application.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
@SpringBootApplication
77
public class Application {
88

9+
910
public static void main(String[] args) {
1011
SpringApplication.run(Application.class, args);
1112
}

plugins/gradle/graphql-java-codegen-gradle-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/gradle/GraphQLCodegenGradleTask.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ public class GraphQLCodegenGradleTask extends DefaultTask implements GraphQLCode
106106
private Boolean initializeNullableTypes = MappingConfigConstants.DEFAULT_INITIALIZE_NULLABLE_TYPES;
107107
private Boolean generateSealedInterfaces = MappingConfigConstants.DEFAULT_GENERATE_SEALED_INTERFACES;
108108

109+
private Boolean supportUnknownFields = MappingConfigConstants.DEFAULT_SUPPORT_UNKNOWN_FIELDS;
110+
private String unknownFieldsPropertyName = MappingConfigConstants.DEFAULT_UNKNOWN_FIELDS_PROPERTY_NAME;
111+
109112
public GraphQLCodegenGradleTask() {
110113
setGroup("codegen");
111114
setDescription("Generates Java POJOs and interfaces based on GraphQL schemas");
@@ -182,6 +185,9 @@ public void generate() throws Exception {
182185
mappingConfig.setGenerateModelOpenClasses(generateModelOpenClasses);
183186
mappingConfig.setInitializeNullableTypes(initializeNullableTypes);
184187

188+
mappingConfig.setSupportUnknownFields(isSupportUnknownFields());
189+
mappingConfig.setUnknownFieldsPropertyName(getUnknownFieldsPropertyName());
190+
185191
instantiateCodegen(mappingConfig).generate();
186192
}
187193

@@ -869,4 +875,28 @@ public Boolean isGenerateSealedInterfaces() {
869875
public void setGenerateSealedInterfaces(Boolean generateSealedInterfaces) {
870876
this.generateSealedInterfaces = generateSealedInterfaces;
871877
}
878+
879+
@Input
880+
@Optional
881+
@Override
882+
public Boolean isSupportUnknownFields() {
883+
return supportUnknownFields;
884+
}
885+
886+
public void setSupportUnknownFields(boolean supportUnknownFields) {
887+
this.supportUnknownFields = supportUnknownFields;
888+
}
889+
890+
@Input
891+
@Optional
892+
@Override
893+
public String getUnknownFieldsPropertyName() {
894+
return unknownFieldsPropertyName;
895+
}
896+
897+
public void setUnknownFieldsPropertyName(String unknownFieldsPropertyName) {
898+
this.unknownFieldsPropertyName = unknownFieldsPropertyName;
899+
}
900+
901+
872902
}

plugins/maven/graphql-java-codegen-maven-plugin/src/main/java/io/github/kobylynskyi/graphql/codegen/GraphQLCodegenMojo.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,12 @@ public class GraphQLCodegenMojo extends AbstractMojo implements GraphQLCodegenCo
200200
@Parameter
201201
private String[] configurationFiles;
202202

203+
@Parameter(defaultValue = MappingConfigConstants.DEFAULT_SUPPORT_UNKNOWN_FIELDS_STRING)
204+
private boolean supportUnknownFields;
205+
206+
@Parameter(defaultValue = MappingConfigConstants.DEFAULT_UNKNOWN_FIELDS_PROPERTY_NAME)
207+
private String unknownFieldsPropertyName;
208+
203209
/**
204210
* The project being built.
205211
*/
@@ -276,6 +282,9 @@ public void execute() throws MojoExecutionException {
276282
mappingConfig.setInitializeNullableTypes(isInitializeNullableTypes());
277283
mappingConfig.setGenerateSealedInterfaces(isGenerateSealedInterfaces());
278284

285+
mappingConfig.setSupportUnknownFields(isSupportUnknownFields());
286+
mappingConfig.setUnknownFieldsPropertyName(getUnknownFieldsPropertyName());
287+
279288
try {
280289
instantiateCodegen(mappingConfig).generate();
281290
} catch (Exception e) {
@@ -629,6 +638,24 @@ public String[] getConfigurationFiles() {
629638
return configurationFiles;
630639
}
631640

641+
@Override
642+
public Boolean isSupportUnknownFields() {
643+
return supportUnknownFields;
644+
}
645+
646+
public void setSupportUnknownFields(boolean supportUnknownFields) {
647+
this.supportUnknownFields = supportUnknownFields;
648+
}
649+
650+
@Override
651+
public String getUnknownFieldsPropertyName() {
652+
return unknownFieldsPropertyName;
653+
}
654+
655+
public void setUnknownFieldsPropertyName(String unknownFieldsPropertyName) {
656+
this.unknownFieldsPropertyName = unknownFieldsPropertyName;
657+
}
658+
632659
private static Map<String, List<String>> convertToListsMap(Map<String, Properties> sourceMap) {
633660
if (sourceMap == null) {
634661
return new HashMap<>();

plugins/sbt/graphql-java-codegen-sbt-plugin/src/main/scala/io/github/dreamylost/graphql/codegen/GraphQLCodegenKeys.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,4 +135,7 @@ trait GraphQLCodegenKeys {
135135
//some others for sbt
136136
val generateCodegenTargetPath = settingKey[File]("Where to store generated files and add the generated code to the classpath, so that they can be referenced.")
137137

138+
val supportUnknownFields = settingKey[Boolean]("supportUnknownFields")
139+
val unknownFieldsPropertyName = settingKey[String]("unknownFieldsPropertyName")
140+
138141
}

plugins/sbt/graphql-java-codegen-sbt-plugin/src/main/scala/io/github/dreamylost/graphql/codegen/GraphQLCodegenPlugin.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,10 @@ class GraphQLCodegenPlugin(configuration: Configuration, private[codegen] val co
117117
// parent interfaces configs:
118118
parentInterfaces := parentInterfacesConfig,
119119
generateAllMethodInProjection := MappingConfigConstants.DEFAULT_GENERATE_ALL_METHOD,
120-
responseProjectionMaxDepth := MappingConfigConstants.DEFAULT_RESPONSE_PROJECTION_MAX_DEPTH
120+
responseProjectionMaxDepth := MappingConfigConstants.DEFAULT_RESPONSE_PROJECTION_MAX_DEPTH,
121+
122+
supportUnknownFields := MappingConfigConstants.DEFAULT_SUPPORT_UNKNOWN_FIELDS,
123+
unknownFieldsPropertyName := MappingConfigConstants.DEFAULT_UNKNOWN_FIELDS_PROPERTY_NAME
121124
)
122125

123126
private def getMappingConfig(): Def.Initialize[MappingConfig] = Def.setting {
@@ -172,6 +175,10 @@ class GraphQLCodegenPlugin(configuration: Configuration, private[codegen] val co
172175
mappingConfig.setGeneratedLanguage((generatedLanguage in GraphQLCodegenConfig).value)
173176
mappingConfig.setGenerateModelOpenClasses((generateModelOpenClasses in GraphQLCodegenConfig).value)
174177
mappingConfig.setGenerateJacksonTypeIdResolver((generateJacksonTypeIdResolver in GraphQLCodegenConfig).value);
178+
179+
mappingConfig.setSupportUnknownFields((supportUnknownFields in GraphQLCodegenConfig).value)
180+
mappingConfig.setUnknownFieldsPropertyName((unknownFieldsPropertyName in GraphQLCodegenConfig).value)
181+
175182
mappingConfig
176183
}
177184

src/main/java/com/kobylynskyi/graphql/codegen/mapper/InputDefinitionToDataModelMapper.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
package com.kobylynskyi.graphql.codegen.mapper;
22

33
import com.kobylynskyi.graphql.codegen.model.MappingContext;
4+
import com.kobylynskyi.graphql.codegen.model.ParameterDefinition;
45
import com.kobylynskyi.graphql.codegen.model.builders.JavaDocBuilder;
56
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedInputObjectTypeDefinition;
67

78
import java.util.HashMap;
9+
import java.util.List;
810
import java.util.Map;
911

1012
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.ANNOTATIONS;
11-
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.INITIALIZE_NULLABLE_TYPES;
1213
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.BUILDER;
1314
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.CLASS_NAME;
1415
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.ENUM_IMPORT_IT_SELF_IN_SCALA;
@@ -18,18 +19,21 @@
1819
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.GENERATED_INFO;
1920
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.GENERATE_MODEL_OPEN_CLASSES;
2021
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.IMMUTABLE_MODELS;
22+
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.INITIALIZE_NULLABLE_TYPES;
2123
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.JAVA_DOC;
2224
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.NAME;
2325
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.PACKAGE;
26+
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.SUPPORT_UNKNOWN_FIELDS;
2427
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.TO_STRING;
2528
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.TO_STRING_FOR_REQUEST;
29+
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.UNKNOWN_FIELDS_PROPERTY_NAME;
2630

2731
/**
2832
* Map input type definition to a Freemarker data model
2933
*
3034
* @author kobylynskyi
3135
*/
32-
public class InputDefinitionToDataModelMapper {
36+
public class InputDefinitionToDataModelMapper implements UnknownFieldsSupport {
3337

3438
private final AnnotationsMapper annotationsMapper;
3539
private final DataModelMapper dataModelMapper;
@@ -50,14 +54,17 @@ public InputDefinitionToDataModelMapper(MapperFactory mapperFactory,
5054
* @return Freemarker data model of the GraphQL type
5155
*/
5256
public Map<String, Object> map(MappingContext mappingContext, ExtendedInputObjectTypeDefinition definition) {
57+
List<ParameterDefinition> fields = inputValueDefinitionToParameterMapper
58+
.map(mappingContext, definition.getValueDefinitions(), definition.getName());
59+
createUnknownFields(mappingContext).ifPresent(fields::add);
60+
5361
Map<String, Object> dataModel = new HashMap<>();
5462
// type/enum/input/interface/union classes do not require any imports
5563
dataModel.put(PACKAGE, DataModelMapper.getModelPackageName(mappingContext));
5664
dataModel.put(CLASS_NAME, dataModelMapper.getModelClassNameWithPrefixAndSuffix(mappingContext, definition));
5765
dataModel.put(JAVA_DOC, JavaDocBuilder.build(definition));
5866
dataModel.put(NAME, definition.getName());
59-
dataModel.put(FIELDS, inputValueDefinitionToParameterMapper
60-
.map(mappingContext, definition.getValueDefinitions(), definition.getName()));
67+
dataModel.put(FIELDS, fields);
6168
dataModel.put(ANNOTATIONS, annotationsMapper.getAnnotations(mappingContext, definition));
6269
dataModel.put(BUILDER, mappingContext.getGenerateBuilder());
6370
dataModel.put(EQUALS_AND_HASH_CODE, mappingContext.getGenerateEqualsAndHashCode());
@@ -69,6 +76,8 @@ public Map<String, Object> map(MappingContext mappingContext, ExtendedInputObjec
6976
dataModel.put(ENUM_IMPORT_IT_SELF_IN_SCALA, mappingContext.getEnumImportItSelfInScala());
7077
dataModel.put(GENERATE_MODEL_OPEN_CLASSES, mappingContext.isGenerateModelOpenClasses());
7178
dataModel.put(INITIALIZE_NULLABLE_TYPES, mappingContext.isInitializeNullableTypes());
79+
dataModel.put(SUPPORT_UNKNOWN_FIELDS, mappingContext.isSupportUnknownFields());
80+
dataModel.put(UNKNOWN_FIELDS_PROPERTY_NAME, mappingContext.getUnknownFieldsPropertyName());
7281
return dataModel;
7382
}
7483

src/main/java/com/kobylynskyi/graphql/codegen/mapper/TypeDefinitionToDataModelMapper.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,17 @@
3333
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.JAVA_DOC;
3434
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.PACKAGE;
3535
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.PARENT_INTERFACE_PROPERTIES;
36+
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.SUPPORT_UNKNOWN_FIELDS;
3637
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.TO_STRING;
3738
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.TO_STRING_FOR_REQUEST;
39+
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.UNKNOWN_FIELDS_PROPERTY_NAME;
3840

3941
/**
4042
* Map type definition to a Freemarker data model
4143
*
4244
* @author kobylynskyi
4345
*/
44-
public class TypeDefinitionToDataModelMapper {
46+
public class TypeDefinitionToDataModelMapper implements UnknownFieldsSupport {
4547

4648
private final GraphQLTypeMapper graphQLTypeMapper;
4749
private final AnnotationsMapper annotationsMapper;
@@ -106,6 +108,8 @@ public Map<String, Object> map(MappingContext mappingContext,
106108
dataModel.put(GENERATE_MODEL_OPEN_CLASSES, mappingContext.isGenerateModelOpenClasses());
107109
dataModel.put(INITIALIZE_NULLABLE_TYPES, mappingContext.isInitializeNullableTypes());
108110
dataModel.put(GENERATE_SEALED_INTERFACES, mappingContext.isGenerateSealedInterfaces());
111+
dataModel.put(SUPPORT_UNKNOWN_FIELDS, mappingContext.isSupportUnknownFields());
112+
dataModel.put(UNKNOWN_FIELDS_PROPERTY_NAME, mappingContext.getUnknownFieldsPropertyName());
109113
return dataModel;
110114
}
111115

@@ -132,6 +136,12 @@ private Collection<ParameterDefinition> getFields(MappingContext mappingContext,
132136
.flatMap(Collection::stream)
133137
.forEach(paramDef -> allParameters
134138
.merge(paramDef.getName(), paramDef, TypeDefinitionToDataModelMapper::merge));
139+
140+
141+
createUnknownFields(mappingContext).ifPresent(
142+
unknownFields -> allParameters.put(mappingContext.getUnknownFieldsPropertyName(), unknownFields)
143+
);
144+
135145
return allParameters.values();
136146
}
137147

0 commit comments

Comments
 (0)