All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dev.openfga.sdk.api.client.OpenFgaClient Maven / Gradle / Ivy

/*
 * OpenFGA
 * A high performance and flexible authorization/permission engine built for developers and inspired by Google Zanzibar.
 *
 * The version of the OpenAPI document: 1.x
 * Contact: [email protected]
 *
 * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
 * https://openapi-generator.tech
 * Do not edit the class manually.
 */

package dev.openfga.sdk.api.client;

import static dev.openfga.sdk.util.StringUtil.isNullOrWhitespace;
import static java.util.UUID.randomUUID;

import dev.openfga.sdk.api.*;
import dev.openfga.sdk.api.client.model.*;
import dev.openfga.sdk.api.configuration.*;
import dev.openfga.sdk.api.model.*;
import dev.openfga.sdk.errors.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class OpenFgaClient {
    private final ApiClient apiClient;
    private ClientConfiguration configuration;
    private OpenFgaApi api;

    private static final String CLIENT_BULK_REQUEST_ID_HEADER = "X-OpenFGA-Client-Bulk-Request-Id";
    private static final String CLIENT_METHOD_HEADER = "X-OpenFGA-Client-Method";
    private static final int DEFAULT_MAX_METHOD_PARALLEL_REQS = 10;

    public OpenFgaClient(ClientConfiguration configuration) throws FgaInvalidParameterException {
        this(configuration, new ApiClient());
    }

    public OpenFgaClient(ClientConfiguration configuration, ApiClient apiClient) throws FgaInvalidParameterException {
        this.apiClient = apiClient;
        this.configuration = configuration;
        this.api = new OpenFgaApi(configuration, apiClient);
    }

    /* ***********
     * Utilities *
     *************/

    public void setStoreId(String storeId) {
        configuration.storeId(storeId);
    }

    public void setAuthorizationModelId(String authorizationModelId) {
        configuration.authorizationModelId(authorizationModelId);
    }

    public void setConfiguration(ClientConfiguration configuration) throws FgaInvalidParameterException {
        this.configuration = configuration;
        this.api = new OpenFgaApi(configuration, apiClient);
    }

    /* ********
     * Stores *
     **********/

    /**
     * ListStores - Get a paginated list of stores.
     */
    public CompletableFuture listStores() throws FgaInvalidParameterException {
        configuration.assertValid();
        return call(() -> api.listStores(null, null)).thenApply(ClientListStoresResponse::new);
    }

    /**
     * ListStores - Get a paginated list of stores.
     */
    public CompletableFuture listStores(ClientListStoresOptions options)
            throws FgaInvalidParameterException {
        configuration.assertValid();
        var overrides = new ConfigurationOverride().addHeaders(options);
        return call(() -> api.listStores(options.getPageSize(), options.getContinuationToken(), overrides))
                .thenApply(ClientListStoresResponse::new);
    }

    /**
     * CreateStore - Initialize a store
     */
    public CompletableFuture createStore(CreateStoreRequest request)
            throws FgaInvalidParameterException {
        return createStore(request, null);
    }

    /**
     * CreateStore - Initialize a store
     */
    public CompletableFuture createStore(
            CreateStoreRequest request, ClientCreateStoreOptions options) throws FgaInvalidParameterException {
        configuration.assertValid();
        var overrides = new ConfigurationOverride().addHeaders(options);
        return call(() -> api.createStore(request, overrides)).thenApply(ClientCreateStoreResponse::new);
    }

    /**
     * GetStore - Get information about the current store.
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture getStore() throws FgaInvalidParameterException {
        return getStore(null);
    }

    /**
     * GetStore - Get information about the current store.
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture getStore(ClientGetStoreOptions options)
            throws FgaInvalidParameterException {
        configuration.assertValid();
        String storeId = configuration.getStoreIdChecked();
        var overrides = new ConfigurationOverride().addHeaders(options);
        return call(() -> api.getStore(storeId, overrides)).thenApply(ClientGetStoreResponse::new);
    }

    /**
     * DeleteStore - Delete a store
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture deleteStore() throws FgaInvalidParameterException {
        return deleteStore(null);
    }

    /**
     * DeleteStore - Delete a store
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture deleteStore(ClientDeleteStoreOptions options)
            throws FgaInvalidParameterException {
        configuration.assertValid();
        String storeId = configuration.getStoreIdChecked();
        var overrides = new ConfigurationOverride().addHeaders(options);
        return call(() -> api.deleteStore(storeId, overrides)).thenApply(ClientDeleteStoreResponse::new);
    }

    /* **********************
     * Authorization Models *
     ************************/

    /**
     * ReadAuthorizationModels - Read all authorization models
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture readAuthorizationModels()
            throws FgaInvalidParameterException {
        return readAuthorizationModels(null);
    }

    /**
     * ReadAuthorizationModels - Read all authorization models
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture readAuthorizationModels(
            ClientReadAuthorizationModelsOptions options) throws FgaInvalidParameterException {
        configuration.assertValid();
        String storeId = configuration.getStoreIdChecked();

        Integer pageSize;
        String continuationToken;

        if (options != null) {
            pageSize = options.getPageSize();
            continuationToken = options.getContinuationToken();
        } else {
            // null are valid for these values
            continuationToken = null;
            pageSize = null;
        }

        var overrides = new ConfigurationOverride().addHeaders(options);

        return call(() -> api.readAuthorizationModels(storeId, pageSize, continuationToken, overrides))
                .thenApply(ClientReadAuthorizationModelsResponse::new);
    }

    /**
     * WriteAuthorizationModel - Create a new version of the authorization model
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture writeAuthorizationModel(
            WriteAuthorizationModelRequest request) throws FgaInvalidParameterException {
        return writeAuthorizationModel(request, null);
    }

    /**
     * WriteAuthorizationModel - Create a new version of the authorization model
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture writeAuthorizationModel(
            WriteAuthorizationModelRequest request, ClientWriteAuthorizationModelOptions options)
            throws FgaInvalidParameterException {
        configuration.assertValid();
        String storeId = configuration.getStoreIdChecked();
        var overrides = new ConfigurationOverride().addHeaders(options);
        return call(() -> api.writeAuthorizationModel(storeId, request, overrides))
                .thenApply(ClientWriteAuthorizationModelResponse::new);
    }

    /**
     * ReadAuthorizationModel - Read the current authorization model
     *
     * @throws FgaInvalidParameterException When either the Store ID or Authorization Model ID are null, empty, or whitespace
     */
    public CompletableFuture readAuthorizationModel()
            throws FgaInvalidParameterException {
        configuration.assertValid();
        String storeId = configuration.getStoreIdChecked();
        String authorizationModelId = configuration.getAuthorizationModelIdChecked();
        return call(() -> api.readAuthorizationModel(storeId, authorizationModelId))
                .thenApply(ClientReadAuthorizationModelResponse::new);
    }

    /**
     * ReadAuthorizationModel - Read the current authorization model
     *
     * @throws FgaInvalidParameterException When either the Store ID or Authorization Model ID are null, empty, or whitespace
     */
    public CompletableFuture readAuthorizationModel(
            ClientReadAuthorizationModelOptions options) throws FgaInvalidParameterException {
        configuration.assertValid();
        String storeId = configuration.getStoreIdChecked();
        String authorizationModelId = options.getAuthorizationModelIdChecked();
        var overrides = new ConfigurationOverride().addHeaders(options);
        return call(() -> api.readAuthorizationModel(storeId, authorizationModelId, overrides))
                .thenApply(ClientReadAuthorizationModelResponse::new);
    }

    /**
     * ReadLatestAuthorizationModel - Read the latest authorization model for the current store
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture readLatestAuthorizationModel()
            throws FgaInvalidParameterException {
        return readLatestAuthorizationModel(null);
    }

    /**
     * ReadLatestAuthorizationModel - Read the latest authorization model for the current store
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture readLatestAuthorizationModel(
            ClientReadLatestAuthorizationModelOptions options) throws FgaInvalidParameterException {
        configuration.assertValid();
        String storeId = configuration.getStoreIdChecked();
        var overrides = new ConfigurationOverride().addHeaders(options);
        return call(() -> api.readAuthorizationModels(storeId, 1, null, overrides))
                .thenApply(ClientReadAuthorizationModelResponse::latestOf);
    }

    /* *********************
     * Relationship Tuples *
     ***********************/

    /**
     * Read Changes - Read the list of historical relationship tuple writes and deletes
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture readChanges(ClientReadChangesRequest request)
            throws FgaInvalidParameterException {
        return readChanges(request, null);
    }

    /**
     * Read Changes - Read the list of historical relationship tuple writes and deletes
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture readChanges(
            ClientReadChangesRequest request, ClientReadChangesOptions readChangesOptions)
            throws FgaInvalidParameterException {
        configuration.assertValid();
        String storeId = configuration.getStoreIdChecked();
        var options = readChangesOptions != null ? readChangesOptions : new ClientReadChangesOptions();
        var overrides = new ConfigurationOverride().addHeaders(options);
        return call(() -> api.readChanges(
                        storeId, request.getType(), options.getPageSize(), options.getContinuationToken(), overrides))
                .thenApply(ClientReadChangesResponse::new);
    }

    /**
     * Read - Read tuples previously written to the store (does not evaluate)
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture read(ClientReadRequest request) throws FgaInvalidParameterException {
        return read(request, null);
    }

    /**
     * Read - Read tuples previously written to the store (does not evaluate)
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture read(ClientReadRequest request, ClientReadOptions options)
            throws FgaInvalidParameterException {
        configuration.assertValid();
        String storeId = configuration.getStoreIdChecked();

        ReadRequest body = new ReadRequest();

        if (request != null
                && (request.getUser() != null || request.getRelation() != null || request.getObject() != null)) {
            body.tupleKey(new ReadRequestTupleKey()
                    .user(request.getUser())
                    .relation(request.getRelation())
                    ._object(request.getObject()));
        }

        if (options != null) {
            body.pageSize(options.getPageSize()).continuationToken(options.getContinuationToken());
            if (options.getConsistency() != null) {
                body.consistency(options.getConsistency());
            }
        }

        var overrides = new ConfigurationOverride().addHeaders(options);

        return call(() -> api.read(storeId, body, overrides)).thenApply(ClientReadResponse::new);
    }

    /**
     * Write - Create or delete relationship tuples
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture write(ClientWriteRequest request)
            throws FgaInvalidParameterException {
        return write(request, null);
    }

    /**
     * Write - Create or delete relationship tuples
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture write(ClientWriteRequest request, ClientWriteOptions options)
            throws FgaInvalidParameterException {
        configuration.assertValid();
        String storeId = configuration.getStoreIdChecked();

        if (options != null && options.disableTransactions()) {
            return writeNonTransaction(storeId, request, options);
        }

        return writeTransactions(storeId, request, options);
    }

    private CompletableFuture writeTransactions(
            String storeId, ClientWriteRequest request, ClientWriteOptions options) {

        WriteRequest body = new WriteRequest();

        var writeTuples = request.getWrites();
        if (writeTuples != null && !writeTuples.isEmpty()) {
            body.writes(ClientTupleKey.asWriteRequestWrites(writeTuples));
        }

        var deleteTuples = request.getDeletes();
        if (deleteTuples != null && !deleteTuples.isEmpty()) {
            body.deletes(ClientTupleKeyWithoutCondition.asWriteRequestDeletes(deleteTuples));
        }

        if (options != null && !isNullOrWhitespace(options.getAuthorizationModelId())) {
            body.authorizationModelId(options.getAuthorizationModelId());
        } else {
            String authorizationModelId = configuration.getAuthorizationModelId();
            body.authorizationModelId(authorizationModelId);
        }

        var overrides = new ConfigurationOverride().addHeaders(options);

        return call(() -> api.write(storeId, body, overrides)).thenApply(ClientWriteResponse::new);
    }

    private CompletableFuture writeNonTransaction(
            String storeId, ClientWriteRequest request, ClientWriteOptions writeOptions) {

        var options = writeOptions != null
                ? writeOptions
                : new ClientWriteOptions().transactionChunkSize(DEFAULT_MAX_METHOD_PARALLEL_REQS);

        if (options.getAdditionalHeaders() == null) {
            options.additionalHeaders(new HashMap<>());
        }
        options.getAdditionalHeaders().putIfAbsent(CLIENT_METHOD_HEADER, "Write");
        options.getAdditionalHeaders()
                .putIfAbsent(CLIENT_BULK_REQUEST_ID_HEADER, randomUUID().toString());

        int chunkSize = options.getTransactionChunkSize();
        var writeTransactions = chunksOf(chunkSize, request.getWrites()).map(ClientWriteRequest::ofWrites);
        var deleteTransactions = chunksOf(chunkSize, request.getDeletes()).map(ClientWriteRequest::ofDeletes);

        var transactions = Stream.concat(writeTransactions, deleteTransactions).collect(Collectors.toList());

        if (transactions.isEmpty()) {
            var emptyTransaction = new ClientWriteRequest().writes(null).deletes(null);
            return this.writeTransactions(storeId, emptyTransaction, writeOptions);
        }

        var futureResponse = this.writeTransactions(storeId, transactions.get(0), options);

        for (int i = 1; i < transactions.size(); i++) {
            final int index = i; // Must be final in this scope for closure.

            // The resulting completable future of this chain will result in either:
            // 1. The first exception thrown in a failed completion. Other thenCompose() will not be evaluated.
            // 2. The final successful ClientWriteResponse.
            futureResponse = futureResponse.thenCompose(
                    _response -> this.writeTransactions(storeId, transactions.get(index), options));
        }

        return futureResponse;
    }

    private  Stream> chunksOf(int chunkSize, List list) {
        if (list == null || list.isEmpty()) {
            return Stream.empty();
        }

        int nChunks = (int) Math.ceil(list.size() / (double) chunkSize);

        int finalEndExclusive = list.size();
        Stream.Builder> chunks = Stream.builder();

        for (int i = 0; i < nChunks; i++) {
            List chunk = list.subList(i * chunkSize, Math.min((i + 1) * chunkSize, finalEndExclusive));
            chunks.add(chunk);
        }

        return chunks.build();
    }

    /**
     * WriteTuples - Utility method to write tuples, wraps Write
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture writeTuples(List tupleKeys)
            throws FgaInvalidParameterException {
        return writeTuples(tupleKeys, null);
    }

    /**
     * WriteTuples - Utility method to write tuples, wraps Write
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture writeTuples(
            List tupleKeys, ClientWriteTuplesOptions options) throws FgaInvalidParameterException {
        configuration.assertValid();
        String storeId = configuration.getStoreIdChecked();

        var body = new WriteRequest();

        body.writes(ClientTupleKey.asWriteRequestWrites(tupleKeys));

        String authorizationModelId = configuration.getAuthorizationModelId();
        if (!isNullOrWhitespace(authorizationModelId)) {
            body.authorizationModelId(authorizationModelId);
        }

        var overrides = new ConfigurationOverride().addHeaders(options);

        return call(() -> api.write(storeId, body, overrides)).thenApply(ClientWriteResponse::new);
    }

    /**
     * DeleteTuples - Utility method to delete tuples, wraps Write
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture deleteTuples(List tupleKeys)
            throws FgaInvalidParameterException {
        return deleteTuples(tupleKeys, null);
    }

    /**
     * DeleteTuples - Utility method to delete tuples, wraps Write
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture deleteTuples(
            List tupleKeys, ClientDeleteTuplesOptions options)
            throws FgaInvalidParameterException {
        configuration.assertValid();
        String storeId = configuration.getStoreIdChecked();

        var body = new WriteRequest();

        body.deletes(ClientTupleKeyWithoutCondition.asWriteRequestDeletes(tupleKeys));

        String authorizationModelId = configuration.getAuthorizationModelId();
        if (!isNullOrWhitespace(authorizationModelId)) {
            body.authorizationModelId(authorizationModelId);
        }

        var overrides = new ConfigurationOverride().addHeaders(options);

        return call(() -> api.write(storeId, body, overrides)).thenApply(ClientWriteResponse::new);
    }

    /* **********************
     * Relationship Queries *
     ***********************/

    /**
     * Check - Check if a user has a particular relation with an object (evaluates)
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture check(ClientCheckRequest request)
            throws FgaInvalidParameterException {
        return check(request, null);
    }

    /**
     * Check - Check if a user has a particular relation with an object (evaluates)
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture check(ClientCheckRequest request, ClientCheckOptions options)
            throws FgaInvalidParameterException {
        configuration.assertValid();
        String storeId = configuration.getStoreIdChecked();

        CheckRequest body = request.asCheckRequest();
        if (options != null) {
            if (options.getConsistency() != null) {
                body.consistency(options.getConsistency());
            }

            // Set authorizationModelId from options if available; otherwise, use the default from configuration
            String authorizationModelId = !isNullOrWhitespace(options.getAuthorizationModelId())
                    ? options.getAuthorizationModelId()
                    : configuration.getAuthorizationModelId();
            body.authorizationModelId(authorizationModelId);
        } else {
            body.setAuthorizationModelId(configuration.getAuthorizationModelId());
        }

        var overrides = new ConfigurationOverride().addHeaders(options);

        return call(() -> api.check(storeId, body, overrides)).thenApply(ClientCheckResponse::new);
    }

    /**
     * BatchCheck - Run a set of checks (evaluates)
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture> batchCheck(List requests)
            throws FgaInvalidParameterException {
        return batchCheck(requests, null);
    }

    /**
     * BatchCheck - Run a set of checks (evaluates)
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture> batchCheck(
            List requests, ClientBatchCheckOptions batchCheckOptions)
            throws FgaInvalidParameterException {
        configuration.assertValid();
        configuration.assertValidStoreId();

        var options = batchCheckOptions != null
                ? batchCheckOptions
                : new ClientBatchCheckOptions().maxParallelRequests(DEFAULT_MAX_METHOD_PARALLEL_REQS);
        if (options.getAdditionalHeaders() == null) {
            options.additionalHeaders(new HashMap<>());
        }
        options.getAdditionalHeaders().putIfAbsent(CLIENT_METHOD_HEADER, "BatchCheck");
        options.getAdditionalHeaders()
                .putIfAbsent(CLIENT_BULK_REQUEST_ID_HEADER, randomUUID().toString());

        int maxParallelRequests = options.getMaxParallelRequests() != null
                ? options.getMaxParallelRequests()
                : DEFAULT_MAX_METHOD_PARALLEL_REQS;
        var executor = Executors.newScheduledThreadPool(maxParallelRequests);
        var latch = new CountDownLatch(requests.size());

        var responses = new ConcurrentLinkedQueue();

        final var clientCheckOptions = options.asClientCheckOptions();

        Consumer singleClientCheckRequest =
                request -> call(() -> this.check(request, clientCheckOptions))
                        .handleAsync(ClientBatchCheckResponse.asyncHandler(request))
                        .thenAccept(responses::add)
                        .thenRun(latch::countDown);

        try {
            requests.forEach(request -> executor.execute(() -> singleClientCheckRequest.accept(request)));
            latch.await();
            return CompletableFuture.completedFuture(new ArrayList<>(responses));
        } catch (Exception e) {
            return CompletableFuture.failedFuture(e);
        }
    }

    /**
     * Expand - Expands the relationships in userset tree format (evaluates)
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture expand(ClientExpandRequest request)
            throws FgaInvalidParameterException {
        return expand(request, null);
    }

    /**
     * Expand - Expands the relationships in userset tree format (evaluates)
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture expand(ClientExpandRequest request, ClientExpandOptions options)
            throws FgaInvalidParameterException {
        configuration.assertValid();
        String storeId = configuration.getStoreIdChecked();

        ExpandRequest body = new ExpandRequest();

        if (request != null) {
            body.tupleKey(
                    new ExpandRequestTupleKey().relation(request.getRelation())._object(request.getObject()));
        }

        if (options != null) {
            if (options.getConsistency() != null) {
                body.consistency(options.getConsistency());
            }

            // Set authorizationModelId from options if available; otherwise, use the default from configuration
            String authorizationModelId = !isNullOrWhitespace(options.getAuthorizationModelId())
                    ? options.getAuthorizationModelId()
                    : configuration.getAuthorizationModelId();
            body.authorizationModelId(authorizationModelId);
        } else {
            body.setAuthorizationModelId(configuration.getAuthorizationModelId());
        }

        var overrides = new ConfigurationOverride().addHeaders(options);

        return call(() -> api.expand(storeId, body, overrides)).thenApply(ClientExpandResponse::new);
    }

    /**
     * ListObjects - List the objects of a particular type that the user has a certain relation to (evaluates)
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture listObjects(ClientListObjectsRequest request)
            throws FgaInvalidParameterException {
        return listObjects(request, null);
    }

    /**
     * ListObjects - List the objects of a particular type that the user has a certain relation to (evaluates)
     *
     * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace
     */
    public CompletableFuture listObjects(
            ClientListObjectsRequest request, ClientListObjectsOptions options) throws FgaInvalidParameterException {
        configuration.assertValid();
        String storeId = configuration.getStoreIdChecked();

        ListObjectsRequest body = new ListObjectsRequest();

        if (request != null) {
            body.user(request.getUser()).relation(request.getRelation()).type(request.getType());
            if (request.getContextualTupleKeys() != null) {
                var contextualTuples = request.getContextualTupleKeys();
                var bodyContextualTuples = ClientTupleKey.asContextualTupleKeys(contextualTuples);
                body.contextualTuples(bodyContextualTuples);
            }
            if (request.getContext() != null) {
                body.context(request.getContext());
            }
        }

        if (options != null) {
            if (options.getConsistency() != null) {
                body.consistency(options.getConsistency());
            }

            // Set authorizationModelId from options if available; otherwise, use the default from configuration
            String authorizationModelId = !isNullOrWhitespace(options.getAuthorizationModelId())
                    ? options.getAuthorizationModelId()
                    : configuration.getAuthorizationModelId();
            body.authorizationModelId(authorizationModelId);
        } else {
            body.setAuthorizationModelId(configuration.getAuthorizationModelId());
        }

        var overrides = new ConfigurationOverride().addHeaders(options);

        return call(() -> api.listObjects(storeId, body, overrides)).thenApply(ClientListObjectsResponse::new);
    }

    /**
     * ListRelations - List allowed relations a user has with an object (evaluates)
     */
    public CompletableFuture listRelations(ClientListRelationsRequest request)
            throws FgaInvalidParameterException {
        return listRelations(request, null);
    }

    /**
     * ListRelations - List allowed relations a user has with an object (evaluates)
     */
    public CompletableFuture listRelations(
            ClientListRelationsRequest request, ClientListRelationsOptions listRelationsOptions)
            throws FgaInvalidParameterException {
        if (request.getRelations() == null || request.getRelations().isEmpty()) {
            throw new FgaInvalidParameterException(
                    "At least 1 relation to check has to be provided when calling ListRelations");
        }

        var options = listRelationsOptions != null
                ? listRelationsOptions
                : new ClientListRelationsOptions().maxParallelRequests(DEFAULT_MAX_METHOD_PARALLEL_REQS);
        if (options.getAdditionalHeaders() == null) {
            options.additionalHeaders(new HashMap<>());
        }
        options.getAdditionalHeaders().putIfAbsent(CLIENT_METHOD_HEADER, "ListRelations");
        options.getAdditionalHeaders()
                .putIfAbsent(CLIENT_BULK_REQUEST_ID_HEADER, randomUUID().toString());

        var batchCheckRequests = request.getRelations().stream()
                .map(relation -> new ClientCheckRequest()
                        .user(request.getUser())
                        .relation(relation)
                        ._object(request.getObject())
                        .contextualTuples(request.getContextualTupleKeys())
                        .context(request.getContext()))
                .collect(Collectors.toList());

        return this.batchCheck(batchCheckRequests, options.asClientBatchCheckOptions())
                .thenCompose(responses -> call(() -> ClientListRelationsResponse.fromBatchCheckResponses(responses)));
    }

    /**
     * ListUsers - List all users of the given type that the object has a relation with (evaluates)
     */
    public CompletableFuture listUsers(ClientListUsersRequest request)
            throws FgaInvalidParameterException {
        return listUsers(request, null);
    }

    /**
     * ListUsers - List all users of the given type that the object has a relation with (evaluates)
     */
    public CompletableFuture listUsers(
            ClientListUsersRequest request, ClientListUsersOptions options) throws FgaInvalidParameterException {
        configuration.assertValid();
        String storeId = configuration.getStoreIdChecked();

        ListUsersRequest body = new ListUsersRequest();

        if (request != null) {
            body._object(request.getObject()).relation(request.getRelation()).userFilters(request.getUserFilters());
            if (request.getContextualTupleKeys() != null) {
                var contextualTuples = request.getContextualTupleKeys();
                var bodyContextualTuples = ClientTupleKey.asContextualTupleKeys(contextualTuples);
                body.contextualTuples(bodyContextualTuples.getTupleKeys());
            }
            if (request.getContext() != null) {
                body.context(request.getContext());
            }
        }

        if (options != null) {
            if (options.getConsistency() != null) {
                body.consistency(options.getConsistency());
            }

            // Set authorizationModelId from options if available; otherwise, use the default from configuration
            String authorizationModelId = !isNullOrWhitespace(options.getAuthorizationModelId())
                    ? options.getAuthorizationModelId()
                    : configuration.getAuthorizationModelId();
            body.authorizationModelId(authorizationModelId);
        } else {
            body.setAuthorizationModelId(configuration.getAuthorizationModelId());
        }

        var overrides = new ConfigurationOverride().addHeaders(options);

        return call(() -> api.listUsers(storeId, body, overrides)).thenApply(ClientListUsersResponse::new);
    }

    /* ************
     * Assertions *
     **************/

    /**
     * ReadAssertions - Read assertions for a particular authorization model
     *
     * @throws FgaInvalidParameterException When either the Store ID or Authorization Model ID is null, empty, or whitespace
     */
    public CompletableFuture readAssertions() throws FgaInvalidParameterException {
        return readAssertions(null);
    }

    /**
     * ReadAssertions - Read assertions for a particular authorization model
     *
     * @throws FgaInvalidParameterException When either the Store ID or Authorization Model ID is null, empty, or whitespace
     */
    public CompletableFuture readAssertions(ClientReadAssertionsOptions options)
            throws FgaInvalidParameterException {
        configuration.assertValid();
        String storeId = configuration.getStoreIdChecked();

        String authorizationModelId;
        if (options != null && options.hasValidAuthorizationModelId()) {
            authorizationModelId = options.getAuthorizationModelId();
        } else {
            authorizationModelId = configuration.getAuthorizationModelIdChecked();
        }

        var overrides = new ConfigurationOverride().addHeaders(options);

        return call(() -> api.readAssertions(storeId, authorizationModelId, overrides))
                .thenApply(ClientReadAssertionsResponse::new);
    }

    /**
     * WriteAssertions - Updates assertions for a particular authorization model
     *
     * @throws FgaInvalidParameterException When either the Store ID or Authorization Model ID is null, empty, or whitespace
     */
    public CompletableFuture writeAssertions(List assertions)
            throws FgaInvalidParameterException {
        return writeAssertions(assertions, null);
    }

    /**
     * WriteAssertions - Updates assertions for a particular authorization model
     *
     * @throws FgaInvalidParameterException When either the Store ID or Authorization Model ID is null, empty, or whitespace
     */
    public CompletableFuture writeAssertions(
            List assertions, ClientWriteAssertionsOptions options)
            throws FgaInvalidParameterException {
        configuration.assertValid();
        String storeId = configuration.getStoreIdChecked();

        String authorizationModelId;
        if (options != null && options.hasValidAuthorizationModelId()) {
            authorizationModelId = options.getAuthorizationModelId();
        } else {
            authorizationModelId = configuration.getAuthorizationModelIdChecked();
        }

        WriteAssertionsRequest body = new WriteAssertionsRequest().assertions(ClientAssertion.asAssertions(assertions));

        var overrides = new ConfigurationOverride().addHeaders(options);

        return call(() -> api.writeAssertions(storeId, authorizationModelId, body, overrides))
                .thenApply(ClientWriteAssertionsResponse::new);
    }

    /**
     * A {@link FunctionalInterface} for calling a low-level API from {@link OpenFgaApi}. It wraps exceptions
     * encountered with {@link CompletableFuture#failedFuture(Throwable)}
     *
     * @param  The type of API response
     */
    @FunctionalInterface
    private interface CheckedAsyncInvocation {
        CompletableFuture call() throws Throwable;
    }

    private  CompletableFuture call(CheckedAsyncInvocation action) {
        try {
            return action.call();
        } catch (CompletionException completionException) {
            return CompletableFuture.failedFuture(completionException.getCause());
        } catch (Throwable throwable) {
            return CompletableFuture.failedFuture(throwable);
        }
    }

    /**
     * A {@link FunctionalInterface} for calling any function that could throw an exception.
     * It wraps exceptions encountered with {@link CompletableFuture#failedFuture(Throwable)}
     *
     * @param  The return type
     */
    @FunctionalInterface
    private interface CheckedInvocation {
        R call() throws Throwable;
    }

    private  CompletableFuture call(CheckedInvocation action) {
        try {
            return CompletableFuture.completedFuture(action.call());
        } catch (CompletionException completionException) {
            return CompletableFuture.failedFuture(completionException.getCause());
        } catch (Throwable throwable) {
            return CompletableFuture.failedFuture(throwable);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy