From c0165177bd065d5ea02b093ce30a876bcb9512a2 Mon Sep 17 00:00:00 2001 From: Mary Dickson Date: Tue, 17 Feb 2026 13:54:13 -0800 Subject: [PATCH 1/3] feat(sdk): DSPX-2418 add attribute discovery methods --- .../java/io/opentdf/platform/sdk/SDK.java | 165 +++++++++- .../opentdf/platform/sdk/DiscoveryTest.java | 310 ++++++++++++++++++ 2 files changed, 474 insertions(+), 1 deletion(-) create mode 100644 sdk/src/test/java/io/opentdf/platform/sdk/DiscoveryTest.java diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java index 9e80b0ab..91745db1 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java @@ -1,11 +1,21 @@ package io.opentdf.platform.sdk; +import com.connectrpc.ConnectException; import com.connectrpc.Interceptor; - +import com.connectrpc.ResponseMessageKt; import com.connectrpc.impl.ProtocolClient; import io.opentdf.platform.authorization.AuthorizationServiceClientInterface; +import io.opentdf.platform.authorization.Entity; +import io.opentdf.platform.authorization.EntityEntitlements; +import io.opentdf.platform.authorization.GetEntitlementsRequest; +import io.opentdf.platform.authorization.GetEntitlementsResponse; +import io.opentdf.platform.policy.Attribute; +import io.opentdf.platform.policy.PageRequest; import io.opentdf.platform.policy.SimpleKasKey; import io.opentdf.platform.policy.attributes.AttributesServiceClientInterface; +import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsRequest; +import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsResponse; +import io.opentdf.platform.policy.attributes.ListAttributesRequest; import io.opentdf.platform.policy.kasregistry.KeyAccessServerRegistryServiceClientInterface; import io.opentdf.platform.policy.namespaces.NamespaceServiceClientInterface; import io.opentdf.platform.policy.resourcemapping.ResourceMappingServiceClientInterface; @@ -17,7 +27,12 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.channels.SeekableByteChannel; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; /** * The SDK class represents a software development kit for interacting with the @@ -179,6 +194,143 @@ public String getPlatformUrl() { return platformUrl; } + // Caps the pagination loop in listAttributes to prevent unbounded memory growth + // if a server repeatedly returns a non-zero next_offset. + private static final int MAX_LIST_ATTRIBUTES_PAGES = 1000; + + // Matches the server-side limit on GetAttributeValuesByFqns so callers get a + // clear local error instead of a cryptic server rejection. + private static final int MAX_VALIDATE_FQNS = 250; + + /** + * Lists all active attributes available on the platform, auto-paginating through all results. + * An optional namespace name or ID may be provided to filter results. + * + *

Use this before calling {@code createTDF()} to see what attributes are available for data tagging. + * + * @param namespace optional namespace name or ID to filter results + * @return list of all active {@link Attribute} objects + * @throws SDKException if a service error occurs or pagination exceeds the maximum page limit + */ + public List listAttributes(String... namespace) { + ListAttributesRequest.Builder reqBuilder = ListAttributesRequest.newBuilder(); + if (namespace.length > 0 && namespace[0] != null) { + reqBuilder.setNamespace(namespace[0]); + } + List result = new ArrayList<>(); + for (int pages = 0; pages < MAX_LIST_ATTRIBUTES_PAGES; pages++) { + try { + var resp = ResponseMessageKt.getOrThrow( + services.attributes() + .listAttributesBlocking(reqBuilder.build(), Collections.emptyMap()) + .execute()); + result.addAll(resp.getAttributesList()); + int nextOffset = resp.getPagination().getNextOffset(); + if (nextOffset == 0) { + return result; + } + reqBuilder.setPagination(PageRequest.newBuilder().setOffset(nextOffset).build()); + } catch (ConnectException e) { + throw new SDKException("listing attributes: " + e.getMessage(), e); + } + } + throw new SDKException("listing attributes: exceeded maximum page limit (" + MAX_LIST_ATTRIBUTES_PAGES + ")"); + } + + /** + * Checks that all provided attribute value FQNs exist on the platform. + * Validates FQN format first, then verifies existence via the platform API. + * + *

Use this before {@code createTDF()} to catch missing or misspelled attributes early + * instead of discovering the problem at decryption time. + * + * @param fqns list of attribute value FQNs in the form + * {@code https:///attr//value/} + * @throws AttributeNotFoundException if any FQNs are not found on the platform + * @throws SDKException if input validation fails or a service error occurs + */ + public void validateAttributes(List fqns) { + if (fqns == null || fqns.isEmpty()) { + return; + } + if (fqns.size() > MAX_VALIDATE_FQNS) { + throw new SDKException("too many attribute FQNs: " + fqns.size() + + " exceeds maximum of " + MAX_VALIDATE_FQNS); + } + for (String fqn : fqns) { + try { + new Autoconfigure.AttributeValueFQN(fqn); + } catch (AutoConfigureException e) { + throw new SDKException("invalid attribute value FQN \"" + fqn + "\": " + e.getMessage(), e); + } + } + GetAttributeValuesByFqnsResponse resp; + try { + resp = ResponseMessageKt.getOrThrow( + services.attributes() + .getAttributeValuesByFqnsBlocking( + GetAttributeValuesByFqnsRequest.newBuilder().addAllFqns(fqns).build(), + Collections.emptyMap()) + .execute()); + } catch (ConnectException e) { + throw new SDKException("validating attributes: " + e.getMessage(), e); + } + Map found = resp.getFqnAttributeValuesMap(); + List missing = fqns.stream() + .filter(fqn -> !found.containsKey(fqn)) + .collect(Collectors.toList()); + if (!missing.isEmpty()) { + throw new AttributeNotFoundException("attribute not found: " + String.join(", ", missing)); + } + } + + /** + * Returns the attribute value FQNs assigned to an entity (person or non-person entity). + * + *

Use this to inspect what attributes a user, service account, or other entity has been + * granted before making authorization decisions or constructing access policies. + * + * @param entity the entity to look up; must not be null + * @return list of attribute value FQNs assigned to the entity, or an empty list if none + * @throws SDKException if entity is null or a service error occurs + */ + public List getEntityAttributes(Entity entity) { + if (entity == null) { + throw new SDKException("entity must not be null"); + } + GetEntitlementsResponse resp; + try { + resp = ResponseMessageKt.getOrThrow( + services.authorization() + .getEntitlementsBlocking( + GetEntitlementsRequest.newBuilder().addEntities(entity).build(), + Collections.emptyMap()) + .execute()); + } catch (ConnectException e) { + throw new SDKException("getting entity attributes: " + e.getMessage(), e); + } + String entityId = entity.getId(); + for (EntityEntitlements e : resp.getEntitlementsList()) { + if (entityId.isEmpty() || e.getEntityId().equals(entityId)) { + return e.getAttributeValueFqnsList(); + } + } + return Collections.emptyList(); + } + + /** + * Checks that a single attribute value FQN is valid in format and exists on the platform. + * + *

This is a convenience wrapper around {@link #validateAttributes(List)} for the single-FQN case. + * + * @param fqn the attribute value FQN to validate + * @throws AttributeNotFoundException if the FQN does not exist on the platform + * @throws SDKException if the FQN format is invalid or a service error occurs + */ + public void validateAttributeValue(String fqn) { + validateAttributes(Collections.singletonList(fqn)); + } + /** * Indicates that the TDF is malformed in some way */ @@ -284,4 +436,15 @@ public AssertionException(String errorMessage, String id) { super("assertion id: "+ id + "; " + errorMessage); } } + + /** + * {@link AttributeNotFoundException} is thrown by {@link #validateAttributes(List)} and + * {@link #validateAttributeValue(String)} when one or more attribute FQNs are not found + * on the platform. + */ + public static class AttributeNotFoundException extends SDKException { + public AttributeNotFoundException(String errorMessage) { + super(errorMessage); + } + } } diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/DiscoveryTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/DiscoveryTest.java new file mode 100644 index 00000000..805a7bc8 --- /dev/null +++ b/sdk/src/test/java/io/opentdf/platform/sdk/DiscoveryTest.java @@ -0,0 +1,310 @@ +package io.opentdf.platform.sdk; + +import io.opentdf.platform.authorization.Entity; +import io.opentdf.platform.authorization.EntityEntitlements; +import io.opentdf.platform.authorization.GetEntitlementsResponse; +import io.opentdf.platform.authorization.AuthorizationServiceClientInterface; +import io.opentdf.platform.policy.Attribute; +import io.opentdf.platform.policy.PageResponse; +import io.opentdf.platform.policy.attributes.AttributesServiceClientInterface; +import io.opentdf.platform.policy.attributes.GetAttributeValuesByFqnsResponse; +import io.opentdf.platform.policy.attributes.ListAttributesRequest; +import io.opentdf.platform.policy.attributes.ListAttributesResponse; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class DiscoveryTest { + + // Helper: build a minimal SDK wired with mock services. + private SDK sdkWith(AttributesServiceClientInterface attrSvc, + AuthorizationServiceClientInterface authzSvc) { + var services = new FakeServicesBuilder() + .setAttributesService(attrSvc) + .setAuthorizationService(authzSvc) + .build(); + return new SDK(services, null, null, null, null, null); + } + + // Helper: build a ListAttributesResponse with optional next_offset. + private ListAttributesResponse listResponse(List attrs, int nextOffset) { + var builder = ListAttributesResponse.newBuilder().addAllAttributes(attrs); + if (nextOffset != 0) { + builder.setPagination(PageResponse.newBuilder().setNextOffset(nextOffset).build()); + } + return builder.build(); + } + + // Helper: build a GetAttributeValuesByFqns response containing exactly the given FQNs. + private GetAttributeValuesByFqnsResponse fqnResponse(String... presentFqns) { + var builder = GetAttributeValuesByFqnsResponse.newBuilder(); + for (String fqn : presentFqns) { + builder.putFqnAttributeValues(fqn, + GetAttributeValuesByFqnsResponse.AttributeAndValue.getDefaultInstance()); + } + return builder.build(); + } + + // Helper: build a minimal Attribute proto with a given FQN. + private Attribute attr(String fqn) { + return Attribute.newBuilder().setFqn(fqn).build(); + } + + // --- listAttributes --- + + @Test + void listAttributes_emptyResult() { + var attrSvc = mock(AttributesServiceClientInterface.class); + when(attrSvc.listAttributesBlocking(any(), any())) + .thenReturn(TestUtil.successfulUnaryCall(listResponse(Collections.emptyList(), 0))); + + var sdk = sdkWith(attrSvc, null); + List result = sdk.listAttributes(); + assertThat(result).isEmpty(); + } + + @Test + void listAttributes_singlePage() { + var expected = List.of( + attr("https://example.com/attr/level/value/high"), + attr("https://example.com/attr/level/value/low")); + var attrSvc = mock(AttributesServiceClientInterface.class); + when(attrSvc.listAttributesBlocking(any(), any())) + .thenReturn(TestUtil.successfulUnaryCall(listResponse(expected, 0))); + + var sdk = sdkWith(attrSvc, null); + assertThat(sdk.listAttributes()).containsExactlyElementsOf(expected); + } + + @Test + void listAttributes_multiPage() { + var page1 = List.of(attr("https://example.com/attr/a/value/1")); + var page2 = List.of(attr("https://example.com/attr/b/value/2")); + + var attrSvc = mock(AttributesServiceClientInterface.class); + var callCount = new AtomicInteger(0); + when(attrSvc.listAttributesBlocking(any(), any())).thenAnswer(invocation -> { + int call = callCount.getAndIncrement(); + if (call == 0) { + return TestUtil.successfulUnaryCall(listResponse(page1, 1)); + } + return TestUtil.successfulUnaryCall(listResponse(page2, 0)); + }); + + var sdk = sdkWith(attrSvc, null); + var result = sdk.listAttributes(); + assertThat(callCount.get()).as("should have paginated twice").isEqualTo(2); + var expected = new ArrayList<>(page1); + expected.addAll(page2); + assertThat(result).containsExactlyElementsOf(expected); + } + + @Test + void listAttributes_namespaceFilter() { + var attrSvc = mock(AttributesServiceClientInterface.class); + var capturedReq = new ListAttributesRequest[1]; + when(attrSvc.listAttributesBlocking(any(), any())).thenAnswer(invocation -> { + capturedReq[0] = (ListAttributesRequest) invocation.getArgument(0); + return TestUtil.successfulUnaryCall(listResponse(Collections.emptyList(), 0)); + }); + + var sdk = sdkWith(attrSvc, null); + sdk.listAttributes("my-namespace"); + assertThat(capturedReq[0].getNamespace()).isEqualTo("my-namespace"); + } + + @Test + void listAttributes_pageLimitExceeded() { + var attrSvc = mock(AttributesServiceClientInterface.class); + // Always return a non-zero next_offset to simulate a runaway server. + when(attrSvc.listAttributesBlocking(any(), any())) + .thenReturn(TestUtil.successfulUnaryCall( + listResponse(List.of(attr("https://example.com/attr/a/value/1")), 1))); + + var sdk = sdkWith(attrSvc, null); + assertThatThrownBy(sdk::listAttributes) + .isInstanceOf(SDKException.class) + .hasMessageContaining("exceeded maximum page limit"); + } + + // --- validateAttributes --- + + @Test + void validateAttributes_nullInput_noOp() { + var sdk = sdkWith(null, null); + // Should not throw and must not call any service. + sdk.validateAttributes(null); + } + + @Test + void validateAttributes_emptyInput_noOp() { + var sdk = sdkWith(null, null); + sdk.validateAttributes(Collections.emptyList()); + } + + @Test + void validateAttributes_allFound() { + var fqns = List.of( + "https://example.com/attr/level/value/high", + "https://example.com/attr/type/value/secret"); + var attrSvc = mock(AttributesServiceClientInterface.class); + when(attrSvc.getAttributeValuesByFqnsBlocking(any(), any())) + .thenReturn(TestUtil.successfulUnaryCall(fqnResponse(fqns.toArray(new String[0])))); + + var sdk = sdkWith(attrSvc, null); + // Should complete without exception. + sdk.validateAttributes(fqns); + } + + @Test + void validateAttributes_someMissing() { + var fqns = List.of( + "https://example.com/attr/level/value/high", + "https://example.com/attr/type/value/missing"); + var attrSvc = mock(AttributesServiceClientInterface.class); + // Only return the first FQN. + when(attrSvc.getAttributeValuesByFqnsBlocking(any(), any())) + .thenReturn(TestUtil.successfulUnaryCall(fqnResponse(fqns.get(0)))); + + var sdk = sdkWith(attrSvc, null); + assertThatThrownBy(() -> sdk.validateAttributes(fqns)) + .isInstanceOf(SDK.AttributeNotFoundException.class) + .hasMessageContaining("https://example.com/attr/type/value/missing"); + } + + @Test + void validateAttributes_allMissing() { + var fqns = List.of( + "https://example.com/attr/a/value/x", + "https://example.com/attr/b/value/y"); + var attrSvc = mock(AttributesServiceClientInterface.class); + when(attrSvc.getAttributeValuesByFqnsBlocking(any(), any())) + .thenReturn(TestUtil.successfulUnaryCall(fqnResponse())); + + var sdk = sdkWith(attrSvc, null); + assertThatThrownBy(() -> sdk.validateAttributes(fqns)) + .isInstanceOf(SDK.AttributeNotFoundException.class); + } + + @Test + void validateAttributes_tooManyFqns() { + var fqns = new ArrayList(); + for (int i = 0; i <= 250; i++) { + fqns.add("https://example.com/attr/level/value/v" + i); + } + var sdk = sdkWith(null, null); + assertThatThrownBy(() -> sdk.validateAttributes(fqns)) + .isInstanceOf(SDKException.class) + .hasMessageContaining("too many attribute FQNs"); + } + + @Test + void validateAttributes_invalidFqnFormat() { + var sdk = sdkWith(null, null); + assertThatThrownBy(() -> sdk.validateAttributes(List.of("not-a-valid-fqn"))) + .isInstanceOf(SDKException.class) + .hasMessageContaining("invalid attribute value FQN"); + } + + // --- getEntityAttributes --- + + @Test + void getEntityAttributes_nullEntity() { + var sdk = sdkWith(null, null); + assertThatThrownBy(() -> sdk.getEntityAttributes(null)) + .isInstanceOf(SDKException.class) + .hasMessageContaining("entity must not be null"); + } + + @Test + void getEntityAttributes_found() { + var expectedFqns = List.of( + "https://example.com/attr/clearance/value/secret", + "https://example.com/attr/country/value/us"); + var authzSvc = mock(AuthorizationServiceClientInterface.class); + when(authzSvc.getEntitlementsBlocking(any(), any())).thenReturn( + TestUtil.successfulUnaryCall(GetEntitlementsResponse.newBuilder() + .addEntitlements(EntityEntitlements.newBuilder() + .setEntityId("e1") + .addAllAttributeValueFqns(expectedFqns) + .build()) + .build())); + + var sdk = sdkWith(null, authzSvc); + var entity = Entity.newBuilder().setId("e1").setEmailAddress("alice@example.com").build(); + assertThat(sdk.getEntityAttributes(entity)).containsExactlyElementsOf(expectedFqns); + } + + @Test + void getEntityAttributes_noEntitlements() { + var authzSvc = mock(AuthorizationServiceClientInterface.class); + when(authzSvc.getEntitlementsBlocking(any(), any())).thenReturn( + TestUtil.successfulUnaryCall(GetEntitlementsResponse.newBuilder().build())); + + var sdk = sdkWith(null, authzSvc); + var entity = Entity.newBuilder().setId("e1").setClientId("my-service").build(); + assertThat(sdk.getEntityAttributes(entity)).isEmpty(); + } + + @Test + void getEntityAttributes_idMismatch() { + // Server returns entitlements for a different entity ID than requested. + var authzSvc = mock(AuthorizationServiceClientInterface.class); + when(authzSvc.getEntitlementsBlocking(any(), any())).thenReturn( + TestUtil.successfulUnaryCall(GetEntitlementsResponse.newBuilder() + .addEntitlements(EntityEntitlements.newBuilder() + .setEntityId("other-entity") + .addAttributeValueFqns("https://example.com/attr/a/value/x") + .build()) + .build())); + + var sdk = sdkWith(null, authzSvc); + var entity = Entity.newBuilder().setId("e1").setEmailAddress("alice@example.com").build(); + assertThat(sdk.getEntityAttributes(entity)) + .as("should return empty when no entitlement matches the requested entity ID") + .isEmpty(); + } + + // --- validateAttributeValue --- + + @Test + void validateAttributeValue_validAndExists() { + var fqn = "https://example.com/attr/level/value/high"; + var attrSvc = mock(AttributesServiceClientInterface.class); + when(attrSvc.getAttributeValuesByFqnsBlocking(any(), any())) + .thenReturn(TestUtil.successfulUnaryCall(fqnResponse(fqn))); + + var sdk = sdkWith(attrSvc, null); + // Should complete without exception. + sdk.validateAttributeValue(fqn); + } + + @Test + void validateAttributeValue_validButMissing() { + var fqn = "https://example.com/attr/level/value/nonexistent"; + var attrSvc = mock(AttributesServiceClientInterface.class); + when(attrSvc.getAttributeValuesByFqnsBlocking(any(), any())) + .thenReturn(TestUtil.successfulUnaryCall(fqnResponse())); + + var sdk = sdkWith(attrSvc, null); + assertThatThrownBy(() -> sdk.validateAttributeValue(fqn)) + .isInstanceOf(SDK.AttributeNotFoundException.class); + } + + @Test + void validateAttributeValue_invalidFormat() { + var sdk = sdkWith(null, null); + assertThatThrownBy(() -> sdk.validateAttributeValue("bad-fqn-format")) + .isInstanceOf(SDKException.class) + .hasMessageContaining("invalid attribute value FQN"); + } +} From c079a2ca0d60b1b8ac811b7fa0e05fef790ad5ab Mon Sep 17 00:00:00 2001 From: Mary Dickson Date: Tue, 17 Feb 2026 14:13:15 -0800 Subject: [PATCH 2/3] style(sdk): move constants to class top; rename lambda parameter Move MAX_LIST_ATTRIBUTES_PAGES and MAX_VALIDATE_FQNS to the top of the class body (before instance fields), matching standard Java placement for private static final constants. Rename the lambda parameter in the validateAttributes stream filter from 'fqn' to 'f' to avoid the same name being used for both the enhanced-for loop variable and the lambda parameter in the same method. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Mary Dickson --- .../java/io/opentdf/platform/sdk/SDK.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java index 91745db1..fda2bdfb 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java @@ -41,6 +41,15 @@ * platform. */ public class SDK implements AutoCloseable { + + // Caps the pagination loop in listAttributes to prevent unbounded memory growth + // if a server repeatedly returns a non-zero next_offset. + private static final int MAX_LIST_ATTRIBUTES_PAGES = 1000; + + // Matches the server-side limit on GetAttributeValuesByFqns so callers get a + // clear local error instead of a cryptic server rejection. + private static final int MAX_VALIDATE_FQNS = 250; + private final Services services; private final TrustManager trustManager; private final Interceptor authInterceptor; @@ -194,14 +203,6 @@ public String getPlatformUrl() { return platformUrl; } - // Caps the pagination loop in listAttributes to prevent unbounded memory growth - // if a server repeatedly returns a non-zero next_offset. - private static final int MAX_LIST_ATTRIBUTES_PAGES = 1000; - - // Matches the server-side limit on GetAttributeValuesByFqns so callers get a - // clear local error instead of a cryptic server rejection. - private static final int MAX_VALIDATE_FQNS = 250; - /** * Lists all active attributes available on the platform, auto-paginating through all results. * An optional namespace name or ID may be provided to filter results. @@ -277,7 +278,7 @@ public void validateAttributes(List fqns) { } Map found = resp.getFqnAttributeValuesMap(); List missing = fqns.stream() - .filter(fqn -> !found.containsKey(fqn)) + .filter(f -> !found.containsKey(f)) .collect(Collectors.toList()); if (!missing.isEmpty()) { throw new AttributeNotFoundException("attribute not found: " + String.join(", ", missing)); From 375bb9815a9814edd772c80e98ba180f6e966ca0 Mon Sep 17 00:00:00 2001 From: Mary Dickson Date: Tue, 17 Feb 2026 14:29:59 -0800 Subject: [PATCH 3/3] fix(sdk): remove unreachable ConnectException catch blocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The blocking service client methods do not declare `throws ConnectException` in their Java signatures, so catching it is a compilation error. Remove the three try-catch wrappers in listAttributes, validateAttributes, and getEntityAttributes, letting exceptions propagate naturally — consistent with the existing pattern in Autoconfigure.java. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Mary Dickson --- .../java/io/opentdf/platform/sdk/SDK.java | 57 +++++++------------ 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java index fda2bdfb..99c87634 100644 --- a/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java +++ b/sdk/src/main/java/io/opentdf/platform/sdk/SDK.java @@ -1,6 +1,5 @@ package io.opentdf.platform.sdk; -import com.connectrpc.ConnectException; import com.connectrpc.Interceptor; import com.connectrpc.ResponseMessageKt; import com.connectrpc.impl.ProtocolClient; @@ -220,20 +219,16 @@ public List listAttributes(String... namespace) { } List result = new ArrayList<>(); for (int pages = 0; pages < MAX_LIST_ATTRIBUTES_PAGES; pages++) { - try { - var resp = ResponseMessageKt.getOrThrow( - services.attributes() - .listAttributesBlocking(reqBuilder.build(), Collections.emptyMap()) - .execute()); - result.addAll(resp.getAttributesList()); - int nextOffset = resp.getPagination().getNextOffset(); - if (nextOffset == 0) { - return result; - } - reqBuilder.setPagination(PageRequest.newBuilder().setOffset(nextOffset).build()); - } catch (ConnectException e) { - throw new SDKException("listing attributes: " + e.getMessage(), e); + var resp = ResponseMessageKt.getOrThrow( + services.attributes() + .listAttributesBlocking(reqBuilder.build(), Collections.emptyMap()) + .execute()); + result.addAll(resp.getAttributesList()); + int nextOffset = resp.getPagination().getNextOffset(); + if (nextOffset == 0) { + return result; } + reqBuilder.setPagination(PageRequest.newBuilder().setOffset(nextOffset).build()); } throw new SDKException("listing attributes: exceeded maximum page limit (" + MAX_LIST_ATTRIBUTES_PAGES + ")"); } @@ -265,17 +260,12 @@ public void validateAttributes(List fqns) { throw new SDKException("invalid attribute value FQN \"" + fqn + "\": " + e.getMessage(), e); } } - GetAttributeValuesByFqnsResponse resp; - try { - resp = ResponseMessageKt.getOrThrow( - services.attributes() - .getAttributeValuesByFqnsBlocking( - GetAttributeValuesByFqnsRequest.newBuilder().addAllFqns(fqns).build(), - Collections.emptyMap()) - .execute()); - } catch (ConnectException e) { - throw new SDKException("validating attributes: " + e.getMessage(), e); - } + GetAttributeValuesByFqnsResponse resp = ResponseMessageKt.getOrThrow( + services.attributes() + .getAttributeValuesByFqnsBlocking( + GetAttributeValuesByFqnsRequest.newBuilder().addAllFqns(fqns).build(), + Collections.emptyMap()) + .execute()); Map found = resp.getFqnAttributeValuesMap(); List missing = fqns.stream() .filter(f -> !found.containsKey(f)) @@ -299,17 +289,12 @@ public List getEntityAttributes(Entity entity) { if (entity == null) { throw new SDKException("entity must not be null"); } - GetEntitlementsResponse resp; - try { - resp = ResponseMessageKt.getOrThrow( - services.authorization() - .getEntitlementsBlocking( - GetEntitlementsRequest.newBuilder().addEntities(entity).build(), - Collections.emptyMap()) - .execute()); - } catch (ConnectException e) { - throw new SDKException("getting entity attributes: " + e.getMessage(), e); - } + GetEntitlementsResponse resp = ResponseMessageKt.getOrThrow( + services.authorization() + .getEntitlementsBlocking( + GetEntitlementsRequest.newBuilder().addEntities(entity).build(), + Collections.emptyMap()) + .execute()); String entityId = entity.getId(); for (EntityEntitlements e : resp.getEntitlementsList()) { if (entityId.isEmpty() || e.getEntityId().equals(entityId)) {