Skip to content

Commit 137199f

Browse files
committed
Start work on LegacyCoercing support
1 parent 315b396 commit 137199f

2 files changed

Lines changed: 192 additions & 0 deletions

File tree

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package graphql.execution.values.legacycoercing;
2+
3+
import graphql.GraphQLContext;
4+
import graphql.Scalars;
5+
import graphql.execution.values.InputInterceptor;
6+
import graphql.scalar.CoercingUtil;
7+
import graphql.schema.GraphQLInputType;
8+
import org.jetbrains.annotations.NotNull;
9+
import org.jetbrains.annotations.Nullable;
10+
11+
import java.math.BigDecimal;
12+
import java.util.Locale;
13+
import java.util.function.BiConsumer;
14+
import java.util.function.BiFunction;
15+
16+
import static graphql.Assert.assertNotNull;
17+
import static graphql.Assert.assertShouldNeverHappen;
18+
import static graphql.scalar.CoercingUtil.isNumberIsh;
19+
20+
public class LegacyCoercingInputInterceptor implements InputInterceptor {
21+
22+
/**
23+
* This will ONLY observe legacy values and invoke the callback when it gets one. you can use this to enumerate how many
24+
* legacy values are hitting you graphql implementation
25+
*
26+
* @param observerCallback a callback allowing you to observe a legacy scalar value
27+
*
28+
* @return an InputInterceptor that only observes values
29+
*/
30+
public static LegacyCoercingInputInterceptor observeValues(BiConsumer<Object, GraphQLInputType> observerCallback) {
31+
return new LegacyCoercingInputInterceptor(((input, graphQLInputType) -> {
32+
observerCallback.accept(input, graphQLInputType);
33+
return input;
34+
}));
35+
}
36+
37+
/**
38+
* This will ONLY observe legacy values and invoke the callback when it gets one. you can use this to enumerate how many
39+
* legacy values are hitting you graphql implementation
40+
*
41+
* @param observerCallback a callback allowing you to observe a legacy scalar value before it is migrated
42+
*
43+
* @return an InputInterceptor that both observes values and migrates them to a more strict value
44+
*/
45+
public static LegacyCoercingInputInterceptor migrateValues(BiConsumer<Object, GraphQLInputType> observerCallback) {
46+
return new LegacyCoercingInputInterceptor(((input, graphQLInputType) -> {
47+
observerCallback.accept(input, graphQLInputType);
48+
if (Scalars.GraphQLBoolean.equals(graphQLInputType)) {
49+
return coerceLegacyBooleanValue(input);
50+
}
51+
if (Scalars.GraphQLFloat.equals(graphQLInputType)) {
52+
return coerceLegacyFloatValue(input);
53+
}
54+
if (Scalars.GraphQLInt.equals(graphQLInputType)) {
55+
return coerceLegacyIntValue(input);
56+
}
57+
if (Scalars.GraphQLString.equals(graphQLInputType)) {
58+
return coerceLegacyStringValue(input);
59+
}
60+
return input;
61+
}));
62+
}
63+
64+
private final BiFunction<Object, GraphQLInputType, Object> behavior;
65+
66+
private LegacyCoercingInputInterceptor(BiFunction<Object, GraphQLInputType, Object> behavior) {
67+
this.behavior = assertNotNull(behavior);
68+
}
69+
70+
@Override
71+
public Object intercept(@Nullable Object input, @NotNull GraphQLInputType graphQLType, @NotNull GraphQLContext graphqlContext, @NotNull Locale locale) {
72+
if (isLegacyValue(input, graphQLType)) {
73+
// we ONLY apply the new behavior IF it's an old acceptable legacy value.
74+
// so for compliant values - we change nothing and invoke no behaviour
75+
// and for values that would not coerce anyway, we also invoke no behavior
76+
return behavior.apply(input, graphQLType);
77+
}
78+
return input;
79+
}
80+
81+
@SuppressWarnings("RedundantIfStatement")
82+
static boolean isLegacyValue(Object input, GraphQLInputType graphQLType) {
83+
if (Scalars.GraphQLBoolean.equals(graphQLType)) {
84+
return isLegacyBooleanValue(input);
85+
} else if (Scalars.GraphQLFloat.equals(graphQLType)) {
86+
return isLegacyFloatValue(input);
87+
} else if (Scalars.GraphQLInt.equals(graphQLType)) {
88+
return isLegacyIntValue(input);
89+
} else if (Scalars.GraphQLString.equals(graphQLType)) {
90+
return true;
91+
} else {
92+
return false;
93+
}
94+
}
95+
96+
static boolean isLegacyBooleanValue(Object input) {
97+
return input instanceof String || CoercingUtil.isNumberIsh(input);
98+
}
99+
100+
static boolean isLegacyFloatValue(Object input) {
101+
return input instanceof String;
102+
}
103+
104+
static boolean isLegacyIntValue(Object input) {
105+
return input instanceof String;
106+
}
107+
108+
static Object coerceLegacyBooleanValue(Object input) {
109+
if (input instanceof String) {
110+
String lStr = ((String) input).toLowerCase();
111+
if (lStr.equals("true")) {
112+
return true;
113+
}
114+
if (lStr.equals("false")) {
115+
return false;
116+
}
117+
return null;
118+
} else if (isNumberIsh(input)) {
119+
BigDecimal value;
120+
try {
121+
value = new BigDecimal(input.toString());
122+
} catch (NumberFormatException e) {
123+
// this should never happen because String is handled above
124+
return assertShouldNeverHappen();
125+
}
126+
return value.compareTo(BigDecimal.ZERO) != 0;
127+
}
128+
// unchanged
129+
return input;
130+
}
131+
132+
static Object coerceLegacyFloatValue(Object input) {
133+
if (isNumberIsh(input)) {
134+
BigDecimal value;
135+
try {
136+
value = new BigDecimal(input.toString());
137+
} catch (NumberFormatException e) {
138+
return null;
139+
}
140+
return value.doubleValue();
141+
}
142+
return input;
143+
}
144+
145+
static Object coerceLegacyIntValue(Object input) {
146+
if (isNumberIsh(input)) {
147+
BigDecimal value;
148+
try {
149+
value = new BigDecimal(input.toString());
150+
} catch (NumberFormatException e) {
151+
return null;
152+
}
153+
try {
154+
return value.intValueExact();
155+
} catch (ArithmeticException e) {
156+
return null;
157+
}
158+
}
159+
return input;
160+
}
161+
162+
163+
static Object coerceLegacyStringValue(Object input) {
164+
return String.valueOf(input);
165+
}
166+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package graphql.execution.values.legacycoercing
2+
3+
import graphql.Scalars
4+
import spock.lang.Specification
5+
6+
class LegacyCoercingInputInterceptorTest extends Specification {
7+
8+
def "can detect boolean legacy values"() {
9+
when:
10+
def isLegacyValue = LegacyCoercingInputInterceptor.isLegacyValue(input, inputType)
11+
then:
12+
isLegacyValue == expected
13+
14+
where:
15+
input | inputType | expected
16+
"true" | Scalars.GraphQLBoolean | true
17+
"false" | Scalars.GraphQLBoolean | true
18+
"TRUE" | Scalars.GraphQLBoolean | true
19+
"FALSE" | Scalars.GraphQLBoolean | true
20+
"junk" | Scalars.GraphQLBoolean | true
21+
// not acceptable to the old
22+
true | Scalars.GraphQLBoolean | false
23+
false | Scalars.GraphQLBoolean | false
24+
["rubbish"] | Scalars.GraphQLBoolean | false
25+
}
26+
}

0 commit comments

Comments
 (0)