Skip to content

Commit e4004d3

Browse files
committed
105 - lenient numerical scalars
1 parent f4ebaa1 commit e4004d3

2 files changed

Lines changed: 402 additions & 253 deletions

File tree

src/main/java/graphql/Scalars.java

Lines changed: 136 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,45 @@ public class Scalars {
2222
private static final BigInteger SHORT_MAX = BigInteger.valueOf(Short.MAX_VALUE);
2323
private static final BigInteger SHORT_MIN = BigInteger.valueOf(Short.MIN_VALUE);
2424

25+
private static boolean isWholeNumber(Object input) {
26+
return input instanceof Long
27+
|| input instanceof Integer
28+
|| input instanceof Short
29+
|| input instanceof Byte;
30+
}
31+
32+
// true if its a number or string that we will attempt to convert to a number via toNumber()
33+
private static boolean isNumberIsh(Object input) {
34+
return input instanceof Number || input instanceof String;
35+
}
36+
37+
private static Number toNumber(Object input) {
38+
if (input instanceof Number) {
39+
return (Number) input;
40+
}
41+
if (input instanceof String) {
42+
// we go to double and then let each scalar type decide what precision they want from it. This
43+
// will allow lenient behavior in string input as well as Number input... eg "42.3" as a string to a Long
44+
// scalar is the same as new Double(42.3) to a Long scalar.
45+
//
46+
// each type will use Java Narrow casting to turn this into the desired type (Long, Integer, Short etc...)
47+
//
48+
// See http://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.1.3
49+
//
50+
return Double.parseDouble((String) input);
51+
}
52+
// we never expect this and if we do, the code is wired wrong
53+
throw new AssertException("Unexpected case - this call should be protected by a previous call to isNumberIsh()");
54+
}
55+
56+
2557
public static GraphQLScalarType GraphQLInt = new GraphQLScalarType("Int", "Built-in Int", new Coercing<Integer>() {
2658
@Override
2759
public Integer serialize(Object input) {
28-
if (input instanceof String) {
29-
return Integer.parseInt((String) input);
30-
} else if (input instanceof Integer) {
60+
if (input instanceof Integer) {
3161
return (Integer) input;
62+
} else if (isNumberIsh(input)) {
63+
return toNumber(input).intValue();
3264
} else {
3365
return null;
3466
}
@@ -50,16 +82,13 @@ public Integer parseLiteral(Object input) {
5082
}
5183
});
5284

53-
5485
public static GraphQLScalarType GraphQLLong = new GraphQLScalarType("Long", "Long type", new Coercing<Long>() {
5586
@Override
5687
public Long serialize(Object input) {
57-
if (input instanceof String) {
58-
return Long.parseLong((String) input);
59-
} else if (input instanceof Long) {
88+
if (input instanceof Long) {
6089
return (Long) input;
61-
} else if (input instanceof Integer) {
62-
return ((Integer) input).longValue();
90+
} else if (isNumberIsh(input)) {
91+
return toNumber(input).longValue();
6392
} else {
6493
return null;
6594
}
@@ -86,112 +115,89 @@ public Long parseLiteral(Object input) {
86115
}
87116
});
88117

89-
public static GraphQLScalarType GraphQLFloat = new GraphQLScalarType("Float", "Built-in Float", new Coercing<Double>() {
118+
public static GraphQLScalarType GraphQLShort = new GraphQLScalarType("Short", "Built-in Short as Int", new Coercing<Short>() {
90119
@Override
91-
public Double serialize(Object input) {
92-
if (input instanceof String) {
93-
return Double.parseDouble((String) input);
94-
} else if (input instanceof Double) {
95-
return (Double) input;
96-
} else if (input instanceof Float) {
97-
return (double) (Float) input;
98-
} else if (input instanceof Integer) {
99-
return (double) (Integer) input;
120+
public Short serialize(Object input) {
121+
if (input instanceof Short) {
122+
return (Short) input;
123+
} else if (isNumberIsh(input)) {
124+
return toNumber(input).shortValue();
100125
} else {
101126
return null;
102127
}
103128
}
104129

105130
@Override
106-
public Double parseValue(Object input) {
131+
public Short parseValue(Object input) {
107132
return serialize(input);
108133
}
109134

110135
@Override
111-
public Double parseLiteral(Object input) {
112-
if (input instanceof IntValue) {
113-
return ((IntValue) input).getValue().doubleValue();
114-
} else if (input instanceof FloatValue) {
115-
return ((FloatValue) input).getValue().doubleValue();
116-
} else {
117-
return null;
136+
public Short parseLiteral(Object input) {
137+
if (!(input instanceof IntValue)) return null;
138+
BigInteger value = ((IntValue) input).getValue();
139+
if (value.compareTo(SHORT_MIN) < 0 || value.compareTo(SHORT_MAX) > 0) {
140+
throw new GraphQLException("Int literal is too big or too small for a short, would cause overflow");
118141
}
142+
return value.shortValue();
119143
}
120144
});
121145

122-
public static GraphQLScalarType GraphQLString = new GraphQLScalarType("String", "Built-in String", new Coercing<String>() {
123-
@Override
124-
public String serialize(Object input) {
125-
return input == null ? null : input.toString();
126-
}
127-
128-
@Override
129-
public String parseValue(Object input) {
130-
return serialize(input);
131-
}
132-
133-
@Override
134-
public String parseLiteral(Object input) {
135-
if (!(input instanceof StringValue)) return null;
136-
return ((StringValue) input).getValue();
137-
}
138-
});
139-
140-
141-
public static GraphQLScalarType GraphQLBoolean = new GraphQLScalarType("Boolean", "Built-in Boolean", new Coercing<Boolean>() {
146+
public static GraphQLScalarType GraphQLByte = new GraphQLScalarType("Byte", "Built-in Byte as Int", new Coercing<Byte>() {
142147
@Override
143-
public Boolean serialize(Object input) {
144-
if (input instanceof Boolean) {
145-
return (Boolean) input;
146-
} else if (input instanceof Integer) {
147-
return (Integer) input > 0;
148-
} else if (input instanceof String) {
149-
return Boolean.parseBoolean((String) input);
148+
public Byte serialize(Object input) {
149+
if (input instanceof Byte) {
150+
return (Byte) input;
151+
} else if (isNumberIsh(input)) {
152+
return toNumber(input).byteValue();
150153
} else {
151154
return null;
152155
}
153156
}
154157

155158
@Override
156-
public Boolean parseValue(Object input) {
159+
public Byte parseValue(Object input) {
157160
return serialize(input);
158161
}
159162

160163
@Override
161-
public Boolean parseLiteral(Object input) {
162-
if (!(input instanceof BooleanValue)) return null;
163-
return ((BooleanValue) input).isValue();
164+
public Byte parseLiteral(Object input) {
165+
if (!(input instanceof IntValue)) return null;
166+
BigInteger value = ((IntValue) input).getValue();
167+
if (value.compareTo(BYTE_MIN) < 0 || value.compareTo(BYTE_MAX) > 0) {
168+
throw new GraphQLException("Int literal is too big or too small for a byte, would cause overflow");
169+
}
170+
return value.byteValue();
164171
}
165172
});
166173

167174

168-
public static GraphQLScalarType GraphQLID = new GraphQLScalarType("ID", "Built-in ID", new Coercing<Object>() {
175+
public static GraphQLScalarType GraphQLFloat = new GraphQLScalarType("Float", "Built-in Float", new Coercing<Double>() {
169176
@Override
170-
public Object serialize(Object input) {
171-
if (input instanceof String) {
172-
return input;
173-
}
174-
if (input instanceof Integer) {
175-
return String.valueOf(input);
177+
public Double serialize(Object input) {
178+
if (input instanceof Double) {
179+
return (Double) input;
180+
} else if (isNumberIsh(input)) {
181+
return toNumber(input).doubleValue();
182+
} else {
183+
return null;
176184
}
177-
178-
return null;
179185
}
180186

181187
@Override
182-
public Object parseValue(Object input) {
188+
public Double parseValue(Object input) {
183189
return serialize(input);
184190
}
185191

186192
@Override
187-
public Object parseLiteral(Object input) {
188-
if (input instanceof StringValue) {
189-
return ((StringValue) input).getValue();
190-
}
193+
public Double parseLiteral(Object input) {
191194
if (input instanceof IntValue) {
192-
return ((IntValue) input).getValue().toString();
195+
return ((IntValue) input).getValue().doubleValue();
196+
} else if (input instanceof FloatValue) {
197+
return ((FloatValue) input).getValue().doubleValue();
198+
} else {
199+
return null;
193200
}
194-
return null;
195201
}
196202
});
197203

@@ -202,10 +208,8 @@ public BigInteger serialize(Object input) {
202208
return (BigInteger) input;
203209
} else if (input instanceof String) {
204210
return new BigInteger((String) input);
205-
} else if (input instanceof Integer) {
206-
return BigInteger.valueOf((Integer) input);
207-
} else if (input instanceof Long) {
208-
return BigInteger.valueOf((Long) input);
211+
} else if (isNumberIsh(input)) {
212+
return BigInteger.valueOf(toNumber(input).longValue());
209213
} else {
210214
return null;
211215
}
@@ -234,14 +238,10 @@ public BigDecimal serialize(Object input) {
234238
return (BigDecimal) input;
235239
} else if (input instanceof String) {
236240
return new BigDecimal((String) input);
237-
} else if (input instanceof Float) {
238-
return BigDecimal.valueOf((Float) input);
239-
} else if (input instanceof Double) {
240-
return BigDecimal.valueOf((Double) input);
241-
} else if (input instanceof Integer) {
242-
return BigDecimal.valueOf((Integer) input);
243-
} else if (input instanceof Long) {
244-
return BigDecimal.valueOf((Long) input);
241+
} else if (isWholeNumber(input)) {
242+
return BigDecimal.valueOf(toNumber(input).longValue());
243+
} else if (input instanceof Number) {
244+
return BigDecimal.valueOf(toNumber(input).doubleValue());
245245
} else {
246246
return null;
247247
}
@@ -265,62 +265,84 @@ public BigDecimal parseLiteral(Object input) {
265265
}
266266
});
267267

268-
public static GraphQLScalarType GraphQLByte = new GraphQLScalarType("Byte", "Built-in Byte as Int", new Coercing<Byte>() {
268+
269+
public static GraphQLScalarType GraphQLString = new GraphQLScalarType("String", "Built-in String", new Coercing<String>() {
269270
@Override
270-
public Byte serialize(Object input) {
271-
if (input instanceof String) {
272-
return Byte.parseByte((String) input);
273-
} else if (input instanceof Byte) {
274-
return (Byte) input;
271+
public String serialize(Object input) {
272+
return input == null ? null : input.toString();
273+
}
274+
275+
@Override
276+
public String parseValue(Object input) {
277+
return serialize(input);
278+
}
279+
280+
@Override
281+
public String parseLiteral(Object input) {
282+
if (!(input instanceof StringValue)) return null;
283+
return ((StringValue) input).getValue();
284+
}
285+
});
286+
287+
288+
public static GraphQLScalarType GraphQLBoolean = new GraphQLScalarType("Boolean", "Built-in Boolean", new Coercing<Boolean>() {
289+
@Override
290+
public Boolean serialize(Object input) {
291+
if (input instanceof Boolean) {
292+
return (Boolean) input;
293+
} else if (input instanceof Integer) {
294+
return (Integer) input > 0;
295+
} else if (input instanceof String) {
296+
return Boolean.parseBoolean((String) input);
275297
} else {
276298
return null;
277299
}
278300
}
279301

280302
@Override
281-
public Byte parseValue(Object input) {
303+
public Boolean parseValue(Object input) {
282304
return serialize(input);
283305
}
284306

285307
@Override
286-
public Byte parseLiteral(Object input) {
287-
if (!(input instanceof IntValue)) return null;
288-
BigInteger value = ((IntValue) input).getValue();
289-
if (value.compareTo(BYTE_MIN) < 0 || value.compareTo(BYTE_MAX) > 0) {
290-
throw new GraphQLException("Int literal is too big or too small for a byte, would cause overflow");
291-
}
292-
return value.byteValue();
308+
public Boolean parseLiteral(Object input) {
309+
if (!(input instanceof BooleanValue)) return null;
310+
return ((BooleanValue) input).isValue();
293311
}
294312
});
295313

296-
public static GraphQLScalarType GraphQLShort = new GraphQLScalarType("Short", "Built-in Short as Int", new Coercing<Short>() {
314+
315+
public static GraphQLScalarType GraphQLID = new GraphQLScalarType("ID", "Built-in ID", new Coercing<Object>() {
297316
@Override
298-
public Short serialize(Object input) {
317+
public Object serialize(Object input) {
299318
if (input instanceof String) {
300-
return Short.parseShort((String) input);
301-
} else if (input instanceof Short) {
302-
return (Short) input;
303-
} else {
304-
return null;
319+
return input;
320+
}
321+
if (input instanceof Integer) {
322+
return String.valueOf(input);
305323
}
324+
325+
return null;
306326
}
307327

308328
@Override
309-
public Short parseValue(Object input) {
329+
public Object parseValue(Object input) {
310330
return serialize(input);
311331
}
312332

313333
@Override
314-
public Short parseLiteral(Object input) {
315-
if (!(input instanceof IntValue)) return null;
316-
BigInteger value = ((IntValue) input).getValue();
317-
if (value.compareTo(SHORT_MIN) < 0 || value.compareTo(SHORT_MAX) > 0) {
318-
throw new GraphQLException("Int literal is too big or too small for a short, would cause overflow");
334+
public Object parseLiteral(Object input) {
335+
if (input instanceof StringValue) {
336+
return ((StringValue) input).getValue();
319337
}
320-
return value.shortValue();
338+
if (input instanceof IntValue) {
339+
return ((IntValue) input).getValue().toString();
340+
}
341+
return null;
321342
}
322343
});
323344

345+
324346
public static GraphQLScalarType GraphQLChar = new GraphQLScalarType("Char", "Built-in Char as Character", new Coercing<Character>() {
325347
@Override
326348
public Character serialize(Object input) {

0 commit comments

Comments
 (0)