Skip to content

Commit ced2da9

Browse files
authored
Fix int32 type validation to reject decimal values (Closes #89) (#90)
* Issue #89 Fix int32 type validation to reject decimal values like 3.14 - Added BigDecimal fractional part checking in validateInteger method - Added test case testInt32RejectsDecimal() to verify the fix - Ensures all integer types (int8, uint8, int16, uint16, int32, uint32) reject decimal values - Maintains RFC 8927 compliance for integer type validation * Issue #89 Update CI test count to 461 after adding int32 decimal validation test * Issue #89 Update CI test count to 463 after adding integer validation edge case tests * Issue #89 Update CI test count to 463 after adding integer validation edge case tests
1 parent 8dec57f commit ced2da9

File tree

4 files changed

+106
-1
lines changed

4 files changed

+106
-1
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
for k in totals: totals[k]+=int(r.get(k,'0'))
4040
except Exception:
4141
pass
42-
exp_tests=460
42+
exp_tests=463
4343
exp_skipped=0
4444
if totals['tests']!=exp_tests or totals['skipped']!=exp_skipped:
4545
print(f"Unexpected test totals: {totals} != expected tests={exp_tests}, skipped={exp_skipped}")

json-java21-jtd/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ Validates primitive types:
6969

7070
Supported types: `boolean`, `string`, `timestamp`, `int8`, `uint8`, `int16`, `uint16`, `int32`, `uint32`, `float32`, `float64`
7171

72+
#### Integer Type Validation
73+
Integer types (`int8`, `uint8`, `int16`, `uint16`, `int32`, `uint32`) validate based on **numeric value**, not textual representation:
74+
75+
- **Valid integers**: `3`, `3.0`, `3.000`, `42.00` (mathematically integers)
76+
- **Invalid integers**: `3.1`, `3.14`, `3.0001` (have fractional components)
77+
78+
This follows RFC 8927 §2.2.3.1: "An integer value is a number without a fractional component."
79+
7280
### 4. Enum Schema
7381
Validates against string values:
7482
```json

json-java21-jtd/src/main/java/json/java21/jtd/JtdSchema.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,13 @@ Jtd.Result validateInteger(JsonValue instance, String type, boolean verboseError
244244
return Jtd.Result.failure(Jtd.Error.EXPECTED_INTEGER.message());
245245
}
246246

247+
// Handle BigDecimal - check if it has fractional component (not just scale > 0)
248+
// RFC 8927 §2.2.3.1: "An integer value is a number without a fractional component"
249+
// Values like 3.0 or 3.000 are valid integers despite positive scale, but 3.1 is not
250+
if (value instanceof java.math.BigDecimal bd && bd.remainder(java.math.BigDecimal.ONE).signum() != 0) {
251+
return Jtd.Result.failure(Jtd.Error.EXPECTED_INTEGER.message());
252+
}
253+
247254
// Convert to long for range checking
248255
long longValue = value.longValue();
249256

json-java21-jtd/src/test/java/json/java21/jtd/TestRfc8927.java

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import jdk.sandbox.java.util.json.Json;
44
import jdk.sandbox.java.util.json.JsonValue;
5+
import jdk.sandbox.java.util.json.JsonNumber;
56
import org.junit.jupiter.api.Test;
67

78
import static org.assertj.core.api.Assertions.assertThat;
@@ -440,4 +441,93 @@ public void testRefSchemaRecursiveBad() throws Exception {
440441
.as("Recursive ref should reject heterogeneous nested data")
441442
.isFalse();
442443
}
444+
445+
/// Micro test to debug int32 validation with decimal values
446+
/// Should reject non-integer values like 3.14 for int32 type
447+
@Test
448+
public void testInt32RejectsDecimal() throws Exception {
449+
JsonValue schema = Json.parse("{\"type\": \"int32\"}");
450+
JsonValue decimalValue = JsonNumber.of(new java.math.BigDecimal("3.14"));
451+
452+
LOG.info(() -> "Testing int32 validation against decimal value 3.14");
453+
LOG.fine(() -> "Schema: " + schema);
454+
LOG.fine(() -> "Instance: " + decimalValue);
455+
456+
Jtd validator = new Jtd();
457+
Jtd.Result result = validator.validate(schema, decimalValue);
458+
459+
LOG.fine(() -> "Validation result: " + (result.isValid() ? "VALID" : "INVALID"));
460+
if (!result.isValid()) {
461+
LOG.fine(() -> "ERRORS: " + result.errors());
462+
}
463+
464+
// This should be invalid - int32 should reject decimal values
465+
assertThat(result.isValid())
466+
.as("int32 should reject decimal value 3.14")
467+
.isFalse();
468+
assertThat(result.errors())
469+
.as("Should have validation errors for decimal value")
470+
.isNotEmpty();
471+
}
472+
473+
/// Test that integer types accept valid integer representations with trailing zeros
474+
/// RFC 8927 §2.2.3.1: "An integer value is a number without a fractional component"
475+
/// Values like 3.0, 3.000 are valid integers despite positive scale
476+
@Test
477+
public void testIntegerTypesAcceptTrailingZeros() throws Exception {
478+
JsonValue schema = Json.parse("{\"type\": \"int32\"}");
479+
480+
// Valid integer representations with trailing zeros
481+
JsonValue[] validIntegers = {
482+
JsonNumber.of(new java.math.BigDecimal("3.0")),
483+
JsonNumber.of(new java.math.BigDecimal("3.000")),
484+
JsonNumber.of(new java.math.BigDecimal("42.00")),
485+
JsonNumber.of(new java.math.BigDecimal("0.0"))
486+
};
487+
488+
Jtd validator = new Jtd();
489+
490+
for (JsonValue validValue : validIntegers) {
491+
Jtd.Result result = validator.validate(schema, validValue);
492+
493+
LOG.fine(() -> "Testing int32 with valid integer representation: " + validValue);
494+
495+
assertThat(result.isValid())
496+
.as("int32 should accept integer representation %s", validValue)
497+
.isTrue();
498+
assertThat(result.errors())
499+
.as("Should have no validation errors for integer representation %s", validValue)
500+
.isEmpty();
501+
}
502+
}
503+
504+
/// Test that integer types reject values with actual fractional components
505+
/// RFC 8927 §2.2.3.1: "An integer value is a number without a fractional component"
506+
@Test
507+
public void testIntegerTypesRejectFractionalComponents() throws Exception {
508+
JsonValue schema = Json.parse("{\"type\": \"int32\"}");
509+
510+
// Invalid values with actual fractional components
511+
JsonValue[] invalidValues = {
512+
JsonNumber.of(new java.math.BigDecimal("3.1")),
513+
JsonNumber.of(new java.math.BigDecimal("3.0001")),
514+
JsonNumber.of(new java.math.BigDecimal("3.14")),
515+
JsonNumber.of(new java.math.BigDecimal("0.1"))
516+
};
517+
518+
Jtd validator = new Jtd();
519+
520+
for (JsonValue invalidValue : invalidValues) {
521+
Jtd.Result result = validator.validate(schema, invalidValue);
522+
523+
LOG.fine(() -> "Testing int32 with fractional value: " + invalidValue);
524+
525+
assertThat(result.isValid())
526+
.as("int32 should reject fractional value %s", invalidValue)
527+
.isFalse();
528+
assertThat(result.errors())
529+
.as("Should have validation errors for fractional value %s", invalidValue)
530+
.isNotEmpty();
531+
}
532+
}
443533
}

0 commit comments

Comments
 (0)