Skip to content

Commit 8cf3440

Browse files
committed
add method name cases and ignore graphql name
1 parent 47c97da commit 8cf3440

File tree

3 files changed

+178
-28
lines changed

3 files changed

+178
-28
lines changed

src/main/java/graphql/annotations/dataFetchers/MethodDataFetcher.java

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,35 @@
2323

2424
import java.lang.reflect.*;
2525
import java.util.ArrayList;
26+
import java.util.Arrays;
2627
import java.util.List;
2728
import java.util.Map;
2829

2930
import static graphql.annotations.processor.util.NamingKit.toGraphqlName;
3031
import static graphql.annotations.processor.util.PrefixesUtil.addPrefixToPropertyName;
32+
import static graphql.annotations.processor.util.PrefixesUtil.extractPrefixedName;
3133
import static graphql.annotations.processor.util.ReflectionKit.constructNewInstance;
3234
import static graphql.annotations.processor.util.ReflectionKit.newInstance;
3335

36+
37+
/**
38+
* This class is determining how to return value of a method from an api entity
39+
* The order of the mapping:
40+
* 1. If no source is provided to map between - invoking the method implementation
41+
* 2. If annotated with @GraphQLInvokeDetached - invoking the method implementation
42+
* 3. else If source is provided, and method name is matching a method name in the source object - execute source implementation
43+
* i.e method name is: `name` ; existing method in the source object with name: `name`
44+
* 4. else If source is provided, and method name is matching a method name with a `get` prefix in the source object - execute source implementation
45+
* i.e method name is: `name` ; existing method in the source object with name: `getName`
46+
* 5. else If source is provided, and method name is matching a method name with a `is` prefix in the source object - execute source implementation
47+
* i.e method name is: `name` ; existing method in the source object with name: isName
48+
* 6. else If source is provided, and method name is matching a field name in the source object - return field value from the source object
49+
* i.e method name is: `name` ; field name in source object is: `name`
50+
* 7. else If source is provided, and method name is prefixed with `get` or `is` - and it matches to a field name (without the prefix) in the source object - return field value from the source object
51+
* i.e method name is: `getName` ; field name in source object is: `name`
52+
*
53+
* @param <T> type of the returned value
54+
*/
3455
public class MethodDataFetcher<T> implements DataFetcher<T> {
3556
private final Method method;
3657
private final ProcessingElementsContainer container;
@@ -59,7 +80,7 @@ public T get(DataFetchingEnvironment environment) {
5980
}
6081

6182
if (obj == null && environment.getSource() != null) {
62-
Object value = getGraphQLFieldValue(environment.getSource(), environment.getField().getName());
83+
Object value = getGraphQLFieldValue(environment.getSource(), method.getName());
6384
return (T) value;
6485
}
6586

@@ -140,20 +161,43 @@ private Object getGraphQLFieldValue(Object source, String fieldName) throws Ille
140161
Object methodValue = getValueFromMethod(source, fieldName);
141162
if (methodValue != null) return methodValue;
142163

143-
Field field = getField(source.getClass(), fieldName);
144-
if (getValueFromField(field)) return field.get(source);
164+
Object fieldValue = getValueFromField(source, fieldName);
165+
if (fieldValue != null) return fieldValue;
145166

146167
throw new NoSuchFieldException("No GraphQL field found");
147168
}
148169

