From 9125b741b2a5cb34ffd380fac7569dbc9bf8734a Mon Sep 17 00:00:00 2001 From: Mary Dickson Date: Thu, 9 Apr 2026 07:34:26 -0700 Subject: [PATCH 1/2] feat(sdk): add EntityIdentifier convenience constructors Add EntityIdentifiers utility class with static helpers that mirror the Go SDK's ForEmail, ForClientID, ForUserName, and ForToken constructors. Reduces EntityIdentifier construction from ~10 lines of nested builders to a single method call. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Mary Dickson --- .../platform/sdk/EntityIdentifiers.java | 79 +++++++++++++++++++ .../platform/sdk/EntityIdentifiersTest.java | 78 ++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 sdk/src/main/java/io/opentdf/platform/sdk/EntityIdentifiers.java create mode 100644 sdk/src/test/java/io/opentdf/platform/sdk/EntityIdentifiersTest.java diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/EntityIdentifiers.java b/sdk/src/main/java/io/opentdf/platform/sdk/EntityIdentifiers.java new file mode 100644 index 00000000..3fa7d9ac --- /dev/null +++ b/sdk/src/main/java/io/opentdf/platform/sdk/EntityIdentifiers.java @@ -0,0 +1,79 @@ +package io.opentdf.platform.sdk; + +import io.opentdf.platform.authorization.v2.EntityIdentifier; +import io.opentdf.platform.entity.Entity; +import io.opentdf.platform.entity.EntityChain; +import io.opentdf.platform.entity.Token; + +/** + * Convenience constructors for {@link EntityIdentifier}, mirroring the Go SDK helpers + * in {@code authorization/v2.ForEmail}, {@code ForClientID}, etc. + * + *

Each method builds the full {@code EntityIdentifier} proto so callers avoid + * deeply nested builder chains. + * + *

