diff --git a/aep-core/src/main/java/foundation/aep/core/AepJson.java b/aep-core/src/main/java/foundation/aep/core/AepJson.java index 05f7107..f9723c2 100644 --- a/aep-core/src/main/java/foundation/aep/core/AepJson.java +++ b/aep-core/src/main/java/foundation/aep/core/AepJson.java @@ -195,6 +195,7 @@ public static PlatformAgentIdentityListResponse parsePlatformAgentIdentityListRe Map value = object(json, PLATFORM_AGENT_IDENTITY_LIST); AepRawJson.requireMembers(value, PLATFORM_AGENT_IDENTITY_LIST, "count", "data", "total"); AepRawJson.rejectNullPaths(value, PLATFORM_AGENT_IDENTITY_LIST, "count", "data", "total"); + AepRawJson.requireStrings(value, PLATFORM_AGENT_IDENTITY_LIST, "count", "total"); return parse( json, PlatformAgentIdentityListResponse.class, diff --git a/aep-core/src/main/java/foundation/aep/core/AepRawJson.java b/aep-core/src/main/java/foundation/aep/core/AepRawJson.java index 159fa68..d627f00 100644 --- a/aep-core/src/main/java/foundation/aep/core/AepRawJson.java +++ b/aep-core/src/main/java/foundation/aep/core/AepRawJson.java @@ -24,6 +24,7 @@ static void claimValues(Map value) { "person.username"); requireStrings( value, + CLAIM_VALUES, "contact.email", "contact.mobile", "person.birthdate", @@ -140,11 +141,11 @@ static void requireMembers(Map value, String documentType, Strin } } - private static void requireStrings(Map value, String... members) { + static void requireStrings(Map value, String documentType, String... members) { for (String member : members) { if (value.containsKey(member) && !(value.get(member) instanceof String)) { throw new AepValidationException( - CLAIM_VALUES, List.of(new ValidationIssue("$." + member, "Expected a string."))); + documentType, List.of(new ValidationIssue("$." + member, "Expected a string."))); } } } diff --git a/aep-core/src/main/java/foundation/aep/core/AepValidation.java b/aep-core/src/main/java/foundation/aep/core/AepValidation.java index 98fcbdc..d1c98cb 100644 --- a/aep-core/src/main/java/foundation/aep/core/AepValidation.java +++ b/aep-core/src/main/java/foundation/aep/core/AepValidation.java @@ -359,8 +359,8 @@ public static PlatformAgentIdentity requirePlatformAgentIdentity(PlatformAgentId public static List platformAgentIdentityListResponse(PlatformAgentIdentityListResponse value) { Issues issues = new Issues(); if (value == null) return issues.required("$", JSON_OBJECT).values(); - nonNegativeIntegerString(value.count(), "$.count", issues); - nonNegativeIntegerString(value.total(), "$.total", issues); + java.math.BigInteger count = nonNegativeIntegerString(value.count(), "$.count", issues); + java.math.BigInteger total = nonNegativeIntegerString(value.total(), "$.total", issues); if (value.data() == null) { issues.required("$.data", "array"); } else { @@ -369,10 +369,14 @@ public static List platformAgentIdentityListResponse(PlatformAg "$.data[" + index + "]", platformAgentIdentity(value.data().get(index))); } - if (nonNegativeInteger(value.count()) != value.data().size()) { + if (count != null + && !count.equals(java.math.BigInteger.valueOf(value.data().size()))) { issues.add("$.count", "Expected count to equal the number of data entries."); } } + if (count != null && total != null && total.compareTo(count) < 0) { + issues.add("$.total", "Expected total not to be less than count."); + } return issues.values(); } @@ -794,11 +798,21 @@ private static void positiveIntegerString(String value, String path, Issues issu } } - private static void nonNegativeIntegerString(String value, String path, Issues issues) { - int number = nonNegativeInteger(value); - if (number < 0 || !Integer.toString(number).equals(value)) { + private static java.math.BigInteger nonNegativeIntegerString(String value, String path, Issues issues) { + if (value == null) { + issues.add(path, "Expected a non-negative decimal integer string."); + return null; + } + java.math.BigInteger number; + try { + number = new java.math.BigInteger(value); + } catch (NumberFormatException exception) { issues.add(path, "Expected a non-negative decimal integer string."); + return null; } + if (number.signum() >= 0 && number.toString().equals(value)) return number; + issues.add(path, "Expected a non-negative decimal integer string."); + return null; } private static int nonNegativeInteger(String value) { diff --git a/aep-core/src/test/java/foundation/aep/core/PlatformProtocolTest.java b/aep-core/src/test/java/foundation/aep/core/PlatformProtocolTest.java index d7f612b..56e7e8b 100644 --- a/aep-core/src/test/java/foundation/aep/core/PlatformProtocolTest.java +++ b/aep-core/src/test/java/foundation/aep/core/PlatformProtocolTest.java @@ -68,6 +68,36 @@ void rejectsMalformedPlatformDiscoveryTemplatesWithoutThrowing() { assertEquals(2, AepValidation.platformDiscoveryDocument(malformed).size()); } + @Test + void validatesPlatformIdentityListPagination() { + PlatformAgentIdentity identity = new PlatformAgentIdentity( + "did:web:p.example:a:4Yf7p2xQd9", + "pai_01J0AEPPLATFORM000000000001", + "2026-07-06T12:00:00Z", + "https://p.example/a/4Yf7p2xQd9/did.json", + "did:web:p.example:a:4Yf7p2xQd9", + "did:web:api.service.example", + List.of("ES256"), + ManagedAgentStatus.ACTIVE, + "2026-07-06T12:00:00Z"); + + assertTrue(AepValidation.platformAgentIdentityListResponse( + new PlatformAgentIdentityListResponse("1", List.of(identity), "999999999999999999999999")) + .isEmpty()); + assertEquals( + "$.count", + AepValidation.platformAgentIdentityListResponse( + new PlatformAgentIdentityListResponse("0", List.of(identity), "1")) + .get(0) + .path()); + assertEquals( + "$.total", + AepValidation.platformAgentIdentityListResponse( + new PlatformAgentIdentityListResponse("1", List.of(identity), "0")) + .get(0) + .path()); + } + private static PlatformDiscoveryDocument discovery() { return new PlatformDiscoveryDocument( "1.0", diff --git a/aep-json-jackson2/src/test/java/foundation/aep/json/jackson2/Jackson2JsonProviderTest.java b/aep-json-jackson2/src/test/java/foundation/aep/json/jackson2/Jackson2JsonProviderTest.java index 6a6b6c9..0e55c16 100644 --- a/aep-json-jackson2/src/test/java/foundation/aep/json/jackson2/Jackson2JsonProviderTest.java +++ b/aep-json-jackson2/src/test/java/foundation/aep/json/jackson2/Jackson2JsonProviderTest.java @@ -97,6 +97,16 @@ void parsesWireAuthorizationValues() { AepJson.write(foundation.aep.core.ProblemDetails.of("invalid_request", "Invalid request", 400))); } + @Test + void rejectsNumericPlatformPaginationFields() { + assertThrows( + AepValidationException.class, + () -> AepJson.parsePlatformAgentIdentityListResponse("{\"count\":0,\"data\":[],\"total\":\"0\"}")); + assertThrows( + AepValidationException.class, + () -> AepJson.parsePlatformAgentIdentityListResponse("{\"count\":\"0\",\"data\":[],\"total\":0}")); + } + @Test void parsesEveryCoreWireDocument() { assertEquals( diff --git a/aep-json-jackson3/src/test/java/foundation/aep/json/jackson3/Jackson3JsonProviderTest.java b/aep-json-jackson3/src/test/java/foundation/aep/json/jackson3/Jackson3JsonProviderTest.java index 6b6952a..7d8c3fc 100644 --- a/aep-json-jackson3/src/test/java/foundation/aep/json/jackson3/Jackson3JsonProviderTest.java +++ b/aep-json-jackson3/src/test/java/foundation/aep/json/jackson3/Jackson3JsonProviderTest.java @@ -97,6 +97,16 @@ void parsesWireAuthorizationValues() { AepJson.write(foundation.aep.core.ProblemDetails.of("invalid_request", "Invalid request", 400))); } + @Test + void rejectsNumericPlatformPaginationFields() { + assertThrows( + AepValidationException.class, + () -> AepJson.parsePlatformAgentIdentityListResponse("{\"count\":0,\"data\":[],\"total\":\"0\"}")); + assertThrows( + AepValidationException.class, + () -> AepJson.parsePlatformAgentIdentityListResponse("{\"count\":\"0\",\"data\":[],\"total\":0}")); + } + @Test void parsesEveryCoreWireDocument() { assertEquals(