Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,11 @@ public static void addJson() {
populateScalar(AtomicInteger.class.getName(), INTEGER, Integer.class.getName());

// Float
populateScalar(Float.class.getName(), FLOAT, Float.class.getName());
populateScalar(float.class.getName(), FLOAT, Float.class.getName());
populateScalar(Double.class.getName(), FLOAT, Float.class.getName());
populateScalar(double.class.getName(), FLOAT, Float.class.getName());
populateScalar(OptionalDouble.class.getName(), FLOAT, Float.class.getName());
populateScalar(Float.class.getName(), FLOAT, Double.class.getName());
populateScalar(float.class.getName(), FLOAT, Double.class.getName());
populateScalar(Double.class.getName(), FLOAT, Double.class.getName());
populateScalar(double.class.getName(), FLOAT, Double.class.getName());
populateScalar(OptionalDouble.class.getName(), FLOAT, Double.class.getName());

// BigInteger
populateScalar(BigInteger.class.getName(), BIGINTEGER, BigInteger.class.getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
* Class helper
Expand Down Expand Up @@ -173,6 +178,11 @@ public static boolean isDateLikeType(String className) {
NUMBERS.add(LONG_PRIMATIVE);
NUMBERS.add(FLOAT);
NUMBERS.add(FLOAT_PRIMATIVE);
NUMBERS.add(AtomicInteger.class.getName());
NUMBERS.add(AtomicLong.class.getName());
NUMBERS.add(OptionalInt.class.getName());
NUMBERS.add(OptionalLong.class.getName());
NUMBERS.add(OptionalDouble.class.getName());

DATES.add(LOCALDATE);
DATES.add(LOCALTIME);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package io.smallrye.graphql.execution.datafetcher.helper;

import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand All @@ -27,6 +25,8 @@
import io.smallrye.graphql.schema.model.Wrapper;
import io.smallrye.graphql.spi.ClassloadingService;
import io.smallrye.graphql.transformation.AbstractDataFetcherException;
import io.smallrye.graphql.transformation.NumberTransformer;
import io.smallrye.graphql.transformation.TransformException;
import io.smallrye.graphql.transformation.Transformer;

/**
Expand Down Expand Up @@ -148,23 +148,33 @@ Object recursiveTransform(Object value, Field field, DataFetchingEnvironment dfe
// Also handle optionals
return recursiveTransformOptional(value, field, dfe);
} else if (field.getReference().getClassName().equals("java.util.OptionalInt")) {
if (value instanceof BigDecimal)
return OptionalInt.of(((BigDecimal) value).intValueExact());
if (value instanceof OptionalInt)
return value;
return OptionalInt.of((Integer) value);
try {
return OptionalInt.of(NumberTransformer.intValueExact((Number) value));
} catch (ArithmeticException e) {
throw new TransformException(e, field, value);
}
} else if (field.getReference().getClassName().equals("java.util.OptionalLong")) {
if (value instanceof BigDecimal)
return OptionalLong.of(((BigDecimal) value).longValueExact());
if (value instanceof OptionalLong)
return value;
return OptionalLong.of(((BigInteger) value).longValueExact());
try {
return OptionalLong.of(NumberTransformer.longValueExact((Number) value));
} catch (ArithmeticException e) {
throw new TransformException(e, field, value);
}
} else if (field.getReference().getClassName().equals("java.util.OptionalDouble")) {
if (value instanceof BigDecimal)
return OptionalDouble.of(((BigDecimal) value).doubleValue());
if (value instanceof OptionalDouble)
return value;
return OptionalDouble.of((Double) value);
try {
if (value instanceof OptionalDouble optional) {
if (optional.isPresent()) {
NumberTransformer.finiteDouble(optional.getAsDouble());
}
return value;
}
return OptionalDouble.of(NumberTransformer.doubleValueFinite((Number) value));
} catch (ArithmeticException e) {
throw new TransformException(e, field, value);
}
} else {
// we need to transform before we make sure the type is correct
value = singleTransform(value, field);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,23 @@
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.Set;

import graphql.schema.DataFetchingEnvironment;
import graphql.schema.GraphQLScalarType;
import io.smallrye.graphql.execution.Classes;
import io.smallrye.graphql.json.InputFieldsInfo;
import io.smallrye.graphql.json.JacksonCreator;
import io.smallrye.graphql.json.NumericInputField;
import io.smallrye.graphql.scalar.GraphQLScalarTypes;
import io.smallrye.graphql.schema.model.AdaptWith;
import io.smallrye.graphql.schema.model.Argument;
Expand All @@ -30,6 +35,7 @@
import io.smallrye.graphql.schema.model.Reference;
import io.smallrye.graphql.schema.model.ReferenceType;
import io.smallrye.graphql.transformation.AbstractDataFetcherException;
import io.smallrye.graphql.transformation.NumberTransformer;
import io.smallrye.graphql.transformation.TransformException;
import io.smallrye.graphql.transformation.Transformer;
import tools.jackson.core.JacksonException;
Expand Down Expand Up @@ -109,6 +115,14 @@ private Object getArgument(DataFetchingEnvironment dfe, Argument argument) throw
if (argument.hasWrapper() && argument.getWrapper().isOptional()) {
return Optional.empty();
}
String type = argument.getReference().getClassName();
if (type.equals(OptionalInt.class.getName())) {
return OptionalInt.empty();
} else if (type.equals(OptionalLong.class.getName())) {
return OptionalLong.empty();
} else if (type.equals(OptionalDouble.class.getName())) {
return OptionalDouble.empty();
}
return null;
}

Expand Down Expand Up @@ -213,9 +227,8 @@ private Object adaptInputWith(Field field, Object argumentValue, DataFetchingEnv
argumentValue = correctComplexObjectFromMap((Map) argumentValue, field, dfe);
}

argumentValue = coerceToParameterType(argumentValue, reflectionInvoker.getMethod());

try {
argumentValue = coerceToParameterType(argumentValue, reflectionInvoker.getMethod());
Object adaptedObject = reflectionInvoker.invoke(argumentValue);
return adaptedObject;
} catch (Exception ex) {
Expand All @@ -237,17 +250,17 @@ private static Object coerceToParameterType(Object value, java.lang.reflect.Meth
if (value instanceof Number) {
Number num = (Number) value;
if (expected == Long.class || expected == long.class) {
return num.longValue();
return NumberTransformer.longValueExact(num);
} else if (expected == Integer.class || expected == int.class) {
return num.intValue();
return NumberTransformer.intValueExact(num);
} else if (expected == Short.class || expected == short.class) {
return num.shortValue();
return NumberTransformer.shortValueExact(num);
} else if (expected == Byte.class || expected == byte.class) {
return num.byteValue();
return NumberTransformer.byteValueExact(num);
} else if (expected == Double.class || expected == double.class) {
return num.doubleValue();
return NumberTransformer.doubleValueFinite(num);
} else if (expected == Float.class || expected == float.class) {
return num.floatValue();
return NumberTransformer.floatValueFinite(num);
}
}
if (expected == String.class) {
Expand Down Expand Up @@ -377,6 +390,7 @@ private static boolean isJacksonJsonNodeType(String className) {
private Object correctComplexObjectFromMap(Map m, Field field, DataFetchingEnvironment dfe)
throws AbstractDataFetcherException {
String className = field.getReference().getClassName();
validateNumericInputFields(m, className);
Map<String, Object> targetValues = removeTargetValues(m, className);

// Let's see if there are any fields that needs transformation or adaption
Expand Down Expand Up @@ -476,6 +490,39 @@ private Map<String, Object> removeTargetValues(Map m, String className) {
return targetValues;
}

private void validateNumericInputFields(Map values, String className) throws AbstractDataFetcherException {
for (NumericInputField numericField : InputFieldsInfo.getNumericInputFields(className)) {
Field field = numericField.field();
Object value = values.get(field.getName());
if (value != null) {
try {
validateNumericInputValue(value, numericField);
} catch (ArithmeticException e) {
throw new TransformException(e, field, value);
}
}
}
}

private void validateNumericInputValue(Object value, NumericInputField numericField)
throws AbstractDataFetcherException {
if (value instanceof Number number) {
if (numericField.numberType() != null) {
numericField.numberType().checkRange(number);
}
} else if (value instanceof Collection<?> collection) {
for (Object element : collection) {
if (element != null) {
validateNumericInputValue(element, numericField);
}
}
} else if (value instanceof Map map && numericField.numberType() == null) {
validateNumericInputFields(map, numericField.field().getReference().getClassName());
} else if (value instanceof Optional<?> optional && optional.isPresent()) {
validateNumericInputValue(optional.get(), numericField);
}
}

private Object invokeTargetFields(Object target, String className, Map<String, Object> targetValues,
DataFetchingEnvironment dfe) throws AbstractDataFetcherException {
if (targetValues.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.Set;

import graphql.schema.DataFetchingEnvironment;
import io.smallrye.graphql.execution.Classes;
import io.smallrye.graphql.schema.model.AdaptWith;
import io.smallrye.graphql.schema.model.Field;
import io.smallrye.graphql.transformation.AbstractDataFetcherException;
Expand Down Expand Up @@ -103,6 +104,10 @@ protected Object afterRecursiveTransform(Object fieldValue, Field field, DataFet

@Override
protected Class<?> getArrayType(final Field field) {
if (!field.hasTransformation() && Classes.isNumberLikeType(field.getReference().getClassName())) {
// Unformatted numeric elements are serialized by the scalar without copying the array.
return classloadingService.loadClass(field.getReference().getClassName());
}
return classloadingService.loadClass(field.getReference().getGraphQLClassName());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import io.smallrye.graphql.schema.model.Field;
import io.smallrye.graphql.schema.model.InputType;
import io.smallrye.graphql.schema.model.Operation;
import io.smallrye.graphql.schema.model.ReferenceType;
import io.smallrye.graphql.transformation.BoundedNumberType;

/**
* Here we create a mapping of all fields in a input type that needs transformation and mapping
Expand All @@ -19,6 +21,8 @@
public class InputFieldsInfo {

private static final Map<String, Map<String, Field>> inputFieldTransformationMap = new HashMap<>();
private static final NumericInputField[] NO_NUMERIC_FIELDS = new NumericInputField[0];
private static final Map<String, NumericInputField[]> numericInputFields = new HashMap<>();
private static final Map<String, Map<String, Field>> inputFieldAdaptingToMap = new HashMap<>();
private static final Map<String, Map<String, Field>> inputFieldAdaptingWithMap = new HashMap<>();
private static final Map<String, List<Field>> creatorParameters = new HashMap<>();
Expand All @@ -36,16 +40,25 @@ protected static void register(InputType inputType) {
InputFieldsInfo.creatorParameters.put(inputType.getClassName(), creatorParameters);

Map<String, Field> fieldsThatNeedsTransformation = new HashMap<>();
List<NumericInputField> fieldsNeedingNumericValidation = new ArrayList<>();
Map<String, Field> fieldsThatNeedsAdaptingToScalar = new HashMap<>();
Map<String, Field> fieldsThatNeedsAdaptingWith = new HashMap<>();

Collection<Field> fields = inputType.getFields().values();
for (Field field : fields) {
// See if there is a transformation
if (field.hasTransformation()
&& !field.getTransformation().isJsonB()) {
if (field.hasTransformation() && !field.getTransformation().isJsonB()) {
fieldsThatNeedsTransformation.put(field.getName(), field);
}
// Nested input objects are bound together with their parent, so their numeric fields are checked from here
if (field.getReference().getType() == ReferenceType.INPUT) {
fieldsNeedingNumericValidation.add(new NumericInputField(field, null));
} else if (!field.hasTransformation()) {
BoundedNumberType numberType = BoundedNumberType.forClassName(field.getReference().getClassName());
if (numberType != null) {
fieldsNeedingNumericValidation.add(new NumericInputField(field, numberType));
}
}
// See if there is an adapter
if (field.isAdaptingWith()) {
fieldsThatNeedsAdaptingWith.put(field.getName(), field);
Expand All @@ -63,6 +76,7 @@ protected static void register(InputType inputType) {
if (!fieldsThatNeedsTransformation.isEmpty()) {
inputFieldTransformationMap.put(inputType.getClassName(), fieldsThatNeedsTransformation);
}
numericInputFields.put(inputType.getClassName(), fieldsNeedingNumericValidation.toArray(NO_NUMERIC_FIELDS));

if (!fieldsThatNeedsAdaptingToScalar.isEmpty()) {
inputFieldAdaptingToMap.put(inputType.getClassName(), fieldsThatNeedsAdaptingToScalar);
Expand All @@ -81,6 +95,10 @@ public static boolean hasTransformationFields(String className) {
return inputFieldTransformationMap.containsKey(className);
}

public static NumericInputField[] getNumericInputFields(String className) {
return numericInputFields.getOrDefault(className, NO_NUMERIC_FIELDS);
}

public static Map<String, Field> getTransformationFields(String className) {
if (inputFieldTransformationMap.containsKey(className)) {
return inputFieldTransformationMap.get(className);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.smallrye.graphql.json;

import io.smallrye.graphql.schema.model.Field;
import io.smallrye.graphql.transformation.BoundedNumberType;

/**
* An input field whose numeric values must be range checked before the input object is bound.
*
* @param field the input field
* @param numberType the bounded number type to check, or {@code null} if the field is a nested input object whose own
* numeric fields are checked recursively
*/
public record NumericInputField(Field field, BoundedNumberType numberType) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.math.BigDecimal;
import java.math.BigInteger;

import io.smallrye.graphql.transformation.NumberTransformer;

/**
* Scalar for BigDecimal.
* Based on graphql-java's Scalars.GraphQLBigDecimal
Expand All @@ -24,6 +26,16 @@ public Object fromBigDecimal(BigDecimal bigDecimal) {
public Object fromBigInteger(BigInteger bigInteger) {
return new BigDecimal(bigInteger);
}

@Override
public Object fromLong(long value) {
return BigDecimal.valueOf(value);
}

@Override
public Object fromFloat(float value) {
return NumberTransformer.bigDecimalValue(value);
}
},
BigDecimal.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@ public Object fromBigDecimal(BigDecimal bigDecimal) {
// so bound the absolute scale as well as the precision. Thrown as an ArithmeticException so that
// NumberCoercing.parseLiteral translates it into a normal CoercingParseLiteralException instead of
// exhausting resources.
if (bigDecimal.precision() > MAX_PRECISION || Math.abs((long) bigDecimal.scale()) > MAX_SCALE) {
throw new ArithmeticException("Numeric literal magnitude out of allowed range");
}
return bigDecimal.toBigIntegerExact();
return toBigIntegerExact(bigDecimal);
}

@Override
Expand All @@ -48,4 +45,11 @@ public Object fromBigInteger(BigInteger bigInteger) {
},
AtomicLong.class, OptionalLong.class, BigInteger.class, Long.class, long.class);
}

public static BigInteger toBigIntegerExact(BigDecimal value) {
if (value.precision() > MAX_PRECISION || Math.abs((long) value.scale()) > MAX_SCALE) {
throw new ArithmeticException("Numeric literal magnitude out of allowed range");
}
return value.toBigIntegerExact();
}
}
Loading
Loading