149-
private boolean getValueFromField(Field field) throws IllegalAccessException {
170+
private Object getValueFromField(Object source, String fieldName) throws IllegalAccessException {
171+
List<String> namesToSearchFor = Arrays.asList(fieldName, extractPrefixedName(fieldName));
172+
for (String name : namesToSearchFor) {
173+
Field field = getField(source.getClass(), name);
174+
if (isFieldContainsValue(field)) {
175+
return field.get(source);
176+
}
177+
}
178+
return null;
179+
}
180+
181+
private boolean isFieldContainsValue(Field field) throws IllegalAccessException {
150182
if (field != null) {
151183
field.setAccessible(true);
152184
return true;
153185
}
154186
return false;
155187
}
156188

189+
private Field getField(Class<?> clazz, String name) {
190+
Field field = null;
191+
while (clazz != null && field == null) {
192+
try {
193+
field = clazz.getDeclaredField(name);
194+
} catch (Exception ignored) {
195+
}
196+
clazz = clazz.getSuperclass();
197+
}
198+
return field;
199+
}
200+
157201
private Object getValueFromMethod(Object source, String fieldName) throws IllegalAccessException, InvocationTargetException {
158202
String[] orderedPrefixes = new String[]{"", "get", "is"};
159203
for (String orderedPrefix : orderedPrefixes) {
@@ -166,7 +210,13 @@ private Object getValueFromMethod(Object source, String fieldName) throws Illega
166210
}
167211

168212
private Method getMethod(Class<?> clazz, String name, String prefix) {
169-
String prefixedName = addPrefixToPropertyName(prefix, name);
213+
String prefixedName;
214+
if (prefix.isEmpty()) {
215+
prefixedName = name;
216+
} else {
217+
prefixedName = addPrefixToPropertyName(prefix, name);
218+
}
219+
170220
Method method = null;
171221
while (clazz != null && method == null) {
172222
try {
@@ -179,16 +229,4 @@ private Method getMethod(Class<?> clazz, String name, String prefix) {
179229
return method;
180230
}
181231

182-
private Field getField(Class<?> clazz, String name) {
183-
Field field = null;
184-
while (clazz != null && field == null) {
185-
try {
186-
field = clazz.getDeclaredField(name);
187-
} catch (Exception ignored) {
188-
}
189-
clazz = clazz.getSuperclass();
190-
}
191-
return field;
192-
}
193-
194232
}

src/main/java/graphql/annotations/processor/util/PrefixesUtil.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,13 @@ public class PrefixesUtil {
1818
public static String addPrefixToPropertyName(String prefix, String propertyName) {
1919
return prefix + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
2020
}
21+
22+
public static String extractPrefixedName(String name) {
23+
if (name.startsWith("is")) {
24+
return name.replaceFirst("^is", "").toLowerCase();
25+
} else if (name.startsWith("get")) {
26+
return name.replaceFirst("^get", "").toLowerCase();
27+
}
28+
return name;
29+
}
2130
}

src/test/java/graphql/annotations/MethodDataFetcherTest.java

Lines changed: 114 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import org.testng.annotations.BeforeMethod;
2626
import org.testng.annotations.Test;
2727

28-
import javax.xml.crypto.Data;
2928
import java.util.ArrayList;
3029
import java.util.HashMap;
3130
import java.util.Map;
@@ -95,10 +94,12 @@ public void query_onlyApiClass_valueIsDeterminedByMethod() throws Exception {
9594
}
9695

9796
/**
98-
* Case 3: Api and a DB class, value is determined by the db field
97+
* Case 3: Api and a DB class with polymorphism, value is determined by the db field
98+
* name of api method <-> name of db field
9999
*/
100100
public static class Api3 {
101101
@GraphQLField
102+
@GraphQLName("nameX")
102103
public String name() {
103104
return "dani";
104105
}
@@ -131,17 +132,19 @@ public void query_apiAndDbClass_valueIsDeterminedByDBField() throws Exception {
131132
GraphQLObjectType object = GraphQLAnnotations.object(Query3.class);
132133
GraphQLSchema schema = newSchema().query(object).build();
133134

134-
ExecutionResult result = GraphQL.newGraphQL(schema).build().execute(builder -> builder.query("query { queryField { name } }").root(new Query3()));
135+
ExecutionResult result = GraphQL.newGraphQL(schema).build().execute(builder -> builder.query("query { queryField { nameX } }").root(new Query3()));
135136
assertTrue(result.getErrors().isEmpty());
136-
assertEquals(((Map<String, Map<String, String>>) result.getData()).get("queryField").get("name").toString(), "osher");
137+
assertEquals(((Map<String, Map<String, String>>) result.getData()).get("queryField").get("nameX").toString(), "osher");
137138
}
138139

139140
/**
140141
* Case 4: Api and DB classes, value is determined by db method
142+
* api method name <-> (`get`) + db method name
141143
*/
142144

143145
public static class Api4 {
144146
@GraphQLField
147+
@GraphQLName("nameX")
145148
public String name() {
146149
return null;
147150
}
@@ -173,15 +176,116 @@ public static class Query4 {
173176
}
174177

175178
@Test
176-
public void query_apiAndDbClass_valueIsDeterminedByDBMethod() throws Exception {
179+
public void query_apiAndDbClass_valueIsDeterminedByGetPrefixDBMethod() throws Exception {
177180
GraphQLObjectType object = GraphQLAnnotations.object(Query4.class);
178181
GraphQLSchema schema = newSchema().query(object).build();
179182

180-
ExecutionResult result = GraphQL.newGraphQL(schema).build().execute(builder -> builder.query("query { queryField { name } }").root(new Query4()));
183+
ExecutionResult result = GraphQL.newGraphQL(schema).build().execute(builder -> builder.query("query { queryField { nameX } }").root(new Query4()));
184+
assertTrue(result.getErrors().isEmpty());
185+
assertEquals(((Map<String, Map<String, String>>) result.getData()).get("queryField").get("nameX").toString(), "guy/yarin");
186+
}
187+
188+
/**
189+
* Case: Api and DB classes, value is determined by db method
190+
* api method name <-> (`is`) + db method name
191+
*/
192+
193+
public static class Api6 {
194+
@GraphQLField
195+
@GraphQLName("nameX")
196+
public String name() {
197+
return null;
198+
}
199+
}
200+
201+
public static class SuperDB6 {
202+
private String name = "guy";
203+
204+
public String isName() {
205+
return name + "/yarin";
206+
}
207+
}
208+
209+
public static class DB6 extends SuperDB6 {
210+
}
211+
212+
public static class Api6Resolver implements DataFetcher<DB6> {
213+
214+
@Override
215+
public DB6 get(DataFetchingEnvironment environment) {
216+
return new DB6();
217+
}
218+
}
219+
220+
public static class Query6 {
221+
@GraphQLField
222+
@GraphQLDataFetcher(Api6Resolver.class)
223+
public Api6 queryField;
224+
}
225+
226+
@Test
227+
public void query_apiAndDbClass_valueIsDeterminedByIsPrefixDBMethod() throws Exception {
228+
GraphQLObjectType object = GraphQLAnnotations.object(Query6.class);
229+
GraphQLSchema schema = newSchema().query(object).build();
230+
231+
ExecutionResult result = GraphQL.newGraphQL(schema).build().execute(builder -> builder.query("query { queryField { nameX } }").root(new Query6()));
181232
assertTrue(result.getErrors().isEmpty());
182-
assertEquals(((Map<String, Map<String, String>>) result.getData()).get("queryField").get("name").toString(), "guy/yarin");
233+
assertEquals(((Map<String, Map<String, String>>) result.getData()).get("queryField").get("nameX").toString(), "guy/yarin");
183234
}
184235

236+
/**
237+
* Case: Api and DB classes, value is determined by db method
238+
* api method name <-> db method name
239+
*/
240+
241+
public static class Api7 {
242+
@GraphQLField
243+
@GraphQLName("nameX")
244+
public String name() {
245+
return null;
246+
}
247+
}
248+
249+
public static class SuperDB7 {
250+
private String name = "guy";
251+
252+
public String name() {
253+
return name + "/yarin";
254+
}
255+
256+
public String isName() {
257+
return "blabla";
258+
}
259+
}
260+
261+
public static class DB7 extends SuperDB7 {
262+
}
263+
264+
public static class Api7Resolver implements DataFetcher<DB7> {
265+
266+
@Override
267+
public DB7 get(DataFetchingEnvironment environment) {
268+
return new DB7();
269+
}
270+
}
271+
272+
public static class Query7 {
273+
@GraphQLField
274+
@GraphQLDataFetcher(Api7Resolver.class)
275+
public Api7 queryField;
276+
}
277+
278+
@Test
279+
public void query_apiAndDbClass_valueIsDeterminedByDBMethod() throws Exception {
280+
GraphQLObjectType object = GraphQLAnnotations.object(Query7.class);
281+
GraphQLSchema schema = newSchema().query(object).build();
282+
283+
ExecutionResult result = GraphQL.newGraphQL(schema).build().execute(builder -> builder.query("query { queryField { nameX } }").root(new Query7()));
284+
assertTrue(result.getErrors().isEmpty());
285+
assertEquals(((Map<String, Map<String, String>>) result.getData()).get("queryField").get("nameX").toString(), "guy/yarin");
286+
}
287+
288+
185289
/**
186290
* Case 5: Invoke Detached on method, both api and db classes, value is determined by the api method
187291
*/
@@ -215,8 +319,6 @@ public static class Query5 {
215319
public Api5 queryField;
216320
}
217321

218-
/////////////////////////////////////////
219-
220322
@Test
221323
public void query_apiAndDbClassAndApiIsInvokeDetached_valueIsDeterminedByApiMethod() throws Exception {
222324
GraphQLObjectType object = GraphQLAnnotations.object(Query5.class);
@@ -227,6 +329,9 @@ public void query_apiAndDbClassAndApiIsInvokeDetached_valueIsDeterminedByApiMeth
227329
assertEquals(((Map<String, Map<String, String>>) result.getData()).get("queryField").get("name").toString(), "yarin/guy/osher");
228330
}
229331

332+
/////////////////////////////////////////
333+
/////////////////////////////////////////
334+
/////////////////////////////////////////
230335

231336
public class TestException extends Exception {
232337
}
@@ -276,8 +381,6 @@ public int c() {
276381
public CanonizedTypeApi getCanonizedType() {
277382
return null;
278383
}
279-
280-
281384
}
282385

283386
public static class CanonizedFetcher implements DataFetcher<CanonizedType> {

0 commit comments

Comments
 (0)