-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Float coercion tidy up #2982
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Float coercion tidy up #2982
Conversation
| return null; | ||
| } | ||
| return doubleInput; | ||
| } else if (isNumberIsh(input)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not add a separate else if (input instanceof Float) branch because it causes some tests to fail.
Getting the doubleValue() from a BigDecimal produces an ever-so-slightly-different result to getting the doubleValue() out of a Float.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Current this is
private Double convertImpl(Object input) {
// From the GraphQL Float spec, non-finite floating-point internal values (NaN and Infinity)
// must raise a field error on both result and input coercion
if (input instanceof Double) {
Double doubleInput = (Double) input;
if (Double.isNaN(doubleInput) || Double.isInfinite(doubleInput)) {
return null;
}
return doubleInput;
} else if (isNumberIsh(input)) {
BigDecimal value;
try {
value = new BigDecimal(input.toString());
} catch (NumberFormatException e) {
return null;
}
return value.doubleValue();
} else {
return null;
}
}
So it does not apply the checks to the BigDecimal derived string
Why not do
private Double convertImpl(Object input) {
// From the GraphQL Float spec, non-finite floating-point internal values (NaN and Infinity)
// must raise a field error on both result and input coercion
Double doubleInput;
if (input instanceof Double) {
doubleInput = (Double) input;
} else if (isNumberIsh(input)) {
BigDecimal value;
try {
value = new BigDecimal(input.toString());
} catch (NumberFormatException e) {
return null;
}
doubleInput = value.doubleValue();
} else {
return null;
}
if (Double.isNaN(doubleInput) || Double.isInfinite(doubleInput)) {
return null;
}
return doubleInput
}
Forgive me if this doesnt compile
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems more sensible - eg first get it into a double and then check it - regardless
Even if BigDecimal never produces Nan or infinity, the logic reads better
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh no, it can produce infinity! This was a great idea
|
|
||
| public static final GraphQLObjectType queryType = newObject() | ||
| .name("QueryType") | ||
| // Static scalars |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was added a long time ago, before the GraphQL Float spec was updated to specifically not allow NaN and positive/negative infinity values
bbakerman
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we tweak it as suggested or justify why not :)
| return null; | ||
| } | ||
| return doubleInput; | ||
| } else if (isNumberIsh(input)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Current this is
private Double convertImpl(Object input) {
// From the GraphQL Float spec, non-finite floating-point internal values (NaN and Infinity)
// must raise a field error on both result and input coercion
if (input instanceof Double) {
Double doubleInput = (Double) input;
if (Double.isNaN(doubleInput) || Double.isInfinite(doubleInput)) {
return null;
}
return doubleInput;
} else if (isNumberIsh(input)) {
BigDecimal value;
try {
value = new BigDecimal(input.toString());
} catch (NumberFormatException e) {
return null;
}
return value.doubleValue();
} else {
return null;
}
}
So it does not apply the checks to the BigDecimal derived string
Why not do
private Double convertImpl(Object input) {
// From the GraphQL Float spec, non-finite floating-point internal values (NaN and Infinity)
// must raise a field error on both result and input coercion
Double doubleInput;
if (input instanceof Double) {
doubleInput = (Double) input;
} else if (isNumberIsh(input)) {
BigDecimal value;
try {
value = new BigDecimal(input.toString());
} catch (NumberFormatException e) {
return null;
}
doubleInput = value.doubleValue();
} else {
return null;
}
if (Double.isNaN(doubleInput) || Double.isInfinite(doubleInput)) {
return null;
}
return doubleInput
}
Forgive me if this doesnt compile
| return null; | ||
| } | ||
| return doubleInput; | ||
| } else if (isNumberIsh(input)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems more sensible - eg first get it into a double and then check it - regardless
Even if BigDecimal never produces Nan or infinity, the logic reads better
| Double.NEGATIVE_INFINITY | _ | ||
| Float.NaN | _ | ||
| Float.POSITIVE_INFINITY | _ | ||
| Float.NEGATIVE_INFINITY | _ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we have a string rep here? Float.NEGATIVE_INFINITY.toString() ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public static void main(String[] args) {
String negInf = String.valueOf(Float.NEGATIVE_INFINITY);
System.out.println(negInf);
double dv = new BigDecimal(negInf).doubleValue();
System.out.println(dv);
}
-Infinity
Exception in thread "main" java.lang.NumberFormatException
at java.math.BigDecimal.<init>(BigDecimal.java:494)
at java.math.BigDecimal.<init>(BigDecimal.java:383)
at java.math.BigDecimal.<init>(BigDecimal.java:806)
So its not acceptable but lets test anyway to show that we handle it
| final Object serialized = serializeLegacy(type, value, graphqlContext, locale); | ||
| if (isNullishLegacy(serialized)) { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This NaN check was unnecessary. The NaN check already happened inside the serialize method.
| if (Double.isNaN(doubleInput) || Double.isInfinite(doubleInput)) { | ||
| return null; | ||
| } | ||
| return doubleInput; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move NaN/Infinity check lower down to make this easier to read
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Edit: not only easier to read, it catches the edge case where value.doubleValue() returns positive/negative infinity
| Float.POSITIVE_INFINITY | _ | ||
| Float.POSITIVE_INFINITY.toString() | _ | ||
| Float.NEGATIVE_INFINITY | _ | ||
| Float.NEGATIVE_INFINITY.toString() | _ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add String representations to test
| @@ -1,6 +1,13 @@ | |||
| package graphql.language | |||
| package graphql.execution | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tidy up package name and constructor usage
bbakerman
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merge away
The GraphQL Float spec was updated a year ago to specifically not allow NaN and positive/negative infinity in both result coercion and input coercion
https://spec.graphql.org/draft/#sec-Float
graphql/graphql-spec#778
The float coercing was mostly compliant with this spec update, as NaN and positive/negative infinity are not allowed in the BigDecimal String constructor used in
GraphqlFloatCoercing, specifically the linevalue = new BigDecimal(input.toString());.I say mostly, there was one uncaught case. It's possible for the old
convertImplmethod to return a Double value that is later converted to positive infinity withBigDecimal.valueOf, such as in thevalueToLiteralImplThis PR introduces an explicit check for positive/negative infinity and NaN to address this problem. This PR cleans up NaN and infinity tests.