-
-
Notifications
You must be signed in to change notification settings - Fork 111
/
GraphQLTypeMapper.java
331 lines (302 loc) · 16.2 KB
/
GraphQLTypeMapper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
package com.kobylynskyi.graphql.codegen.mapper;
import com.kobylynskyi.graphql.codegen.model.DeprecatedDefinition;
import com.kobylynskyi.graphql.codegen.model.MappingContext;
import com.kobylynskyi.graphql.codegen.model.MultiLanguageDeprecated;
import com.kobylynskyi.graphql.codegen.model.NamedDefinition;
import com.kobylynskyi.graphql.codegen.model.definitions.ExtendedDefinition;
import com.kobylynskyi.graphql.codegen.utils.Utils;
import graphql.language.*;
import java.util.*;
/**
* Map GraphQL type to language-specific type (java/scala/kotlin/etc)
*
* @author kobylynskyi
*/
public interface GraphQLTypeMapper {
/**
* Get nested type of GraphQL Type. Example:
* {@code Event -> Event}
* {@code Event! -> Event}
* {@code [Event!]! -> Event}
* {@code [[Event]] -> Event}
*
* @param graphqlType GraphQL type
* @return GraphQL type without List/NonNull wrapping
*/
static String getNestedTypeName(Type<?> graphqlType) {
if (graphqlType instanceof TypeName) {
return ((TypeName) graphqlType).getName();
} else if (graphqlType instanceof ListType) {
return getNestedTypeName(((ListType) graphqlType).getType());
} else if (graphqlType instanceof NonNullType) {
return getNestedTypeName(((NonNullType) graphqlType).getType());
}
return null;
}
static String getMandatoryType(String typeName) {
return typeName + "!";
}
static List<Directive> getDirectives(NamedNode<?> def) {
if (def instanceof DirectivesContainer) {
return ((DirectivesContainer<?>) def).getDirectives();
}
return Collections.emptyList();
}
/**
* Wrap language-specific type (java/scala/kotlin/etc) into {@link List}.
* E.g.: {@code String} becomes {@code List<String>}
*
* @param type The name of a type that will be wrapped into {@code List<>}
* @param mappingContext Global mapping context
* @param mandatory Type is it mandatory
* @return String The name of the given type, wrapped into {@code List<>}
*/
String wrapIntoList(MappingContext mappingContext, String type, boolean mandatory);
/**
* Return upper bounded wildcard for the given interface type:
* {@code "Foo"} becomes {@code "List<? extends Foo>"}.
*
* @param type The name of a type whose upper bound wildcard will be wrapped into a list.
* @param mappingContext Global mapping context
* @param mandatory Type is it mandatory
* @return String The name of the the wrapped type.
*/
String wrapSuperTypeIntoList(MappingContext mappingContext, String type, boolean mandatory);
/**
* Wraps type into apiReturnType or subscriptionReturnType (defined in the mapping configuration).
* Examples:
* <p>
* - Given GraphQL schema: {@code type Query { events: [Event!]! }}
* - Given config: {@code useOptionalForNullableReturnTypes = true}
* - Return: {@code java.util.Optional<Event>}
* <p>
* - Given GraphQL schema: {@code type Subscription { eventsCreated: [Event!]! }}
* - Given subscriptionReturnType in config: {@code org.reactivestreams.Publisher}
* - Return: {@code org.reactivestreams.Publisher<Event>}
* <p>
* - Given GraphQL schema: {@code type Mutation { createEvent(inp: Inp): Event }}
* - Given apiReturnType in config: {@code reactor.core.publisher.Mono}
* - Return: {@code reactor.core.publisher.Mono<Event>}
* <p>
* - Given GraphQL schema: {@code type Query { events: [Event!]! }}
* - Given apiReturnListType in config: {@code reactor.core.publisher.Flux}
* - Return: {@code reactor.core.publisher.Flux<Event>}
*
* @param mappingContext Global mapping context
* @param namedDefinition Named definition
* @param parentTypeName Name of the parent type
* @return Java/Scala type wrapped into the subscriptionReturnType
*/
String wrapApiReturnTypeIfRequired(MappingContext mappingContext,
NamedDefinition namedDefinition,
String parentTypeName);
/**
* Check if the time is primitive.
*
* @param possiblyPrimitiveType type to check
* @return true if the provided type is primitive, false if the provided type is not primitive
*/
boolean isPrimitive(String possiblyPrimitiveType);
/**
* Wrap string into generics type
*
* @param mappingContext Global mapping context
* @param genericType Generics type
* @param typeParameter Parameter of generics type
* @return type wrapped into generics
*/
String getGenericsString(MappingContext mappingContext, String genericType, String typeParameter);
/**
* Map value of the directive argument
*
* @param mappingContext Global mapping context
* @param dirArg Directive argument
* @param argumentValueFormatter Formatter of the directive argument
* @return formatted value
*/
String mapDirectiveArgumentValue(MappingContext mappingContext, Argument dirArg, String argumentValueFormatter);
/**
* Convert GraphQL type to a corresponding language-specific type
*
* @param mappingContext Global mapping context
* @param type GraphQL type
* @return Corresponding language-specific type (java/scala/kotlin/etc)
*/
default String getLanguageType(MappingContext mappingContext, Type<?> type) {
return getLanguageType(mappingContext, type, null, null).getJavaName();
}
/**
* Convert GraphQL type to a corresponding language-specific type (java/scala/kotlin/etc)
*
* @param mappingContext Global mapping context
* @param graphqlType GraphQL type
* @param name GraphQL type name
* @param parentTypeName Name of the parent type
* @return Corresponding language-specific type (java/scala/kotlin/etc)
*/
default NamedDefinition getLanguageType(MappingContext mappingContext, Type<?> graphqlType, String name, String parentTypeName) {
return getLanguageType(mappingContext, graphqlType, name, parentTypeName, false, false);
}
/**
* Convert GraphQL type to a corresponding language-specific type (java/scala/kotlin/etc)
*
* @param mappingContext Global mapping context
* @param graphqlType GraphQL type
* @param name GraphQL type name
* @param parentTypeName Name of the parent type
* @param mandatory GraphQL type is non-null
* @param collection GraphQL type is collection
* @return Corresponding language-specific type (java/scala/kotlin/etc)
*/
default NamedDefinition getLanguageType(MappingContext mappingContext, Type<?> graphqlType,
String name, String parentTypeName,
boolean mandatory, boolean collection) {
if (graphqlType instanceof TypeName) {
return getLanguageType(mappingContext, ((TypeName) graphqlType).getName(), name, parentTypeName, mandatory, collection);
} else if (graphqlType instanceof ListType) {
NamedDefinition mappedCollectionType = getLanguageType(mappingContext, ((ListType) graphqlType).getType(), name, parentTypeName, false, true);
if (mappedCollectionType.isInterface() && mappingContext.getInterfacesName().contains(parentTypeName)) {
mappedCollectionType.setJavaName(wrapSuperTypeIntoList(mappingContext, mappedCollectionType.getJavaName(), mandatory));
} else {
mappedCollectionType.setJavaName(wrapIntoList(mappingContext, mappedCollectionType.getJavaName(), mandatory));
}
return mappedCollectionType;
} else if (graphqlType instanceof NonNullType) {
return getLanguageType(mappingContext, ((NonNullType) graphqlType).getType(), name, parentTypeName, true, collection);
}
throw new IllegalArgumentException("Unknown type: " + graphqlType);
}
/**
* Convert GraphQL type to a corresponding language-specific type (java/scala/kotlin/etc)
*
* @param mappingContext Global mapping context
* @param graphQLType GraphQL type
* @param name GraphQL type name
* @param parentTypeName Name of the parent type
* @param mandatory GraphQL type is non-null
* @param collection GraphQL type is collection
* @return Corresponding language-specific type (java/scala/kotlin/etc)
*/
default NamedDefinition getLanguageType(MappingContext mappingContext, String graphQLType, String name,
String parentTypeName, boolean mandatory, boolean collection) {
Map<String, String> customTypesMapping = mappingContext.getCustomTypesMapping();
Set<String> serializeFieldsUsingObjectMapper = mappingContext.getUseObjectMapperForRequestSerialization();
String langTypeName;
boolean primitiveCanBeUsed = !collection;
boolean serializeUsingObjectMapper = false;
if (name != null && parentTypeName != null && customTypesMapping.containsKey(parentTypeName + "." + name)) {
langTypeName = customTypesMapping.get(parentTypeName + "." + name);
primitiveCanBeUsed = false;
} else if (mandatory && customTypesMapping.containsKey(getMandatoryType(graphQLType))) {
langTypeName = customTypesMapping.get(getMandatoryType(graphQLType));
} else if (customTypesMapping.containsKey(graphQLType)) {
langTypeName = customTypesMapping.get(graphQLType);
} else {
langTypeName = DataModelMapper.getModelClassNameWithPrefixAndSuffix(mappingContext, graphQLType);
}
if (serializeFieldsUsingObjectMapper.contains(graphQLType) ||
(name != null && parentTypeName != null &&
serializeFieldsUsingObjectMapper.contains(parentTypeName + "." + name))) {
serializeUsingObjectMapper = true;
}
return new NamedDefinition(langTypeName, graphQLType, mappingContext.getInterfacesName().contains(graphQLType),
mandatory, primitiveCanBeUsed, serializeUsingObjectMapper);
}
/**
* Get annotations for a given GraphQL type
*
* @param mappingContext Global mapping context
* @param type GraphQL type
* @param def GraphQL definition
* @param parentTypeName Name of the parent type
* @param mandatory Type is mandatory
* @return list of Java annotations for a given GraphQL type
*/
default List<String> getAnnotations(MappingContext mappingContext, Type<?> type,
NamedNode<?> def, String parentTypeName, boolean mandatory) {
if (type instanceof ListType) {
return getAnnotations(mappingContext, ((ListType) type).getType(), def, parentTypeName, mandatory);
} else if (type instanceof NonNullType) {
return getAnnotations(mappingContext, ((NonNullType) type).getType(), def, parentTypeName, true);
} else if (type instanceof TypeName) {
return getAnnotations(mappingContext, ((TypeName) type).getName(), def.getName(), parentTypeName, getDirectives(def), mandatory);
}
return Collections.emptyList();
}
default List<String> getAnnotations(MappingContext mappingContext, ExtendedDefinition<?, ?> extendedDefinition) {
return getAnnotations(mappingContext, extendedDefinition.getName(), extendedDefinition.getName(), null, Collections.emptyList(), false);
}
default List<String> getAnnotations(MappingContext mappingContext, String name) {
return getAnnotations(mappingContext, name, name, null, Collections.emptyList(), false);
}
default List<String> getAnnotations(MappingContext mappingContext, String graphQLTypeName, String name,
String parentTypeName, List<Directive> directives, boolean mandatory) {
List<String> annotations = new ArrayList<>();
if (mandatory) {
String possiblyPrimitiveType = mappingContext.getCustomTypesMapping().get(getMandatoryType(graphQLTypeName));
String modelValidationAnnotation = mappingContext.getModelValidationAnnotation();
if (Utils.isNotBlank(modelValidationAnnotation) && addModelValidationAnnotationForType(possiblyPrimitiveType)) {
annotations.add(modelValidationAnnotation);
}
}
Map<String, List<String>> customAnnotationsMapping = mappingContext.getCustomAnnotationsMapping();
if (name != null && parentTypeName != null && customAnnotationsMapping.containsKey(parentTypeName + "." + name)) {
List<String> annotationsToAdd = customAnnotationsMapping.get(parentTypeName + "." + name);
if (!Utils.isEmpty(annotationsToAdd)) {
annotations.addAll(annotationsToAdd);
}
} else if (customAnnotationsMapping.containsKey(graphQLTypeName)) {
List<String> annotationsToAdd = customAnnotationsMapping.get(graphQLTypeName);
if (!Utils.isEmpty(annotationsToAdd)) {
annotations.addAll(annotationsToAdd);
}
}
Map<String, List<String>> directiveAnnotationsMapping = mappingContext.getDirectiveAnnotationsMapping();
for (Directive directive : directives) {
if (directiveAnnotationsMapping.containsKey(directive.getName())) {
annotations.addAll(getAnnotationForDirective(mappingContext, directiveAnnotationsMapping.get(directive.getName()), directive));
}
}
return annotations;
}
boolean addModelValidationAnnotationForType(String possiblyPrimitiveType);
default List<String> getAnnotationForDirective(MappingContext mappingContext,
List<String> directiveAnnotations,
Directive directive) {
List<String> directiveAnnotationsMapped = new ArrayList<>();
for (String annotation : directiveAnnotations) {
String directiveAnnotationMapped = annotation;
for (Argument dirArg : directive.getArguments()) {
String argumentValueFormatter = Utils.substringBetween(annotation, "{{" + dirArg.getName(), "}}");
// if argumentValueFormatter == null then the placeholder {{dirArg.getName()}} does not exist
if (argumentValueFormatter != null) {
directiveAnnotationMapped = directiveAnnotationMapped.replace(
String.format("{{%s%s}}", dirArg.getName(), argumentValueFormatter),
mapDirectiveArgumentValue(mappingContext, dirArg, argumentValueFormatter));
}
}
directiveAnnotationsMapped.add(directiveAnnotationMapped);
}
return directiveAnnotationsMapped;
}
default String getTypeConsideringPrimitive(MappingContext mappingContext,
NamedDefinition namedDefinition,
String computedTypeName) {
String graphqlTypeName = namedDefinition.getGraphqlTypeName();
if (namedDefinition.isMandatory() && namedDefinition.isPrimitiveCanBeUsed()) {
String possiblyPrimitiveType = mappingContext.getCustomTypesMapping().get(getMandatoryType(graphqlTypeName));
if (isPrimitive(possiblyPrimitiveType)) {
return possiblyPrimitiveType;
}
}
return computedTypeName;
}
default DeprecatedDefinition getDeprecated(MappingContext mappingContext, DirectivesContainer<?> directivesContainer) {
return directivesContainer.getDirectives()
.stream()
.filter(d -> d.getName().equalsIgnoreCase(Deprecated.class.getSimpleName()))
.findFirst()
.map(directive -> MultiLanguageDeprecated.getLanguageDeprecated(mappingContext.getGeneratedLanguage(), directive))
.orElse(null);
}
}