diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/Resources.java b/sdk/src/main/java/io/opentdf/platform/sdk/Resources.java new file mode 100644 index 00000000..527bec7f --- /dev/null +++ b/sdk/src/main/java/io/opentdf/platform/sdk/Resources.java @@ -0,0 +1,71 @@ +package io.opentdf.platform.sdk; + +import io.opentdf.platform.authorization.v2.Resource; + +import java.util.Arrays; +import java.util.Objects; + +/** + * Convenience constructors for {@link Resource}, analogous to the + * {@link EntityIdentifiers} helpers for {@link io.opentdf.platform.authorization.v2.EntityIdentifier}. + * + *
Each method builds the full {@code Resource} proto so callers avoid + * deeply nested builder chains. + * + *
{@code
+ * // Before
+ * Resource.newBuilder()
+ * .setAttributeValues(
+ * Resource.AttributeValues.newBuilder()
+ * .addFqns("https://example.com/attr/department/value/finance"))
+ * .build();
+ *
+ * // After
+ * Resources.forAttributeValues("https://example.com/attr/department/value/finance");
+ * }
+ */
+public final class Resources {
+
+ private Resources() {}
+
+ /**
+ * Returns a Resource containing the given attribute value FQNs.
+ * This is the most common Resource variant, used when authorizing against
+ * attribute values attached to data (e.g. those on a TDF).
+ *
+ * @param fqns one or more fully qualified attribute value names
+ * @return a fully built {@link Resource} with the {@code attribute_values} oneof set
+ * @throws NullPointerException if {@code fqns} or any element is null
+ * @throws IllegalArgumentException if {@code fqns} is empty
+ */
+ public static Resource forAttributeValues(String... fqns) {
+ Objects.requireNonNull(fqns, "fqns must not be null");
+ if (fqns.length == 0) {
+ throw new IllegalArgumentException("fqns must not be empty");
+ }
+ for (String fqn : fqns) {
+ Objects.requireNonNull(fqn, "individual fqn must not be null");
+ }
+ return Resource.newBuilder()
+ .setAttributeValues(
+ Resource.AttributeValues.newBuilder()
+ .addAllFqns(Arrays.asList(fqns))
+ .build())
+ .build();
+ }
+
+ /**
+ * Returns a Resource that references a single registered resource value
+ * by its fully qualified name, as stored in platform policy.
+ *
+ * @param fqn the fully qualified name of the registered resource value
+ * @return a fully built {@link Resource} with the {@code registered_resource_value_fqn} oneof set
+ * @throws NullPointerException if {@code fqn} is null
+ */
+ public static Resource forRegisteredResourceValueFqn(String fqn) {
+ Objects.requireNonNull(fqn, "fqn must not be null");
+ return Resource.newBuilder()
+ .setRegisteredResourceValueFqn(fqn)
+ .build();
+ }
+}
diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/ResourcesTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/ResourcesTest.java
new file mode 100644
index 00000000..f1bb5488
--- /dev/null
+++ b/sdk/src/test/java/io/opentdf/platform/sdk/ResourcesTest.java
@@ -0,0 +1,63 @@
+package io.opentdf.platform.sdk;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import io.opentdf.platform.authorization.v2.Resource;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+class ResourcesTest {
+
+ @Test
+ void forAttributeValues_single() {
+ String fqn = "https://example.com/attr/department/value/finance";
+ Resource r = Resources.forAttributeValues(fqn);
+
+ assertEquals(Resource.ResourceCase.ATTRIBUTE_VALUES, r.getResourceCase());
+ assertEquals(1, r.getAttributeValues().getFqnsCount());
+ assertEquals(fqn, r.getAttributeValues().getFqns(0));
+ }
+
+ @Test
+ void forAttributeValues_multiple() {
+ String fqn1 = "https://example.com/attr/department/value/finance";
+ String fqn2 = "https://example.com/attr/level/value/public";
+ Resource r = Resources.forAttributeValues(fqn1, fqn2);
+
+ assertEquals(Resource.ResourceCase.ATTRIBUTE_VALUES, r.getResourceCase());
+ assertEquals(2, r.getAttributeValues().getFqnsCount());
+ assertEquals(fqn1, r.getAttributeValues().getFqns(0));
+ assertEquals(fqn2, r.getAttributeValues().getFqns(1));
+ }
+
+ @Test
+ void forAttributeValues_emptyStringFqn() {
+ Resource r = Resources.forAttributeValues("");
+
+ assertEquals(Resource.ResourceCase.ATTRIBUTE_VALUES, r.getResourceCase());
+ assertEquals(1, r.getAttributeValues().getFqnsCount());
+ assertEquals("", r.getAttributeValues().getFqns(0));
+ }
+
+ @Test
+ void forAttributeValues_emptyArrayThrows() {
+ assertThrows(IllegalArgumentException.class, () -> Resources.forAttributeValues());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"https://example.com/attr/department/value/finance", ""})
+ void forRegisteredResourceValueFqn(String fqn) {
+ Resource r = Resources.forRegisteredResourceValueFqn(fqn);
+
+ assertEquals(Resource.ResourceCase.REGISTERED_RESOURCE_VALUE_FQN, r.getResourceCase());
+ assertEquals(fqn, r.getRegisteredResourceValueFqn());
+ }
+
+ @Test
+ void nullInputsThrow() {
+ assertThrows(NullPointerException.class, () -> Resources.forAttributeValues((String[]) null));
+ assertThrows(NullPointerException.class, () -> Resources.forAttributeValues("valid", null));
+ assertThrows(NullPointerException.class, () -> Resources.forRegisteredResourceValueFqn(null));
+ }
+}