Skip to content

Commit eeb8805

Browse files
marythoughtclaude
andauthored
feat(sdk): add EntityIdentifier convenience constructors (#346)
## Summary - Add `EntityIdentifiers` utility class with static helpers mirroring the Go SDK's `ForEmail`, `ForClientID`, `ForUserName`, and `ForToken` constructors - Reduces EntityIdentifier construction from ~10 lines of nested builders to a single call **Before:** ```java GetDecisionRequest request = GetDecisionRequest.newBuilder() .setEntityIdentifier( EntityIdentifier.newBuilder() .setEntityChain( EntityChain.newBuilder() .addEntities( Entity.newBuilder() .setEmailAddress("jen@example.com") .setCategory(Entity.Category.CATEGORY_SUBJECT)))) .setAction(Action.newBuilder().setName("read")) .setResource( Resource.newBuilder() .setAttributeValues( Resource.AttributeValues.newBuilder() .addFqns("https://example.com/attr/department/value/finance"))) .build(); ``` **After:** ```java GetDecisionRequest request = GetDecisionRequest.newBuilder() .setEntityIdentifier(EntityIdentifiers.forEmail("jen@example.com")) .setAction(Action.newBuilder().setName("read")) .setResource( Resource.newBuilder() .setAttributeValues( Resource.AttributeValues.newBuilder() .addFqns("https://example.com/attr/department/value/finance"))) .build(); ``` ## Test plan - [x] `EntityIdentifiersTest` — 8 tests covering all 4 helpers with valid and empty string inputs 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added convenient factory methods to create entity identifiers from email addresses, client IDs, usernames, or JWT tokens, returning ready-to-use identifier objects. * **Tests** * Added comprehensive parameterized tests for these factory methods, covering empty-string inputs and null-argument error handling, and verifying the produced identifier structures and token contents. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Mary Dickson <mary.dickson@virtru.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 982b287 commit eeb8805

2 files changed

Lines changed: 163 additions & 0 deletions

File tree

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package io.opentdf.platform.sdk;
2+
3+
import io.opentdf.platform.authorization.v2.EntityIdentifier;
4+
import io.opentdf.platform.entity.Entity;
5+
import io.opentdf.platform.entity.EntityChain;
6+
import io.opentdf.platform.entity.Token;
7+
8+
import java.util.Objects;
9+
10+
/**
11+
* Convenience constructors for {@link EntityIdentifier}, mirroring the Go SDK helpers
12+
* in {@code authorization/v2.ForEmail}, {@code ForClientID}, etc.
13+
*
14+
* <p>Each method builds the full {@code EntityIdentifier} proto so callers avoid
15+
* deeply nested builder chains.
16+
*
17+
* <pre>{@code
18+
* // Before
19+
* EntityIdentifier.newBuilder()
20+
* .setEntityChain(EntityChain.newBuilder()
21+
* .addEntities(Entity.newBuilder()
22+
* .setEmailAddress("jen@example.com")
23+
* .setCategory(Entity.Category.CATEGORY_SUBJECT)))
24+
* .build();
25+
*
26+
* // After
27+
* EntityIdentifiers.forEmail("jen@example.com");
28+
* }</pre>
29+
*/
30+
public final class EntityIdentifiers {
31+
32+
private EntityIdentifiers() {}
33+
34+
/**
35+
* Returns an EntityIdentifier for a subject identified by email address.
36+
*/
37+
public static EntityIdentifier forEmail(String email) {
38+
Objects.requireNonNull(email, "email must not be null");
39+
return fromEntity(Entity.newBuilder()
40+
.setEmailAddress(email)
41+
.setCategory(Entity.Category.CATEGORY_SUBJECT)
42+
.build());
43+
}
44+
45+
/**
46+
* Returns an EntityIdentifier for a subject identified by client ID.
47+
*/
48+
public static EntityIdentifier forClientId(String clientId) {
49+
Objects.requireNonNull(clientId, "clientId must not be null");
50+
return fromEntity(Entity.newBuilder()
51+
.setClientId(clientId)
52+
.setCategory(Entity.Category.CATEGORY_SUBJECT)
53+
.build());
54+
}
55+
56+
/**
57+
* Returns an EntityIdentifier for a subject identified by username.
58+
*/
59+
public static EntityIdentifier forUserName(String userName) {
60+
Objects.requireNonNull(userName, "userName must not be null");
61+
return fromEntity(Entity.newBuilder()
62+
.setUserName(userName)
63+
.setCategory(Entity.Category.CATEGORY_SUBJECT)
64+
.build());
65+
}
66+
67+
/**
68+
* Returns an EntityIdentifier that resolves the entity from the given JWT.
69+
* The authorization service parses the token to derive the entity chain.
70+
*/
71+
public static EntityIdentifier forToken(String jwt) {
72+
Objects.requireNonNull(jwt, "jwt must not be null");
73+
return EntityIdentifier.newBuilder()
74+
.setToken(Token.newBuilder().setJwt(jwt).build())
75+
.build();
76+
}
77+
78+
private static EntityIdentifier fromEntity(Entity entity) {
79+
return EntityIdentifier.newBuilder()
80+
.setEntityChain(EntityChain.newBuilder()
81+
.addEntities(entity)
82+
.build())
83+
.build();
84+
}
85+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package io.opentdf.platform.sdk;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
5+
import io.opentdf.platform.authorization.v2.EntityIdentifier;
6+
import io.opentdf.platform.entity.Entity;
7+
import io.opentdf.platform.entity.EntityChain;
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.params.ParameterizedTest;
10+
import org.junit.jupiter.params.provider.ValueSource;
11+
12+
class EntityIdentifiersTest {
13+
14+
@ParameterizedTest
15+
@ValueSource(strings = {"user@example.com", ""})
16+
void forEmail(String email) {
17+
EntityIdentifier eid = EntityIdentifiers.forEmail(email);
18+
19+
EntityChain chain = extractEntityChain(eid);
20+
assertEquals(1, chain.getEntitiesCount());
21+
22+
Entity e = chain.getEntities(0);
23+
assertEquals(Entity.EntityTypeCase.EMAIL_ADDRESS, e.getEntityTypeCase());
24+
assertEquals(email, e.getEmailAddress());
25+
assertEquals(Entity.Category.CATEGORY_SUBJECT, e.getCategory());
26+
}
27+
28+
@ParameterizedTest
29+
@ValueSource(strings = {"my-client", ""})
30+
void forClientId(String clientId) {
31+
EntityIdentifier eid = EntityIdentifiers.forClientId(clientId);
32+
33+
EntityChain chain = extractEntityChain(eid);
34+
assertEquals(1, chain.getEntitiesCount());
35+
36+
Entity e = chain.getEntities(0);
37+
assertEquals(Entity.EntityTypeCase.CLIENT_ID, e.getEntityTypeCase());
38+
assertEquals(clientId, e.getClientId());
39+
assertEquals(Entity.Category.CATEGORY_SUBJECT, e.getCategory());
40+
}
41+
42+
@ParameterizedTest
43+
@ValueSource(strings = {"alice", ""})
44+
void forUserName(String userName) {
45+
EntityIdentifier eid = EntityIdentifiers.forUserName(userName);
46+
47+
EntityChain chain = extractEntityChain(eid);
48+
assertEquals(1, chain.getEntitiesCount());
49+
50+
Entity e = chain.getEntities(0);
51+
assertEquals(Entity.EntityTypeCase.USER_NAME, e.getEntityTypeCase());
52+
assertEquals(userName, e.getUserName());
53+
assertEquals(Entity.Category.CATEGORY_SUBJECT, e.getCategory());
54+
}
55+
56+
@ParameterizedTest
57+
@ValueSource(strings = {"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.test", ""})
58+
void forToken(String jwt) {
59+
EntityIdentifier eid = EntityIdentifiers.forToken(jwt);
60+
61+
assertEquals(EntityIdentifier.IdentifierCase.TOKEN, eid.getIdentifierCase());
62+
assertEquals(jwt, eid.getToken().getJwt());
63+
}
64+
65+
@Test
66+
void nullInputsThrow() {
67+
assertThrows(NullPointerException.class, () -> EntityIdentifiers.forEmail(null));
68+
assertThrows(NullPointerException.class, () -> EntityIdentifiers.forClientId(null));
69+
assertThrows(NullPointerException.class, () -> EntityIdentifiers.forUserName(null));
70+
assertThrows(NullPointerException.class, () -> EntityIdentifiers.forToken(null));
71+
}
72+
73+
private static EntityChain extractEntityChain(EntityIdentifier eid) {
74+
assertEquals(EntityIdentifier.IdentifierCase.ENTITY_CHAIN, eid.getIdentifierCase(),
75+
"expected ENTITY_CHAIN identifier");
76+
return eid.getEntityChain();
77+
}
78+
}

0 commit comments

Comments
 (0)