{@code
+ * // Before
+ * EntityIdentifier.newBuilder()
+ *     .setEntityChain(EntityChain.newBuilder()
+ *         .addEntities(Entity.newBuilder()
+ *             .setEmailAddress("jen@example.com")
+ *             .setCategory(Entity.Category.CATEGORY_SUBJECT)))
+ *     .build();
+ *
+ * // After
+ * EntityIdentifiers.forEmail("jen@example.com");
+ * }
+ */ +public final class EntityIdentifiers { + + private EntityIdentifiers() {} + + /** + * Returns an EntityIdentifier for a subject identified by email address. + */ + public static EntityIdentifier forEmail(String email) { + return fromEntity(Entity.newBuilder() + .setEmailAddress(email) + .setCategory(Entity.Category.CATEGORY_SUBJECT) + .build()); + } + + /** + * Returns an EntityIdentifier for a subject identified by client ID. + */ + public static EntityIdentifier forClientId(String clientId) { + return fromEntity(Entity.newBuilder() + .setClientId(clientId) + .setCategory(Entity.Category.CATEGORY_SUBJECT) + .build()); + } + + /** + * Returns an EntityIdentifier for a subject identified by username. + */ + public static EntityIdentifier forUserName(String userName) { + return fromEntity(Entity.newBuilder() + .setUserName(userName) + .setCategory(Entity.Category.CATEGORY_SUBJECT) + .build()); + } + + /** + * Returns an EntityIdentifier that resolves the entity from the given JWT. + * The authorization service parses the token to derive the entity chain. + */ + public static EntityIdentifier forToken(String jwt) { + return EntityIdentifier.newBuilder() + .setToken(Token.newBuilder().setJwt(jwt).build()) + .build(); + } + + private static EntityIdentifier fromEntity(Entity entity) { + return EntityIdentifier.newBuilder() + .setEntityChain(EntityChain.newBuilder() + .addEntities(entity) + .build()) + .build(); + } +} diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/EntityIdentifiersTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/EntityIdentifiersTest.java new file mode 100644 index 00000000..294565c5 --- /dev/null +++ b/sdk/src/test/java/io/opentdf/platform/sdk/EntityIdentifiersTest.java @@ -0,0 +1,78 @@ +package io.opentdf.platform.sdk; + +import static org.junit.jupiter.api.Assertions.*; + +import io.opentdf.platform.authorization.v2.EntityIdentifier; +import io.opentdf.platform.entity.Entity; +import io.opentdf.platform.entity.EntityChain; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class EntityIdentifiersTest { + + @ParameterizedTest + @ValueSource(strings = {"user@example.com", ""}) + void forEmail(String email) { + EntityIdentifier eid = EntityIdentifiers.forEmail(email); + + EntityChain chain = extractEntityChain(eid); + assertEquals(1, chain.getEntitiesCount()); + + Entity e = chain.getEntities(0); + assertEquals(Entity.EntityTypeCase.EMAIL_ADDRESS, e.getEntityTypeCase()); + assertEquals(email, e.getEmailAddress()); + assertEquals(Entity.Category.CATEGORY_SUBJECT, e.getCategory()); + } + + @ParameterizedTest + @ValueSource(strings = {"my-client", ""}) + void forClientId(String clientId) { + EntityIdentifier eid = EntityIdentifiers.forClientId(clientId); + + EntityChain chain = extractEntityChain(eid); + assertEquals(1, chain.getEntitiesCount()); + + Entity e = chain.getEntities(0); + assertEquals(Entity.EntityTypeCase.CLIENT_ID, e.getEntityTypeCase()); + assertEquals(clientId, e.getClientId()); + assertEquals(Entity.Category.CATEGORY_SUBJECT, e.getCategory()); + } + + @ParameterizedTest + @ValueSource(strings = {"alice", ""}) + void forUserName(String userName) { + EntityIdentifier eid = EntityIdentifiers.forUserName(userName); + + EntityChain chain = extractEntityChain(eid); + assertEquals(1, chain.getEntitiesCount()); + + Entity e = chain.getEntities(0); + assertEquals(Entity.EntityTypeCase.USER_NAME, e.getEntityTypeCase()); + assertEquals(userName, e.getUserName()); + assertEquals(Entity.Category.CATEGORY_SUBJECT, e.getCategory()); + } + + @Test + void forToken() { + String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.test"; + EntityIdentifier eid = EntityIdentifiers.forToken(jwt); + + assertEquals(EntityIdentifier.IdentifierCase.TOKEN, eid.getIdentifierCase()); + assertEquals(jwt, eid.getToken().getJwt()); + } + + @Test + void forTokenEmptyString() { + EntityIdentifier eid = EntityIdentifiers.forToken(""); + + assertEquals(EntityIdentifier.IdentifierCase.TOKEN, eid.getIdentifierCase()); + assertEquals("", eid.getToken().getJwt()); + } + + private static EntityChain extractEntityChain(EntityIdentifier eid) { + assertEquals(EntityIdentifier.IdentifierCase.ENTITY_CHAIN, eid.getIdentifierCase(), + "expected ENTITY_CHAIN identifier"); + return eid.getEntityChain(); + } +} From 9b8b01fa72338b7c8abdc7fb65315d9955268ba5 Mon Sep 17 00:00:00 2001 From: Mary Dickson Date: Thu, 9 Apr 2026 07:47:25 -0700 Subject: [PATCH 2/2] fix(sdk): add null checks and consolidate token tests Address CodeRabbit review: - Add Objects.requireNonNull to all four factory methods - Consolidate forToken tests into a parameterized test - Add null-input contract test Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Mary Dickson --- .../opentdf/platform/sdk/EntityIdentifiers.java | 6 ++++++ .../platform/sdk/EntityIdentifiersTest.java | 16 ++++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/EntityIdentifiers.java b/sdk/src/main/java/io/opentdf/platform/sdk/EntityIdentifiers.java index 3fa7d9ac..af6e4234 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/EntityIdentifiers.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/EntityIdentifiers.java @@ -5,6 +5,8 @@ import io.opentdf.platform.entity.EntityChain; import io.opentdf.platform.entity.Token; +import java.util.Objects; + /** * Convenience constructors for {@link EntityIdentifier}, mirroring the Go SDK helpers * in {@code authorization/v2.ForEmail}, {@code ForClientID}, etc. @@ -33,6 +35,7 @@ private EntityIdentifiers() {} * Returns an EntityIdentifier for a subject identified by email address. */ public static EntityIdentifier forEmail(String email) { + Objects.requireNonNull(email, "email must not be null"); return fromEntity(Entity.newBuilder() .setEmailAddress(email) .setCategory(Entity.Category.CATEGORY_SUBJECT) @@ -43,6 +46,7 @@ public static EntityIdentifier forEmail(String email) { * Returns an EntityIdentifier for a subject identified by client ID. */ public static EntityIdentifier forClientId(String clientId) { + Objects.requireNonNull(clientId, "clientId must not be null"); return fromEntity(Entity.newBuilder() .setClientId(clientId) .setCategory(Entity.Category.CATEGORY_SUBJECT) @@ -53,6 +57,7 @@ public static EntityIdentifier forClientId(String clientId) { * Returns an EntityIdentifier for a subject identified by username. */ public static EntityIdentifier forUserName(String userName) { + Objects.requireNonNull(userName, "userName must not be null"); return fromEntity(Entity.newBuilder() .setUserName(userName) .setCategory(Entity.Category.CATEGORY_SUBJECT) @@ -64,6 +69,7 @@ public static EntityIdentifier forUserName(String userName) { * The authorization service parses the token to derive the entity chain. */ public static EntityIdentifier forToken(String jwt) { + Objects.requireNonNull(jwt, "jwt must not be null"); return EntityIdentifier.newBuilder() .setToken(Token.newBuilder().setJwt(jwt).build()) .build(); diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/EntityIdentifiersTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/EntityIdentifiersTest.java index 294565c5..1466c3cd 100644 --- a/sdk/src/test/java/io/opentdf/platform/sdk/EntityIdentifiersTest.java +++ b/sdk/src/test/java/io/opentdf/platform/sdk/EntityIdentifiersTest.java @@ -53,9 +53,9 @@ void forUserName(String userName) { assertEquals(Entity.Category.CATEGORY_SUBJECT, e.getCategory()); } - @Test - void forToken() { - String jwt = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.test"; + @ParameterizedTest + @ValueSource(strings = {"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.test", ""}) + void forToken(String jwt) { EntityIdentifier eid = EntityIdentifiers.forToken(jwt); assertEquals(EntityIdentifier.IdentifierCase.TOKEN, eid.getIdentifierCase()); @@ -63,11 +63,11 @@ void forToken() { } @Test - void forTokenEmptyString() { - EntityIdentifier eid = EntityIdentifiers.forToken(""); - - assertEquals(EntityIdentifier.IdentifierCase.TOKEN, eid.getIdentifierCase()); - assertEquals("", eid.getToken().getJwt()); + void nullInputsThrow() { + assertThrows(NullPointerException.class, () -> EntityIdentifiers.forEmail(null)); + assertThrows(NullPointerException.class, () -> EntityIdentifiers.forClientId(null)); + assertThrows(NullPointerException.class, () -> EntityIdentifiers.forUserName(null)); + assertThrows(NullPointerException.class, () -> EntityIdentifiers.forToken(null)); } private static EntityChain extractEntityChain(EntityIdentifier eid) {