Skip to content
Merged
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
27 changes: 22 additions & 5 deletions src/main/java/io/vertx/openapi/impl/OpenAPIFormatValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@

public class OpenAPIFormatValidator implements JsonFormatValidator {

// The formats "float" and "double" define the precision of a number, not its representation. Whole numbers
// are valid, as long as they can be represented exactly with the related precision.
private static final long MAX_EXACT_FLOAT_INTEGER = 1L << 24;
private static final long MAX_EXACT_DOUBLE_INTEGER = 1L << 53;

@Override
public String validateFormat(String instanceType, String format, Object instance) {
if ("int32".equalsIgnoreCase(format) && !(instance instanceof Integer)) {
Expand All @@ -15,23 +20,35 @@ public String validateFormat(String instanceType, String format, Object instance
}

if ("float".equalsIgnoreCase(format)) {
// Behind the scenes we use jackson, so even floats are converted into doubles for us.
// So now we will down cast the float back into a double, and check the usual isInfinite and isNan.
if (!(instance instanceof Double) || ((Float) ((Double) instance).floatValue()).isInfinite()
if (instance instanceof Integer || instance instanceof Long) {
if (!isExactlyRepresentable(((Number) instance).longValue(), MAX_EXACT_FLOAT_INTEGER)) {
return getMessage(format);
}
// Behind the scenes we use jackson, so even floats are converted into doubles for us.
// So now we will down cast the float back into a double, and check the usual isInfinite and isNan.
} else if (!(instance instanceof Double) || ((Float) ((Double) instance).floatValue()).isInfinite()
|| ((Float) ((Double) instance).floatValue()).isNaN()) {
return getMessage(format);
}
}

if ("float".equalsIgnoreCase(format) || "double".equalsIgnoreCase(format)) {
if (!(instance instanceof Double) || ((Double) instance).isInfinite() || ((Double) instance).isNaN()) {
if ("double".equalsIgnoreCase(format)) {
if (instance instanceof Integer || instance instanceof Long) {
if (!isExactlyRepresentable(((Number) instance).longValue(), MAX_EXACT_DOUBLE_INTEGER)) {
return getMessage(format);
}
} else if (!(instance instanceof Double) || ((Double) instance).isInfinite() || ((Double) instance).isNaN()) {
return getMessage(format);
}
}

return null;
}

private static boolean isExactlyRepresentable(long value, long maxExactInteger) {
return value >= -maxExactInteger && value <= maxExactInteger;
}

private String getMessage(String format) {
String type = "int32".equalsIgnoreCase(format) || "int64".equalsIgnoreCase(format) ? "Integer" : "Number";
return String.format("%s does not match the format \"%s\"", type, format);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,18 @@ private static Stream<Arguments> getBadlyFormattedParameters() {
Arguments.of("Double", numberSchema().toJson().put("format", "double"), "71" + Double.MAX_VALUE,
"The value of path parameter Double is invalid. Reason: Number does not match the format \"double\""),
Arguments.of("Float", numberSchema().toJson().put("format", "float"), "71" + Float.MAX_VALUE,
"The value of path parameter Float is invalid. Reason: Number does not match the format \"float\""),
Arguments.of("Double", numberSchema().toJson().put("format", "double"), 9007199254740993L,
"The value of path parameter Double is invalid. Reason: Number does not match the format \"double\""),
Arguments.of("Double", numberSchema().toJson().put("format", "double"), -9007199254740993L,
"The value of path parameter Double is invalid. Reason: Number does not match the format \"double\""),
Arguments.of("Double", numberSchema().toJson().put("format", "double"), Long.MAX_VALUE,
"The value of path parameter Double is invalid. Reason: Number does not match the format \"double\""),
Arguments.of("Float", numberSchema().toJson().put("format", "float"), 16777217,
"The value of path parameter Float is invalid. Reason: Number does not match the format \"float\""),
Arguments.of("Float", numberSchema().toJson().put("format", "float"), -16777217,
"The value of path parameter Float is invalid. Reason: Number does not match the format \"float\""),
Arguments.of("Float", numberSchema().toJson().put("format", "float"), Integer.MAX_VALUE,
"The value of path parameter Float is invalid. Reason: Number does not match the format \"float\""));
}

Expand All @@ -195,9 +207,17 @@ private static Stream<Arguments> getCorrectlyFormattedParameters() {
Arguments.of("Double min double", numberSchema().toJson().put("format", "double"), Double.MIN_VALUE),
Arguments.of("Double max float", numberSchema().toJson().put("format", "double"), Float.MAX_VALUE),
Arguments.of("Double normal", numberSchema().toJson().put("format", "double"), 123.456),
Arguments.of("Double without decimal part", numberSchema().toJson().put("format", "double"), 10),
Arguments.of("Double max exact integer", numberSchema().toJson().put("format", "double"),
9007199254740992L),
Arguments.of("Double min exact integer", numberSchema().toJson().put("format", "double"),
-9007199254740992L),
Arguments.of("Float max float", numberSchema().toJson().put("format", "float"), Float.MAX_VALUE),
Arguments.of("Float min float", numberSchema().toJson().put("format", "float"), Float.MIN_VALUE),
Arguments.of("Float normal", numberSchema().toJson().put("format", "float"), 123.456));
Arguments.of("Float normal", numberSchema().toJson().put("format", "float"), 123.456),
Arguments.of("Float without decimal part", numberSchema().toJson().put("format", "float"), 10),
Arguments.of("Float max exact integer", numberSchema().toJson().put("format", "float"), 16777216),
Arguments.of("Float min exact integer", numberSchema().toJson().put("format", "float"), -16777216));
}

@BeforeEach
Expand Down