Skip to content

Support ArgumentValue wrapper for input parameters #1450

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Feb 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/codegen-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
| `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. |
| `generateModelsForRootTypes` | Boolean | False | Specifies whether model classes should be generated for `type Query`, `type Subscription`, `type Mutation`. |
| `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. |
| `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. |
| `generateApisWithThrowsException` | Boolean | True | Specifies whether api interface methods should have `throws Exception` in signature. |
| `generateApisWithSuspendFunctions` | Boolean | False | Specifies whether api interface methods should have `suspend` modifier in signature. Only supported in Kotlin. |
| `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> |
Expand All @@ -69,8 +70,7 @@
| `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. |
| `unknownFieldsPropertyName` | String | userDefinedFields | Specifies the name of the property to be included in api classes to support unknown fields during serialization or deserialization |
| `skip` | Boolean | False | If true, then code generation will not happen |
| `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.
|
| `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. |


### Option `graphqlSchemas`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ public class GraphQLCodegenGradleTask extends DefaultTask implements GraphQLCode
private Boolean generateDataFetchingEnvironmentArgumentInApis = MappingConfigConstants.DEFAULT_GENERATE_DATA_FETCHING_ENV;
private Boolean generateModelsForRootTypes = MappingConfigConstants.DEFAULT_GENERATE_MODELS_FOR_ROOT_TYPES;
private Boolean useOptionalForNullableReturnTypes = MappingConfigConstants.DEFAULT_USE_OPTIONAL_FOR_NULLABLE_RETURN_TYPES;
private Boolean useWrapperForNullableInputTypes =
MappingConfigConstants.DEFAULT_USE_WRAPPER_FOR_NULLABLE_INPUT_TYPES;
private Boolean generateApisWithThrowsException = MappingConfigConstants.DEFAULT_GENERATE_APIS_WITH_THROWS_EXCEPTION;
private Boolean generateApisWithSuspendFunctions =
MappingConfigConstants.DEFAULT_GENERATE_APIS_WITH_SUSPEND_FUNCTIONS;
Expand Down Expand Up @@ -146,7 +148,7 @@ public void generate() throws Exception {
mappingConfig.setCustomAnnotationsMapping(
customAnnotationsMapping != null ? customAnnotationsMapping : new HashMap<>());
mappingConfig.setCustomTemplatesRoot(
customTemplatesRoot != null ? customTemplatesRoot : getProject().getProjectDir()
customTemplatesRoot != null ? customTemplatesRoot : getProject().getProjectDir()
);
mappingConfig.setCustomTemplates(
customTemplates != null ? customTemplates : new HashMap<>());
Expand All @@ -170,6 +172,7 @@ public void generate() throws Exception {
mappingConfig.setGenerateImmutableModels(generateImmutableModels);
mappingConfig.setGenerateToString(generateToString);
mappingConfig.setUseOptionalForNullableReturnTypes(useOptionalForNullableReturnTypes);
mappingConfig.setUseWrapperForNullableInputTypes(useWrapperForNullableInputTypes);
mappingConfig.setGenerateApisWithThrowsException(generateApisWithThrowsException);
mappingConfig.setGenerateApisWithSuspendFunctions(generateApisWithSuspendFunctions);
mappingConfig.setGenerateJacksonTypeIdResolver(generateJacksonTypeIdResolver);
Expand Down Expand Up @@ -236,10 +239,10 @@ private GraphQLCodegen instantiateCodegen(MappingConfig mappingConfig) throws IO

if (skipSchemaSizeLimit) {
ParserOptions.Builder parserOptionBuilder = ParserOptions.newParserOptions()
.maxTokens(Integer.MAX_VALUE)
.maxCharacters(Integer.MAX_VALUE)
.maxWhitespaceTokens(Integer.MAX_VALUE)
.maxRuleDepth(Integer.MAX_VALUE);
.maxTokens(Integer.MAX_VALUE)
.maxCharacters(Integer.MAX_VALUE)
.maxWhitespaceTokens(Integer.MAX_VALUE)
.maxRuleDepth(Integer.MAX_VALUE);
ParserOptions.setDefaultParserOptions(parserOptionBuilder.build());
}
switch (language) {
Expand Down Expand Up @@ -691,6 +694,17 @@ public void setUseOptionalForNullableReturnTypes(Boolean useOptionalForNullableR
this.useOptionalForNullableReturnTypes = useOptionalForNullableReturnTypes;
}

@Input
@Optional
@Override
public Boolean getUseWrapperForNullableInputTypes() {
return useWrapperForNullableInputTypes;
}

public void setUseWrapperForNullableInputTypes(Boolean useWrapperForNullableInputTypes) {
this.useWrapperForNullableInputTypes = useWrapperForNullableInputTypes;
}

@Input
@Optional
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ public class GraphQLCodegenMojo extends AbstractMojo implements GraphQLCodegenCo
@Parameter(defaultValue = MappingConfigConstants.DEFAULT_USE_OPTIONAL_FOR_NULLABLE_RETURN_TYPES_STRING)
private boolean useOptionalForNullableReturnTypes;

@Parameter(defaultValue = MappingConfigConstants.DEFAULT_USE_WRAPPER_FOR_NULLABLE_INPUT_TYPES_STRING)
private boolean useWrapperForNullableInputTypes;

@Parameter(defaultValue = MappingConfigConstants.DEFAULT_GENERATE_APIS_WITH_THROWS_EXCEPTION_STRING)
private boolean generateApisWithThrowsException;

Expand Down Expand Up @@ -291,6 +294,7 @@ public void execute() throws MojoExecutionException {
mappingConfig.setGenerateExtensionFieldsResolvers(generateExtensionFieldsResolvers);
mappingConfig.setGenerateModelsForRootTypes(generateModelsForRootTypes);
mappingConfig.setUseOptionalForNullableReturnTypes(useOptionalForNullableReturnTypes);
mappingConfig.setUseWrapperForNullableInputTypes(useWrapperForNullableInputTypes);
mappingConfig.setGenerateApisWithThrowsException(generateApisWithThrowsException);
mappingConfig.setGenerateApisWithSuspendFunctions(generateApisWithSuspendFunctions);
mappingConfig.setGenerateJacksonTypeIdResolver(generateJacksonTypeIdResolver);
Expand Down Expand Up @@ -349,10 +353,10 @@ private GraphQLCodegen instantiateCodegen(MappingConfig mappingConfig) throws IO

if (skipSchemaSizeLimit) {
ParserOptions.Builder parserOptionBuilder = ParserOptions.newParserOptions()
.maxTokens(Integer.MAX_VALUE)
.maxCharacters(Integer.MAX_VALUE)
.maxWhitespaceTokens(Integer.MAX_VALUE)
.maxRuleDepth(Integer.MAX_VALUE);
.maxTokens(Integer.MAX_VALUE)
.maxCharacters(Integer.MAX_VALUE)
.maxWhitespaceTokens(Integer.MAX_VALUE)
.maxRuleDepth(Integer.MAX_VALUE);
ParserOptions.setDefaultParserOptions(parserOptionBuilder.build());
}

Expand Down Expand Up @@ -563,6 +567,11 @@ public Boolean getUseOptionalForNullableReturnTypes() {
return useOptionalForNullableReturnTypes;
}

@Override
public Boolean getUseWrapperForNullableInputTypes() {
return useWrapperForNullableInputTypes;
}

@Override
public Boolean getGenerateApisWithThrowsException() {
return generateApisWithThrowsException;
Expand Down Expand Up @@ -775,8 +784,8 @@ private static Map<String, String> convertToMap(Properties properties) {
result.put(name, properties.getProperty(name));
}
return result;
}
}

@Override
public File getCustomTemplatesRoot() {
return customTemplatesRoot == null ? project.getBasedir() : customTemplatesRoot;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ trait GraphQLCodegenKeys {

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

val useWrapperForNullableInputTypes = settingKey[Boolean]("useWrapperForNullableInputTypes")

val generateApisWithThrowsException = settingKey[Boolean]("generateApisWithThrowsException")

val graphqlQueryIntrospectionResultPath = settingKey[Option[String]]("graphqlQueryIntrospectionResultPath")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class GraphQLCodegenPlugin(configuration: Configuration, private[codegen] val co
apiReturnListType := None,
apiInterfaceStrategy := MappingConfigConstants.DEFAULT_API_INTERFACE_STRATEGY,
useOptionalForNullableReturnTypes := MappingConfigConstants.DEFAULT_USE_OPTIONAL_FOR_NULLABLE_RETURN_TYPES,
useWrapperForNullableInputTypes := MappingConfigConstants.DEFAULT_USE_WRAPPER_FOR_NULLABLE_INPUT_TYPES,
generateApisWithThrowsException := MappingConfigConstants.DEFAULT_GENERATE_APIS_WITH_THROWS_EXCEPTION,
addGeneratedAnnotation := MappingConfigConstants.DEFAULT_ADD_GENERATED_ANNOTATION,
generatedAnnotation := None,
Expand Down Expand Up @@ -191,6 +192,7 @@ class GraphQLCodegenPlugin(configuration: Configuration, private[codegen] val co
mappingConfig.setDirectiveAnnotationsMapping((GraphQLCodegenConfig / directiveAnnotationsMapping).value)
mappingConfig.setApiInterfaceStrategy((GraphQLCodegenConfig / apiInterfaceStrategy).value)
mappingConfig.setUseOptionalForNullableReturnTypes((GraphQLCodegenConfig / useOptionalForNullableReturnTypes).value)
mappingConfig.setUseWrapperForNullableInputTypes((GraphQLCodegenConfig / useWrapperForNullableInputTypes).value)
mappingConfig.setGenerateApisWithThrowsException((GraphQLCodegenConfig / generateApisWithThrowsException).value)
mappingConfig.setAddGeneratedAnnotation((GraphQLCodegenConfig / addGeneratedAnnotation).value)
mappingConfig.setGeneratedAnnotation((GraphQLCodegenConfig / generatedAnnotation).value.orNull)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import com.kobylynskyi.graphql.codegen.model.NamedDefinition;
import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLOperation;
import com.kobylynskyi.graphql.codegen.utils.Utils;
import graphql.language.InputValueDefinition;
import graphql.language.NullValue;

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

Expand Down Expand Up @@ -126,4 +132,36 @@ public NamedDefinition getLanguageType(MappingContext mappingContext, String gra
mandatory, primitiveCanBeUsed, serializeUsingObjectMapper);
}

@Override
public String wrapApiInputTypeIfRequired(MappingContext mappingContext, NamedDefinition namedDefinition,
String parentTypeName) {
String computedTypeName = namedDefinition.getJavaName();
if (Boolean.TRUE.equals(mappingContext.getUseWrapperForNullableInputTypes()) &&
mappingContext.getInputsName().contains(parentTypeName) &&
!namedDefinition.isMandatory() && !computedTypeName.startsWith(JAVA_UTIL_LIST)) {
return getGenericsString(mappingContext, INPUT_WRAPPER_CLASS, computedTypeName);
}

return getTypeConsideringPrimitive(mappingContext, namedDefinition, computedTypeName);
}

@Override
public String wrapApiDefaultValueIfRequired(MappingContext mappingContext, NamedDefinition namedDefinition,
InputValueDefinition inputValueDefinition, String defaultValue,
String parentTypeName) {
if (Boolean.TRUE.equals(mappingContext.getUseWrapperForNullableInputTypes()) &&
mappingContext.getInputsName().contains(parentTypeName) &&
!namedDefinition.isMandatory() && !namedDefinition.getJavaName().startsWith(JAVA_UTIL_LIST)) {
if (defaultValue == null) {
return INPUT_WRAPPER_UNDEFINED;
} else if (inputValueDefinition.getDefaultValue() instanceof NullValue) {
return INPUT_WRAPPER_NULL;
} else {
return String.format(INPUT_WRAPPER_WITH_VALUE, defaultValue);
}
} else {
return defaultValue;
}
}

}
Loading