Skip to content

Commit 3ac1021

Browse files
Support ArgumentValue wrapper for input parameters (#1450)
1 parent 6b41b5b commit 3ac1021

File tree

21 files changed

+607
-32
lines changed

21 files changed

+607
-32
lines changed

docs/codegen-options.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
| `generateExtensionFieldsResolvers` | Boolean | False | Specifies whether all fields in extensions (`extend type` and `extend interface`) should be present in Resolver interface instead of the type class itself. |
4545
| `generateModelsForRootTypes` | Boolean | False | Specifies whether model classes should be generated for `type Query`, `type Subscription`, `type Mutation`. |
4646
| `useOptionalForNullableReturnTypes` | Boolean | False | Specifies whether nullable return types of api methods should be wrapped into [`java.util.Optional<>`](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/Optional.html). Lists will not be wrapped. |
47+
| `useWrapperForNullableInputTypes` | Boolean | False | Specifies whether nullable parameters on input types should be wrapped into [`ArgumentValue<>`](https://docs.spring.io/spring-graphql/docs/current/api/org/springframework/graphql/data/ArgumentValue.html). This requires org.springframework.graphql.data version 1.1.0+. Lists will not be wrapped. |
4748
| `generateApisWithThrowsException` | Boolean | True | Specifies whether api interface methods should have `throws Exception` in signature. |
4849
| `generateApisWithSuspendFunctions` | Boolean | False | Specifies whether api interface methods should have `suspend` modifier in signature. Only supported in Kotlin. |
4950
| `generateNoArgsConstructorOnly` | Boolean | False | Specifies whether model classes should only have a no-args constructor. All-args constructor will not be generated in case value is <b>true</b> |
@@ -69,8 +70,7 @@
6970
| `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. |
7071
| `unknownFieldsPropertyName` | String | userDefinedFields | Specifies the name of the property to be included in api classes to support unknown fields during serialization or deserialization |
7172
| `skip` | Boolean | False | If true, then code generation will not happen |
72-
| `skipSchemaSizeLimit` | Boolean | True | When set to true, the GraphQL schema will be processed with token, character, line and rule depth limits. Set to false to process the schema regardless of its size.
73-
|
73+
| `skipSchemaSizeLimit` | Boolean | True | When set to true, the GraphQL schema will be processed with token, character, line and rule depth limits. Set to false to process the schema regardless of its size. |
7474

7575

7676
### Option `graphqlSchemas`

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ public class GraphQLCodegenGradleTask extends DefaultTask implements GraphQLCode
8787
private Boolean generateDataFetchingEnvironmentArgumentInApis = MappingConfigConstants.DEFAULT_GENERATE_DATA_FETCHING_ENV;
8888
private Boolean generateModelsForRootTypes = MappingConfigConstants.DEFAULT_GENERATE_MODELS_FOR_ROOT_TYPES;
8989
private Boolean useOptionalForNullableReturnTypes = MappingConfigConstants.DEFAULT_USE_OPTIONAL_FOR_NULLABLE_RETURN_TYPES;
90+
private Boolean useWrapperForNullableInputTypes =
91+
MappingConfigConstants.DEFAULT_USE_WRAPPER_FOR_NULLABLE_INPUT_TYPES;
9092
private Boolean generateApisWithThrowsException = MappingConfigConstants.DEFAULT_GENERATE_APIS_WITH_THROWS_EXCEPTION;
9193
private Boolean generateApisWithSuspendFunctions =
9294
MappingConfigConstants.DEFAULT_GENERATE_APIS_WITH_SUSPEND_FUNCTIONS;
@@ -146,7 +148,7 @@ public void generate() throws Exception {
146148
mappingConfig.setCustomAnnotationsMapping(
147149
customAnnotationsMapping != null ? customAnnotationsMapping : new HashMap<>());
148150
mappingConfig.setCustomTemplatesRoot(
149-
customTemplatesRoot != null ? customTemplatesRoot : getProject().getProjectDir()
151+
customTemplatesRoot != null ? customTemplatesRoot : getProject().getProjectDir()
150152
);
151153
mappingConfig.setCustomTemplates(
152154
customTemplates != null ? customTemplates : new HashMap<>());
@@ -170,6 +172,7 @@ public void generate() throws Exception {
170172
mappingConfig.setGenerateImmutableModels(generateImmutableModels);
171173
mappingConfig.setGenerateToString(generateToString);
172174
mappingConfig.setUseOptionalForNullableReturnTypes(useOptionalForNullableReturnTypes);
175+
mappingConfig.setUseWrapperForNullableInputTypes(useWrapperForNullableInputTypes);
173176
mappingConfig.setGenerateApisWithThrowsException(generateApisWithThrowsException);
174177
mappingConfig.setGenerateApisWithSuspendFunctions(generateApisWithSuspendFunctions);
175178
mappingConfig.setGenerateJacksonTypeIdResolver(generateJacksonTypeIdResolver);
@@ -236,10 +239,10 @@ private GraphQLCodegen instantiateCodegen(MappingConfig mappingConfig) throws IO
236239

237240
if (skipSchemaSizeLimit) {
238241
ParserOptions.Builder parserOptionBuilder = ParserOptions.newParserOptions()
239-
.maxTokens(Integer.MAX_VALUE)
240-
.maxCharacters(Integer.MAX_VALUE)
241-
.maxWhitespaceTokens(Integer.MAX_VALUE)
242-
.maxRuleDepth(Integer.MAX_VALUE);
242+
.maxTokens(Integer.MAX_VALUE)
243+
.maxCharacters(Integer.MAX_VALUE)
244+
.maxWhitespaceTokens(Integer.MAX_VALUE)
245+
.maxRuleDepth(Integer.MAX_VALUE);
243246
ParserOptions.setDefaultParserOptions(parserOptionBuilder.build());
244247
}
245248
switch (language) {
@@ -691,6 +694,17 @@ public void setUseOptionalForNullableReturnTypes(Boolean useOptionalForNullableR
691694
this.useOptionalForNullableReturnTypes = useOptionalForNullableReturnTypes;
692695
}
693696

697+
@Input
698+
@Optional
699+
@Override
700+
public Boolean getUseWrapperForNullableInputTypes() {
701+
return useWrapperForNullableInputTypes;
702+
}
703+
704+
public void setUseWrapperForNullableInputTypes(Boolean useWrapperForNullableInputTypes) {
705+
this.useWrapperForNullableInputTypes = useWrapperForNullableInputTypes;
706+
}
707+
694708
@Input
695709
@Optional
696710
@Override

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ public class GraphQLCodegenMojo extends AbstractMojo implements GraphQLCodegenCo
153153
@Parameter(defaultValue = MappingConfigConstants.DEFAULT_USE_OPTIONAL_FOR_NULLABLE_RETURN_TYPES_STRING)
154154
private boolean useOptionalForNullableReturnTypes;
155155

156+
@Parameter(defaultValue = MappingConfigConstants.DEFAULT_USE_WRAPPER_FOR_NULLABLE_INPUT_TYPES_STRING)
157+
private boolean useWrapperForNullableInputTypes;
158+
156159
@Parameter(defaultValue = MappingConfigConstants.DEFAULT_GENERATE_APIS_WITH_THROWS_EXCEPTION_STRING)
157160
private boolean generateApisWithThrowsException;
158161

@@ -291,6 +294,7 @@ public void execute() throws MojoExecutionException {
291294
mappingConfig.setGenerateExtensionFieldsResolvers(generateExtensionFieldsResolvers);
292295
mappingConfig.setGenerateModelsForRootTypes(generateModelsForRootTypes);
293296
mappingConfig.setUseOptionalForNullableReturnTypes(useOptionalForNullableReturnTypes);
297+
mappingConfig.setUseWrapperForNullableInputTypes(useWrapperForNullableInputTypes);
294298
mappingConfig.setGenerateApisWithThrowsException(generateApisWithThrowsException);
295299
mappingConfig.setGenerateApisWithSuspendFunctions(generateApisWithSuspendFunctions);
296300
mappingConfig.setGenerateJacksonTypeIdResolver(generateJacksonTypeIdResolver);
@@ -349,10 +353,10 @@ private GraphQLCodegen instantiateCodegen(MappingConfig mappingConfig) throws IO
349353

350354
if (skipSchemaSizeLimit) {
351355
ParserOptions.Builder parserOptionBuilder = ParserOptions.newParserOptions()
352-
.maxTokens(Integer.MAX_VALUE)
353-
.maxCharacters(Integer.MAX_VALUE)
354-
.maxWhitespaceTokens(Integer.MAX_VALUE)
355-
.maxRuleDepth(Integer.MAX_VALUE);
356+
.maxTokens(Integer.MAX_VALUE)
357+
.maxCharacters(Integer.MAX_VALUE)
358+
.maxWhitespaceTokens(Integer.MAX_VALUE)
359+
.maxRuleDepth(Integer.MAX_VALUE);
356360
ParserOptions.setDefaultParserOptions(parserOptionBuilder.build());
357361
}
358362

@@ -563,6 +567,11 @@ public Boolean getUseOptionalForNullableReturnTypes() {
563567
return useOptionalForNullableReturnTypes;
564568
}
565569

570+
@Override
571+
public Boolean getUseWrapperForNullableInputTypes() {
572+
return useWrapperForNullableInputTypes;
573+
}
574+
566575
@Override
567576
public Boolean getGenerateApisWithThrowsException() {
568577
return generateApisWithThrowsException;
@@ -775,8 +784,8 @@ private static Map<String, String> convertToMap(Properties properties) {
775784
result.put(name, properties.getProperty(name));
776785
}
777786
return result;
778-
}
779-
787+
}
788+
780789
@Override
781790
public File getCustomTemplatesRoot() {
782791
return customTemplatesRoot == null ? project.getBasedir() : customTemplatesRoot;

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ trait GraphQLCodegenKeys {
133133

134134
val useOptionalForNullableReturnTypes = settingKey[Boolean]("useOptionalForNullableReturnTypes")
135135

136+
val useWrapperForNullableInputTypes = settingKey[Boolean]("useWrapperForNullableInputTypes")
137+
136138
val generateApisWithThrowsException = settingKey[Boolean]("generateApisWithThrowsException")
137139

138140
val graphqlQueryIntrospectionResultPath = settingKey[Option[String]]("graphqlQueryIntrospectionResultPath")

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ class GraphQLCodegenPlugin(configuration: Configuration, private[codegen] val co
9999
apiReturnListType := None,
100100
apiInterfaceStrategy := MappingConfigConstants.DEFAULT_API_INTERFACE_STRATEGY,
101101
useOptionalForNullableReturnTypes := MappingConfigConstants.DEFAULT_USE_OPTIONAL_FOR_NULLABLE_RETURN_TYPES,
102+
useWrapperForNullableInputTypes := MappingConfigConstants.DEFAULT_USE_WRAPPER_FOR_NULLABLE_INPUT_TYPES,
102103
generateApisWithThrowsException := MappingConfigConstants.DEFAULT_GENERATE_APIS_WITH_THROWS_EXCEPTION,
103104
addGeneratedAnnotation := MappingConfigConstants.DEFAULT_ADD_GENERATED_ANNOTATION,
104105
generatedAnnotation := None,
@@ -190,6 +191,7 @@ class GraphQLCodegenPlugin(configuration: Configuration, private[codegen] val co
190191
mappingConfig.setDirectiveAnnotationsMapping((GraphQLCodegenConfig / directiveAnnotationsMapping).value)
191192
mappingConfig.setApiInterfaceStrategy((GraphQLCodegenConfig / apiInterfaceStrategy).value)
192193
mappingConfig.setUseOptionalForNullableReturnTypes((GraphQLCodegenConfig / useOptionalForNullableReturnTypes).value)
194+
mappingConfig.setUseWrapperForNullableInputTypes((GraphQLCodegenConfig / useWrapperForNullableInputTypes).value)
193195
mappingConfig.setGenerateApisWithThrowsException((GraphQLCodegenConfig / generateApisWithThrowsException).value)
194196
mappingConfig.setAddGeneratedAnnotation((GraphQLCodegenConfig / addGeneratedAnnotation).value)
195197
mappingConfig.setGeneratedAnnotation((GraphQLCodegenConfig / generatedAnnotation).value.orNull)

src/main/java/com/kobylynskyi/graphql/codegen/java/JavaGraphQLTypeMapper.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import com.kobylynskyi.graphql.codegen.model.NamedDefinition;
88
import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLOperation;
99
import com.kobylynskyi.graphql.codegen.utils.Utils;
10+
import graphql.language.InputValueDefinition;
11+
import graphql.language.NullValue;
1012

1113
import java.util.HashSet;
1214
import java.util.Map;
@@ -24,6 +26,10 @@ public class JavaGraphQLTypeMapper extends GraphQLTypeMapper {
2426
public static final String JAVA_UTIL_LIST = "java.util.List";
2527
public static final Pattern JAVA_UTIL_LIST_ELEMENT_REGEX = Pattern.compile("java\\.util\\.List<(.+)>");
2628
private static final String JAVA_UTIL_OPTIONAL = "java.util.Optional";
29+
private static final String INPUT_WRAPPER_CLASS = "org.springframework.graphql.data.ArgumentValue";
30+
private static final String INPUT_WRAPPER_NULL = INPUT_WRAPPER_CLASS + ".ofNullable(null)";
31+
private static final String INPUT_WRAPPER_UNDEFINED = INPUT_WRAPPER_CLASS + ".omitted()";
32+
private static final String INPUT_WRAPPER_WITH_VALUE = INPUT_WRAPPER_CLASS + ".ofNullable(%s)";
2733
private static final Set<String> JAVA_PRIMITIVE_TYPES = new HashSet<>(asList(
2834
"byte", "short", "int", "long", "float", "double", "char", "boolean"));
2935

@@ -126,4 +132,36 @@ public NamedDefinition getLanguageType(MappingContext mappingContext, String gra
126132
mandatory, primitiveCanBeUsed, serializeUsingObjectMapper);
127133
}
128134

135+
@Override
136+
public String wrapApiInputTypeIfRequired(MappingContext mappingContext, NamedDefinition namedDefinition,
137+
String parentTypeName) {
138+
String computedTypeName = namedDefinition.getJavaName();
139+
if (Boolean.TRUE.equals(mappingContext.getUseWrapperForNullableInputTypes()) &&
140+
mappingContext.getInputsName().contains(parentTypeName) &&
141+
!namedDefinition.isMandatory() && !computedTypeName.startsWith(JAVA_UTIL_LIST)) {
142+
return getGenericsString(mappingContext, INPUT_WRAPPER_CLASS, computedTypeName);
143+
}
144+
145+
return getTypeConsideringPrimitive(mappingContext, namedDefinition, computedTypeName);
146+
}
147+
148+
@Override
149+
public String wrapApiDefaultValueIfRequired(MappingContext mappingContext, NamedDefinition namedDefinition,
150+
InputValueDefinition inputValueDefinition, String defaultValue,
151+
String parentTypeName) {
152+
if (Boolean.TRUE.equals(mappingContext.getUseWrapperForNullableInputTypes()) &&
153+
mappingContext.getInputsName().contains(parentTypeName) &&
154+
!namedDefinition.isMandatory() && !namedDefinition.getJavaName().startsWith(JAVA_UTIL_LIST)) {
155+
if (defaultValue == null) {
156+
return INPUT_WRAPPER_UNDEFINED;
157+
} else if (inputValueDefinition.getDefaultValue() instanceof NullValue) {
158+
return INPUT_WRAPPER_NULL;
159+
} else {
160+
return String.format(INPUT_WRAPPER_WITH_VALUE, defaultValue);
161+
}
162+
} else {
163+
return defaultValue;
164+
}
165+
}
166+
129167
}

0 commit comments

Comments
 (0)