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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
for k in totals: totals[k]+=int(r.get(k,'0'))
except Exception:
pass
exp_tests=460
exp_tests=463
exp_skipped=0
if totals['tests']!=exp_tests or totals['skipped']!=exp_skipped:
print(f"Unexpected test totals: {totals} != expected tests={exp_tests}, skipped={exp_skipped}")
Expand Down
8 changes: 8 additions & 0 deletions json-java21-jtd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ Validates primitive types:

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

#### Integer Type Validation
Integer types (`int8`, `uint8`, `int16`, `uint16`, `int32`, `uint32`) validate based on **numeric value**, not textual representation:

- **Valid integers**: `3`, `3.0`, `3.000`, `42.00` (mathematically integers)
- **Invalid integers**: `3.1`, `3.14`, `3.0001` (have fractional components)

This follows RFC 8927 §2.2.3.1: "An integer value is a number without a fractional component."

### 4. Enum Schema
Validates against string values:
```json
Expand Down
7 changes: 7 additions & 0 deletions json-java21-jtd/src/main/java/json/java21/jtd/JtdSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,13 @@ Jtd.Result validateInteger(JsonValue instance, String type, boolean verboseError
return Jtd.Result.failure(Jtd.Error.EXPECTED_INTEGER.message());
}

// Handle BigDecimal - check if it has fractional component (not just scale > 0)
// RFC 8927 §2.2.3.1: "An integer value is a number without a fractional component"
// Values like 3.0 or 3.000 are valid integers despite positive scale, but 3.1 is not
if (value instanceof java.math.BigDecimal bd && bd.remainder(java.math.BigDecimal.ONE).signum() != 0) {
return Jtd.Result.failure(Jtd.Error.EXPECTED_INTEGER.message());
}

// Convert to long for range checking
long longValue = value.longValue();

Expand Down
90 changes: 90 additions & 0 deletions json-java21-jtd/src/test/java/json/java21/jtd/TestRfc8927.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import jdk.sandbox.java.util.json.Json;
import jdk.sandbox.java.util.json.JsonValue;
import jdk.sandbox.java.util.json.JsonNumber;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
Expand Down Expand Up @@ -440,4 +441,93 @@ public void testRefSchemaRecursiveBad() throws Exception {
.as("Recursive ref should reject heterogeneous nested data")
.isFalse();
}

/// Micro test to debug int32 validation with decimal values
/// Should reject non-integer values like 3.14 for int32 type
@Test
public void testInt32RejectsDecimal() throws Exception {
JsonValue schema = Json.parse("{\"type\": \"int32\"}");
JsonValue decimalValue = JsonNumber.of(new java.math.BigDecimal("3.14"));

LOG.info(() -> "Testing int32 validation against decimal value 3.14");
LOG.fine(() -> "Schema: " + schema);
LOG.fine(() -> "Instance: " + decimalValue);

Jtd validator = new Jtd();
Jtd.Result result = validator.validate(schema, decimalValue);

LOG.fine(() -> "Validation result: " + (result.isValid() ? "VALID" : "INVALID"));
if (!result.isValid()) {
LOG.fine(() -> "ERRORS: " + result.errors());
}

// This should be invalid - int32 should reject decimal values
assertThat(result.isValid())
.as("int32 should reject decimal value 3.14")
.isFalse();
assertThat(result.errors())
.as("Should have validation errors for decimal value")
.isNotEmpty();
}

/// Test that integer types accept valid integer representations with trailing zeros
/// RFC 8927 §2.2.3.1: "An integer value is a number without a fractional component"
/// Values like 3.0, 3.000 are valid integers despite positive scale
@Test
public void testIntegerTypesAcceptTrailingZeros() throws Exception {
JsonValue schema = Json.parse("{\"type\": \"int32\"}");

// Valid integer representations with trailing zeros
JsonValue[] validIntegers = {
JsonNumber.of(new java.math.BigDecimal("3.0")),
JsonNumber.of(new java.math.BigDecimal("3.000")),
JsonNumber.of(new java.math.BigDecimal("42.00")),
JsonNumber.of(new java.math.BigDecimal("0.0"))
};

Jtd validator = new Jtd();

for (JsonValue validValue : validIntegers) {
Jtd.Result result = validator.validate(schema, validValue);

LOG.fine(() -> "Testing int32 with valid integer representation: " + validValue);

assertThat(result.isValid())
.as("int32 should accept integer representation %s", validValue)
.isTrue();
assertThat(result.errors())
.as("Should have no validation errors for integer representation %s", validValue)
.isEmpty();
}
}

/// Test that integer types reject values with actual fractional components
/// RFC 8927 §2.2.3.1: "An integer value is a number without a fractional component"
@Test
public void testIntegerTypesRejectFractionalComponents() throws Exception {
JsonValue schema = Json.parse("{\"type\": \"int32\"}");

// Invalid values with actual fractional components
JsonValue[] invalidValues = {
JsonNumber.of(new java.math.BigDecimal("3.1")),
JsonNumber.of(new java.math.BigDecimal("3.0001")),
JsonNumber.of(new java.math.BigDecimal("3.14")),
JsonNumber.of(new java.math.BigDecimal("0.1"))
};

Jtd validator = new Jtd();

for (JsonValue invalidValue : invalidValues) {
Jtd.Result result = validator.validate(schema, invalidValue);

LOG.fine(() -> "Testing int32 with fractional value: " + invalidValue);

assertThat(result.isValid())
.as("int32 should reject fractional value %s", invalidValue)
.isFalse();
assertThat(result.errors())
.as("Should have validation errors for fractional value %s", invalidValue)
.isNotEmpty();
}
}
}