com.kobylynskyi.graphql.codegen.mapper.FieldDefinitionsToResolverDataModelMapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of graphql-java-codegen Show documentation
Show all versions of graphql-java-codegen Show documentation
Java Code Generator based on GraphQL schema
The newest version!
package com.kobylynskyi.graphql.codegen.mapper;
import com.kobylynskyi.graphql.codegen.model.MappingContext;
import com.kobylynskyi.graphql.codegen.model.NamedDefinition;
import com.kobylynskyi.graphql.codegen.model.OperationDefinition;
import com.kobylynskyi.graphql.codegen.model.ParameterDefinition;
import com.kobylynskyi.graphql.codegen.model.RelayConfig;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedFieldDefinition;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedObjectTypeDefinition;
import com.kobylynskyi.graphql.codegen.model.graphql.GraphQLOperation;
import com.kobylynskyi.graphql.codegen.utils.Utils;
import graphql.language.Argument;
import graphql.language.Directive;
import graphql.language.StringValue;
import graphql.language.TypeName;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.CLASS_NAME;
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.GENERATED_INFO;
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.IMPLEMENTS;
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.IMPORTS;
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.JAVA_DOC;
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.OPERATIONS;
import static com.kobylynskyi.graphql.codegen.model.DataModelFields.PACKAGE;
import static com.kobylynskyi.graphql.codegen.model.MappingConfigConstants.PARENT_INTERFACE_TYPE_PLACEHOLDER;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
/**
* Map field definitions to a Freemarker data model representing a resolver for these fields.
*/
public class FieldDefinitionsToResolverDataModelMapper {
private FieldDefinitionsToResolverDataModelMapper() {
}
/**
* Map field definition to a Freemarker data model
*
* @param mappingContext Global mapping context
* @param fieldDefs GraphQL field definitions that require resolvers
* @param parentTypeName Name of the type for which Resolver will be generated
* @return Freemarker data model of the GraphQL parametrized field
*/
public static Map mapToTypeResolver(MappingContext mappingContext,
List fieldDefs,
String parentTypeName) {
// Example: PersonResolver
String className = MapperUtils.getTypeResolverClassNameWithPrefixAndSuffix(mappingContext, parentTypeName);
return mapToResolverModel(mappingContext, parentTypeName, className, fieldDefs,
singletonList("Resolver for " + parentTypeName),
getParentInterface(mappingContext, parentTypeName));
}
/**
* Map field definition to a Freemarker data model
*
* @param mappingContext Global mapping context
* @param fieldDefinition GraphQL field definition
* @param rootTypeName Object type (e.g.: "Query", "Mutation" or "Subscription")
* @param fieldNames Names of all fields inside the rootType. Used to detect duplicate
* @return Freemarker data model of the GraphQL field
*/
public static Map mapRootTypeField(MappingContext mappingContext,
ExtendedFieldDefinition fieldDefinition,
String rootTypeName,
List fieldNames) {
String className = MapperUtils.getApiClassNameWithPrefixAndSuffix(mappingContext, fieldDefinition, rootTypeName, fieldNames);
List fieldDefs = Collections.singletonList(fieldDefinition);
return mapToResolverModel(mappingContext, rootTypeName, className, fieldDefs, fieldDefinition.getJavaDoc(),
getParentInterface(mappingContext, rootTypeName));
}
/**
* Map a root object type definition to a Freemarker data model for a resolver with all its fields.
*
* @param mappingContext Global mapping context
* @param definition GraphQL object definition of a root type like Query
* @return Freemarker data model of the GraphQL object
*/
public static Map mapRootTypeFields(MappingContext mappingContext,
ExtendedObjectTypeDefinition definition) {
String className = MapperUtils.getApiClassNameWithPrefixAndSuffix(mappingContext, definition);
// For root types like "Query", we create resolvers for all fields
return mapToResolverModel(mappingContext, definition.getName(), className,
definition.getFieldDefinitions(), definition.getJavaDoc(),
getParentInterface(mappingContext, definition.getName()));
}
private static Map mapToResolverModel(MappingContext mappingContext, String parentTypeName,
String className,
List fieldDefinitions,
List javaDoc,
String parentInterface) {
String packageName = MapperUtils.getApiPackageName(mappingContext);
Set imports = MapperUtils.getImports(mappingContext, packageName);
List operations = mapToOperations(mappingContext, fieldDefinitions, parentTypeName);
Map dataModel = new HashMap<>();
dataModel.put(PACKAGE, packageName);
dataModel.put(IMPORTS, imports);
dataModel.put(CLASS_NAME, className);
dataModel.put(OPERATIONS, operations);
dataModel.put(JAVA_DOC, javaDoc);
dataModel.put(IMPLEMENTS, parentInterface != null ? singletonList(parentInterface) : null);
dataModel.put(GENERATED_INFO, mappingContext.getGeneratedInformation());
return dataModel;
}
/**
* Builds a list of Freemarker-understandable structures representing operations to resolve the given fields
* for a given parent type.
*
* @param mappingContext Global mapping context
* @param fieldDefinitions The GraphQL definition of the fields that the methods should resolve
* @param parentTypeName Name of the parent type which the field belongs to
* @return Freemarker-understandable format of operations
*/
private static List mapToOperations(MappingContext mappingContext,
List fieldDefinitions,
String parentTypeName) {
return fieldDefinitions.stream()
.map(fieldDef -> map(mappingContext, fieldDef, parentTypeName))
.collect(Collectors.toList());
}
/**
* Builds a Freemarker-understandable structure representing an operation to resolve a field for a given parent type.
*
* @param mappingContext Global mapping context
* @param fieldDef The GraphQL definition of the field that the method should resolve
* @param parentTypeName Name of the parent type which the field belongs to
* @return Freemarker-understandable format of operation
*/
private static OperationDefinition map(MappingContext mappingContext, ExtendedFieldDefinition fieldDef,
String parentTypeName) {
String name = MapperUtils.capitalizeIfRestricted(fieldDef.getName());
NamedDefinition javaType = GraphqlTypeToJavaTypeMapper.getJavaType(mappingContext, fieldDef.getType(), fieldDef.getName(), parentTypeName);
String returnType = getReturnType(mappingContext, fieldDef, javaType, parentTypeName);
List annotations = GraphqlTypeToJavaTypeMapper.getAnnotations(mappingContext, fieldDef.getType(), fieldDef, parentTypeName, false);
List parameters = getOperationParameters(mappingContext, fieldDef, parentTypeName);
OperationDefinition operation = new OperationDefinition();
operation.setName(name);
operation.setOriginalName(fieldDef.getName());
operation.setType(returnType);
operation.setAnnotations(annotations);
operation.setParameters(parameters);
operation.setJavaDoc(fieldDef.getJavaDoc());
operation.setDeprecated(fieldDef.isDeprecated());
return operation;
}
private static List getOperationParameters(MappingContext mappingContext,
ExtendedFieldDefinition resolvedField,
String parentTypeName) {
List parameters = new ArrayList<>();
// 1. First parameter is the parent object for which we are resolving fields (unless it's the root Query)
if (!Utils.isGraphqlOperation(parentTypeName)) {
String parentObjectParamType = GraphqlTypeToJavaTypeMapper.getJavaType(mappingContext, new TypeName(parentTypeName));
String parentObjectParamName = MapperUtils.capitalizeIfRestricted(Utils.uncapitalize(parentObjectParamType));
parameters.add(new ParameterDefinition(parentObjectParamType, parentObjectParamName, parentObjectParamName, null, emptyList(), emptyList(), resolvedField.isDeprecated()));
}
// 2. Next parameters are input values
parameters.addAll(InputValueDefinitionToParameterMapper.map(mappingContext, resolvedField.getInputValueDefinitions(), resolvedField.getName()));
// 3. Last parameter (optional) is the DataFetchingEnvironment
if (Boolean.TRUE.equals(mappingContext.getGenerateDataFetchingEnvironmentArgumentInApis())) {
parameters.add(ParameterDefinition.DATA_FETCHING_ENVIRONMENT);
}
return parameters;
}
public static String getParentInterface(MappingContext mappingContext, String typeName) {
// 1. check if provided type name is GraphQL root type
try {
switch (GraphQLOperation.valueOf(typeName.toUpperCase())) {
case QUERY:
return mappingContext.getQueryResolverParentInterface();
case MUTATION:
return mappingContext.getMutationResolverParentInterface();
case SUBSCRIPTION:
return mappingContext.getSubscriptionResolverParentInterface();
}
} catch (Exception ignored) {
}
// 2. if provided type name is GraphQL root type then assume that it is GraphQL type
if (mappingContext.getResolverParentInterface() == null) {
return null;
}
return mappingContext.getResolverParentInterface()
.replace(PARENT_INTERFACE_TYPE_PLACEHOLDER,
MapperUtils.getModelClassNameWithPrefixAndSuffix(mappingContext, typeName));
}
private static String getReturnType(MappingContext mappingContext, ExtendedFieldDefinition fieldDef,
NamedDefinition namedDefinition, String parentTypeName) {
RelayConfig relayConfig = mappingContext.getRelayConfig();
if (relayConfig != null && relayConfig.getDirectiveName() != null) {
Directive connectionDirective = fieldDef.getDirective(relayConfig.getDirectiveName());
if (connectionDirective != null) {
Argument argument = connectionDirective.getArgument(relayConfig.getDirectiveArgumentName());
// as of now supporting only string value of directive argument
if (argument != null && argument.getValue() instanceof StringValue) {
String graphqlTypeName = ((StringValue) argument.getValue()).getValue();
String javaTypeName = GraphqlTypeToJavaTypeMapper.getJavaType(mappingContext,
new TypeName(graphqlTypeName), graphqlTypeName, parentTypeName, false).getName();
return GraphqlTypeToJavaTypeMapper.getGenericsString(relayConfig.getConnectionType(), javaTypeName);
}
}
}
return GraphqlTypeToJavaTypeMapper.wrapApiReturnTypeIfRequired(mappingContext, namedDefinition, parentTypeName);
}
}