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)); + } +}