diff --git a/config/clients/java/.openapi-generator-ignore b/config/clients/java/.openapi-generator-ignore index e3549f0b..787ce9e4 100644 --- a/config/clients/java/.openapi-generator-ignore +++ b/config/clients/java/.openapi-generator-ignore @@ -14,3 +14,6 @@ pom.xml .github/workflows/maven.yml git_push.sh .travis.yml + +# Model tests intentionally ignored +**/test/**/model/* diff --git a/config/clients/java/config.overrides.json b/config/clients/java/config.overrides.json index 0897a7d5..ab4e2e1e 100644 --- a/config/clients/java/config.overrides.json +++ b/config/clients/java/config.overrides.json @@ -52,107 +52,111 @@ "templateType": "SupportingFiles" }, "client-ClientBatchCheckResponse.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientBatchCheckResponse.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientBatchCheckResponse.java", "templateType": "SupportingFiles" }, "client-ClientCheckRequest.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientCheckRequest.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientCheckRequest.java", "templateType": "SupportingFiles" }, "client-ClientCheckResponse.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientCheckResponse.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientCheckResponse.java", "templateType": "SupportingFiles" }, "client-ClientCreateStoreResponse.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientCreateStoreResponse.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientCreateStoreResponse.java", "templateType": "SupportingFiles" }, "client-ClientDeleteStoreResponse.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientDeleteStoreResponse.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientDeleteStoreResponse.java", "templateType": "SupportingFiles" }, "client-ClientExpandRequest.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientExpandRequest.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientExpandRequest.java", "templateType": "SupportingFiles" }, "client-ClientExpandResponse.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientExpandResponse.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientExpandResponse.java", "templateType": "SupportingFiles" }, "client-ClientGetStoreResponse.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientGetStoreResponse.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientGetStoreResponse.java", "templateType": "SupportingFiles" }, "client-ClientListObjectsRequest.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientListObjectsRequest.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientListObjectsRequest.java", "templateType": "SupportingFiles" }, "client-ClientListObjectsResponse.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientListObjectsResponse.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientListObjectsResponse.java", "templateType": "SupportingFiles" }, "client-ClientListStoresResponse.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientListStoresResponse.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientListStoresResponse.java", "templateType": "SupportingFiles" }, "client-ClientListRelationsRequest.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientListRelationsRequest.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientListRelationsRequest.java", "templateType": "SupportingFiles" }, "client-ClientListRelationsResponse.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientListRelationsResponse.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientListRelationsResponse.java", "templateType": "SupportingFiles" }, "client-ClientReadAssertionsResponse.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientReadAssertionsResponse.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientReadAssertionsResponse.java", "templateType": "SupportingFiles" }, "client-ClientReadAuthorizationModelResponse.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientReadAuthorizationModelResponse.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientReadAuthorizationModelResponse.java", "templateType": "SupportingFiles" }, "client-ClientReadAuthorizationModelsResponse.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientReadAuthorizationModelsResponse.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientReadAuthorizationModelsResponse.java", + "templateType": "SupportingFiles" + }, + "client-ClientReadChangesRequest.java.mustache" : { + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientReadChangesRequest.java", "templateType": "SupportingFiles" }, "client-ClientReadChangesResponse.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientReadChangesResponse.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientReadChangesResponse.java", "templateType": "SupportingFiles" }, "client-ClientReadRequest.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientReadRequest.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientReadRequest.java", "templateType": "SupportingFiles" }, "client-ClientReadResponse.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientReadResponse.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientReadResponse.java", "templateType": "SupportingFiles" }, "client-ClientRelationshipCondition.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientRelationshipCondition.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientRelationshipCondition.java", "templateType": "SupportingFiles" }, "client-ClientTupleKey.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientTupleKey.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientTupleKey.java", "templateType": "SupportingFiles" }, "client-ClientTupleKeyWithoutCondition.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientTupleKeyWithoutCondition.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientTupleKeyWithoutCondition.java", "templateType": "SupportingFiles" }, "client-ClientWriteAssertionsResponse.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientWriteAssertionsResponse.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientWriteAssertionsResponse.java", "templateType": "SupportingFiles" }, "client-ClientWriteAuthorizationModelResponse.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientWriteAuthorizationModelResponse.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientWriteAuthorizationModelResponse.java", "templateType": "SupportingFiles" }, "client-ClientWriteRequest.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientWriteRequest.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientWriteRequest.java", "templateType": "SupportingFiles" }, "client-ClientWriteResponse.java.mustache" : { - "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientWriteResponse.java", + "destinationFilename": "src/main/java/dev/openfga/sdk/api/client/model/ClientWriteResponse.java", "templateType": "SupportingFiles" }, "client-HttpRequestAttempt.java.mustache" : { @@ -191,6 +195,10 @@ "destinationFilename": "src/test/java/dev/openfga/sdk/api/auth/OAuth2ClientTest.java", "templateType": "SupportingFiles" }, + "config-AdditionalHeadersSupplier.java.mustache" : { + "destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/AdditionalHeadersSupplier.java", + "templateType": "SupportingFiles" + }, "config-ApiToken.java.mustache" : { "destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ApiToken.java", "templateType": "SupportingFiles" @@ -211,6 +219,10 @@ "destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientConfiguration.java", "templateType": "SupportingFiles" }, + "config-ClientCreateStoreOptions.java.mustache" : { + "destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientCreateStoreOptions.java", + "templateType": "SupportingFiles" + }, "config-ClientCredentials.java.mustache" : { "destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientCredentials.java", "templateType": "SupportingFiles" @@ -219,10 +231,22 @@ "destinationFilename": "src/test/java/dev/openfga/sdk/api/configuration/ClientCredentialsTest.java", "templateType": "SupportingFiles" }, + "config-ClientDeleteStoreOptions.java.mustache" : { + "destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientDeleteStoreOptions.java", + "templateType": "SupportingFiles" + }, + "config-ClientDeleteTuplesOptions.java.mustache" : { + "destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientDeleteTuplesOptions.java", + "templateType": "SupportingFiles" + }, "config-ClientExpandOptions.java.mustache" : { "destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientExpandOptions.java", "templateType": "SupportingFiles" }, + "config-ClientGetStoreOptions.java.mustache" : { + "destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientGetStoreOptions.java", + "templateType": "SupportingFiles" + }, "config-ClientListObjectsOptions.java.mustache" : { "destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientListObjectsOptions.java", "templateType": "SupportingFiles" @@ -251,6 +275,10 @@ "destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientReadChangesOptions.java", "templateType": "SupportingFiles" }, + "config-ClientReadLatestAuthorizationModelOptions.java.mustache" : { + "destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientReadLatestAuthorizationModelOptions.java", + "templateType": "SupportingFiles" + }, "config-ClientReadOptions.java.mustache" : { "destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientReadOptions.java", "templateType": "SupportingFiles" @@ -259,10 +287,18 @@ "destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientWriteAssertionsOptions.java", "templateType": "SupportingFiles" }, + "config-ClientWriteAuthorizationModelOptions.java.mustache" : { + "destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientWriteAuthorizationModelOptions.java", + "templateType": "SupportingFiles" + }, "config-ClientWriteOptions.java.mustache" : { "destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientWriteOptions.java", "templateType": "SupportingFiles" }, + "config-ClientWriteTuplesOptions.java.mustache" : { + "destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientWriteTuplesOptions.java", + "templateType": "SupportingFiles" + }, "config-Configuration.java.mustache" : { "destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/Configuration.java", "templateType": "SupportingFiles" diff --git a/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache b/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache index 217c02eb..20fcabbb 100644 --- a/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache +++ b/config/clients/java/template/OpenFgaApiIntegrationTest.java.mustache @@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.*; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import {{clientPackage}}.model.*; import {{configPackage}}.*; import {{modelPackage}}.*; import java.io.IOException; diff --git a/config/clients/java/template/OpenFgaApiTest.java.mustache b/config/clients/java/template/OpenFgaApiTest.java.mustache index 45909e96..d113a2ef 100644 --- a/config/clients/java/template/OpenFgaApiTest.java.mustache +++ b/config/clients/java/template/OpenFgaApiTest.java.mustache @@ -8,10 +8,11 @@ import static org.mockito.Mockito.*; import com.fasterxml.jackson.databind.ObjectMapper; import com.pgssoft.httpclient.HttpClientMock; -import {{invokerPackage}}.*; -import {{modelPackage}}.*; +import {{clientPackage}}.model.*; import {{configPackage}}.*; import {{errorsPackage}}.*; +import {{invokerPackage}}.*; +import {{modelPackage}}.*; import java.net.http.HttpClient; import java.time.Duration; import java.util.LinkedHashMap; diff --git a/config/clients/java/template/README_calling_api.mustache b/config/clients/java/template/README_calling_api.mustache index 716240ec..ef5b4e11 100644 --- a/config/clients/java/template/README_calling_api.mustache +++ b/config/clients/java/template/README_calling_api.mustache @@ -6,8 +6,11 @@ Get a paginated list of stores. [API Documentation]({{apiDocsUrl}}/docs/api#/Stores/ListStores) +> Passing `ClientListStoresOptions` is optional. All fields of `ClientListStoresOptions` are optional. + ```java var options = new ClientListStoresOptions() + .additionalHeaders(Map.of("Some-Http-Header", "Some value")) .pageSize(10) .continuationToken("..."); var stores = fgaClient.listStores(options); @@ -21,9 +24,12 @@ Initialize a store. [API Documentation]({{apiDocsUrl}}/docs/api#/Stores/CreateStore) +> Passing `ClientCreateStoreOptions` is optional. All fields of `ClientCreateStoreOptions` are optional. + ```java var request = new CreateStoreRequest().name("FGA Demo"); -var store = fgaClient.createStore(request).get(); +var options = new ClientCreateStoreOptions().additionalHeaders(Map.of("Some-Http-Header", "Some value")); +var store = fgaClient.createStore(request, options).get(); // store.getId() = "01FQH7V8BEG3GPQW93KTRFR8JB" @@ -43,8 +49,11 @@ Get information about the current store. > Requires a client initialized with a storeId +> Passing `ClientGetStoreOptions` is optional. All fields of `ClientGetStoreOptions` are optional. + ```java -var store = fgaClient.getStore().get(); +var options = new ClientGetStoreOptions().additionalHeaders(Map.of("Some-Http-Header", "Some value")); +var store = fgaClient.getStore(options).get(); // store = { "id": "01FQH7V8BEG3GPQW93KTRFR8JB", "name": "FGA Demo Store", "created_at": "2022-01-01T00:00:00.000Z", "updated_at": "2022-01-01T00:00:00.000Z" } ``` @@ -57,8 +66,11 @@ Delete a store. > Requires a client initialized with a storeId +> Passing `ClientDeleteStoreOptions` is optional. All fields of `ClientDeleteStoreOptions` are optional. + ```java -var store = fgaClient.deleteStore().get(); +var options = new ClientDeleteStoreOptions().additionalHeaders(Map.of("Some-Http-Header", "Some value")); +var store = fgaClient.deleteStore(options).get(); ``` #### Authorization Models @@ -69,8 +81,11 @@ Read all authorization models in the store. [API Documentation]({{apiDocsUrl}}#/Authorization%20Models/ReadAuthorizationModels) +> Passing `ClientReadAuthorizationModelsOptions` is optional. All fields of `ClientReadAuthorizationModelsOptions` are optional. + ```java var options = new ClientReadAuthorizationModelsOptions() + .additionalHeaders(Map.of("Some-Http-Header", "Some value")) .pageSize(10) .continuationToken("..."); var response = fgaClient.readAuthorizationModels(options).get(); @@ -92,6 +107,8 @@ Create a new authorization model. > You can use the OpenFGA [CLI](https://github.com/openfga/cli) or [Syntax Transformer](https://github.com/openfga/syntax-transformer) to convert between the OpenFGA DSL and the JSON authorization model. +> Passing `ClientWriteAuthorizationModelOptions` is optional. All fields of `ClientWriteAuthorizationModelOptions` are optional. + ```java var request = new WriteAuthorizationModelRequest() @@ -120,8 +137,9 @@ var request = new WriteAuthorizationModelRequest() )) ) )); +var options = new ClientWriteAuthorizationModelOptions().additionalHeaders(Map.of("Some-Http-Header", "Some value")); -var response = fgaClient.writeAuthorizationModel(request).get(); +var response = fgaClient.writeAuthorizationModel(request, options).get(); // response.getAuthorizationModelId() = "01GXSA8YR785C4FYS3C0RTG7B1" ``` @@ -132,8 +150,11 @@ Read a particular authorization model. [API Documentation]({{apiDocsUrl}}#/Authorization%20Models/ReadAuthorizationModel) +> Passing `ClientReadAuthorizationModelOptions` is optional. All fields of `ClientReadAuthorizationModelOptions` are optional. + ```java var options = new ClientReadAuthorizationModelOptions() + .additionalHeaders(Map.of("Some-Http-Header", "Some value")) // You can rely on the model id set in the configuration or override it for this specific request .authorizationModelId("01GXSA8YR785C4FYS3C0RTG7B1"); @@ -150,8 +171,11 @@ Reads the latest authorization model (note: this ignores the model id in configu [API Documentation]({{apiDocsUrl}}#/Authorization%20Models/ReadAuthorizationModel) +> Passing `ClientReadLatestAuthorizationModelOptions` is optional. All fields of `ClientReadLatestAuthorizationModelOptions` are optional. + ```java -var response = fgaClient.readLatestAuthorizationModel().get(); +var options = new ClientReadLatestAuthorizationModelOptions().additionalHeaders(Map.of("Some-Http-Header", "Some value")); +var response = fgaClient.readLatestAuthorizationModel(options).get(); // response.getAuthorizationModel().getId() = "01GXSA8YR785C4FYS3C0RTG7B1" // response.getAuthorizationModel().SchemaVersion() = "1.1" @@ -166,13 +190,16 @@ Reads the list of historical relationship tuple writes and deletes. [API Documentation]({{apiDocsUrl}}#/Relationship%20Tuples/ReadChanges) +> Passing `ClientReadChangesOptions` is optional. All fields of `ClientReadChangesOptions` are optional. + ```java +var request = new ClientReadChangesRequest().type("document"); var options = new ClientReadChangesOptions() - .type("document") + .additionalHeaders(Map.of("Some-Http-Header", "Some value")) .pageSize(10) .continuationToken("..."); -var response = fgaClient.readChanges(options).get(); +var response = fgaClient.readChanges(request, options).get(); // response.getContinuationToken() = ... // response.getChanges() = [ @@ -187,6 +214,8 @@ Reads the relationship tuples stored in the database. It does not evaluate nor e [API Documentation]({{apiDocsUrl}}#/Relationship%20Tuples/Read) +> Passing `ClientReadOptions` is optional. All fields of `ClientReadOptions` are optional. + ```java // Find if a relationship tuple stating that a certain user is a viewer of a certain document var request = new ClientReadRequest() @@ -213,6 +242,7 @@ var request = new ClientReadRequest() var request = new ClientReadRequest(); var options = new ClientReadOptions() + .additionalHeaders(Map.of("Some-Http-Header", "Some value")) .pageSize(10) .continuationToken("..."); @@ -228,6 +258,8 @@ Create and/or delete relationship tuples to update the system state. [API Documentation]({{apiDocsUrl}}#/Relationship%20Tuples/Write) +> Passing `ClientWriteOptions` is optional. All fields of `ClientWriteOptions` are optional. + ###### Transaction mode (default) By default, write runs in a transaction mode where any invalid operation (deleting a non-existing tuple, creating an existing tuple, one of the tuples was invalid) or a server error will fail the entire operation. @@ -251,9 +283,11 @@ var request = new ClientWriteRequest() ._object("document:roadmap") )); -// You can rely on the model id set in the configuration or override it for this specific request var options = new ClientWriteOptions() - .authorizationModelId("01GXSA8YR785C4FYS3C0RTG7B1"); + .additionalHeaders(Map.of("Some-Http-Header", "Some value")) + // You can rely on the model id set in the configuration or override it for this specific request + .authorizationModelId("01GXSA8YR785C4FYS3C0RTG7B1") + .disableTransactions(false); var response = fgaClient.write(request, options).get(); ``` @@ -264,6 +298,9 @@ Convenience `WriteTuples` and `DeleteTuples` methods are also available. The SDK will split the writes into separate requests and send them sequentially to avoid violating rate limits. +> Passing `ClientWriteOptions` with `.disableTransactions(true)` is required to use non-transaction mode. +> All other fields of `ClientWriteOptions` are optional. + ```java var request = new ClientWriteRequest() .writes(List.of( @@ -283,6 +320,7 @@ var request = new ClientWriteRequest() ._object("document:roadmap") )); var options = new ClientWriteOptions() + .additionalHeaders(Map.of("Some-Http-Header", "Some value")) // You can rely on the model id set in the configuration or override it for this specific request .authorizationModelId("01GXSA8YR785C4FYS3C0RTG7B1") .disableTransactions(true) @@ -299,12 +337,15 @@ Check if a user has a particular relation with an object. [API Documentation]({{apiDocsUrl}}#/Relationship%20Queries/Check) +> Passing `ClientCheckOptions` is optional. All fields of `ClientCheckOptions` are optional. + ```java var request = new ClientCheckRequest() .user("user:81684243-9356-4421-8fbf-a4f8d36aa31b") .relation("writer") ._object("document:roadmap"); var options = new ClientCheckOptions() + .additionalHeaders(Map.of("Some-Http-Header", "Some value")) // You can rely on the model id set in the configuration or override it for this specific request .authorizationModelId("01GXSA8YR785C4FYS3C0RTG7B1"); @@ -317,6 +358,8 @@ var response = fgaClient.check(request, options).get(); Run a set of [checks](#check). Batch Check will return `allowed: false` if it encounters an error, and will return the error in the body. If 429s or 5xxs are encountered, the underlying check will retry up to {{defaultMaxRetry}} times before giving up. +> Passing `ClientBatchCheckOptions` is optional. All fields of `ClientBatchCheckOptions` are optional. + ```java var request = List.of( new ClientCheckRequest() @@ -349,6 +392,7 @@ var request = List.of( ._object("document:roadmap") ); var options = new ClientBatchCheckOptions() + .additionalHeaders(Map.of("Some-Http-Header", "Some value")) // You can rely on the model id set in the configuration or override it for this specific request .authorizationModelId("01GXSA8YR785C4FYS3C0RTG7B1") .maxParallelRequests(5); // Max number of requests to issue in parallel, defaults to {{clientMaxMethodParallelRequests}} @@ -405,11 +449,14 @@ Expands the relationships in userset tree format. [API Documentation]({{apiDocsUrl}}#/Relationship%20Queries/Expand) +> Passing `ClientExpandOptions` is optional. All fields of `ClientExpandOptions` are optional. + ```java var request = new ClientExpandRequest() .relation("viewer") ._object("document:roadmap"); -var options = new ClientCheckOptions() +var options = new ClientExpandOptions() + .additionalHeaders(Map.of("Some-Http-Header", "Some value")) // You can rely on the model id set in the configuration or override it for this specific request .authorizationModelId("01GXSA8YR785C4FYS3C0RTG7B1"); @@ -424,6 +471,8 @@ List the objects of a particular type a user has access to. [API Documentation]({{apiDocsUrl}}#/Relationship%20Queries/ListObjects) +> Passing `ClientListObjectsOptions` is optional. All fields of `ClientListObjectsOptions` are optional. + ```java var request = new ClientListObjectsRequest() .user("user:81684243-9356-4421-8fbf-a4f8d36aa31b") @@ -436,6 +485,7 @@ var request = new ClientListObjectsRequest() ._object("document:budget") )); var options = new ClientListObjectsOptions() + .additionalHeaders(Map.of("Some-Http-Header", "Some value")) // You can rely on the model id set in the configuration or override it for this specific request .authorizationModelId("01GXSA8YR785C4FYS3C0RTG7B1"); @@ -448,6 +498,8 @@ var response = fgaClient.listObjects(request, options).get(); List the relations a user has on an object. +> Passing `ClientListRelationsOptions` is optional. All fields of `ClientListRelationsOptions` are optional. + ```java var request = new ClientListRelationsRequest() .user("user:81684243-9356-4421-8fbf-a4f8d36aa31b") @@ -461,6 +513,7 @@ var request = new ClientListRelationsRequest() ) ); var options = new ClientListRelationsOptions() + .additionalHeaders(Map.of("Some-Http-Header", "Some value")) // When unspecified, defaults to {{clientMaxMethodParallelRequests}} .maxParallelRequests() // You can rely on the model id set in the configuration or override it for this specific request @@ -479,8 +532,12 @@ Read assertions for a particular authorization model. [API Documentation]({{apiDocsUrl}}#/Assertions/Read%20Assertions) +> Passing `ClientReadAssertionsOptions` is optional. All fields of `ClientReadAssertionsOptions` are optional. + ```java var options = new ClientReadAssertionsOptions() + .additionalHeaders(Map.of("Some-Http-Header", "Some value")) + // You can rely on the model id set in the configuration or override it for this specific request .authorizationModelId("01GXSA8YR785C4FYS3C0RTG7B1"); var response = fgaClient.readAssertions(options).get(); ``` @@ -491,8 +548,11 @@ Update the assertions for a particular authorization model. [API Documentation]({{apiDocsUrl}}#/Assertions/Write%20Assertions) +> Passing `ClientWriteAssertionsOptions` is optional. All fields of `ClientWriteAssertionsOptions` are optional. + ```java var options = new ClientWriteAssertionsOptions() + .additionalHeaders(Map.of("Some-Http-Header", "Some value")) .authorizationModelId("01GXSA8YR785C4FYS3C0RTG7B1"); var assertions = List.of( new ClientAssertion() diff --git a/config/clients/java/template/client-ClientBatchCheckResponse.java.mustache b/config/clients/java/template/client-ClientBatchCheckResponse.java.mustache index d1b5ac20..412fe95c 100644 --- a/config/clients/java/template/client-ClientBatchCheckResponse.java.mustache +++ b/config/clients/java/template/client-ClientBatchCheckResponse.java.mustache @@ -1,5 +1,5 @@ {{>licenseInfo}} -package {{clientPackage}}; +package {{clientPackage}}.model; import {{modelPackage}}.CheckResponse; import {{errorsPackage}}.FgaError; diff --git a/config/clients/java/template/client-ClientCheckRequest.java.mustache b/config/clients/java/template/client-ClientCheckRequest.java.mustache index 1d477745..df00c168 100644 --- a/config/clients/java/template/client-ClientCheckRequest.java.mustache +++ b/config/clients/java/template/client-ClientCheckRequest.java.mustache @@ -1,5 +1,5 @@ {{>licenseInfo}} -package {{invokerPackage}}; +package {{invokerPackage}}.model; import dev.openfga.sdk.api.model.CheckRequestTupleKey; import java.util.List; diff --git a/config/clients/java/template/client-ClientCheckResponse.java.mustache b/config/clients/java/template/client-ClientCheckResponse.java.mustache index 1630fca0..65d5b6c7 100644 --- a/config/clients/java/template/client-ClientCheckResponse.java.mustache +++ b/config/clients/java/template/client-ClientCheckResponse.java.mustache @@ -1,6 +1,7 @@ {{>licenseInfo}} -package {{clientPackage}}; +package {{clientPackage}}.model; +import {{clientPackage}}.ApiResponse; import {{modelPackage}}.CheckResponse; import java.util.List; import java.util.Map; diff --git a/config/clients/java/template/client-ClientCreateStoreResponse.java.mustache b/config/clients/java/template/client-ClientCreateStoreResponse.java.mustache index 9d3bfc92..7bdbcf39 100644 --- a/config/clients/java/template/client-ClientCreateStoreResponse.java.mustache +++ b/config/clients/java/template/client-ClientCreateStoreResponse.java.mustache @@ -1,6 +1,7 @@ {{>licenseInfo}} -package {{clientPackage}}; +package {{clientPackage}}.model; +import {{clientPackage}}.ApiResponse; import {{modelPackage}}.CreateStoreResponse; import java.util.List; import java.util.Map; diff --git a/config/clients/java/template/client-ClientDeleteStoreResponse.java.mustache b/config/clients/java/template/client-ClientDeleteStoreResponse.java.mustache index c92998ed..3a4cc8a2 100644 --- a/config/clients/java/template/client-ClientDeleteStoreResponse.java.mustache +++ b/config/clients/java/template/client-ClientDeleteStoreResponse.java.mustache @@ -1,6 +1,7 @@ {{>licenseInfo}} -package {{clientPackage}}; +package {{clientPackage}}.model; +import {{clientPackage}}.ApiResponse; import java.util.List; import java.util.Map; diff --git a/config/clients/java/template/client-ClientExpandRequest.java.mustache b/config/clients/java/template/client-ClientExpandRequest.java.mustache index 16929d02..12da1b6b 100644 --- a/config/clients/java/template/client-ClientExpandRequest.java.mustache +++ b/config/clients/java/template/client-ClientExpandRequest.java.mustache @@ -1,5 +1,5 @@ {{>licenseInfo}} -package {{invokerPackage}}; +package {{clientPackage}}.model; public class ClientExpandRequest { private String relation; diff --git a/config/clients/java/template/client-ClientExpandResponse.java.mustache b/config/clients/java/template/client-ClientExpandResponse.java.mustache index 5e2c9577..92ea9687 100644 --- a/config/clients/java/template/client-ClientExpandResponse.java.mustache +++ b/config/clients/java/template/client-ClientExpandResponse.java.mustache @@ -1,6 +1,7 @@ {{>licenseInfo}} -package {{clientPackage}}; +package {{clientPackage}}.model; +import {{clientPackage}}.ApiResponse; import {{modelPackage}}.ExpandResponse; import java.util.List; import java.util.Map; diff --git a/config/clients/java/template/client-ClientGetStoreResponse.java.mustache b/config/clients/java/template/client-ClientGetStoreResponse.java.mustache index 8ad80cd4..7e7de6aa 100644 --- a/config/clients/java/template/client-ClientGetStoreResponse.java.mustache +++ b/config/clients/java/template/client-ClientGetStoreResponse.java.mustache @@ -1,6 +1,7 @@ {{>licenseInfo}} -package {{clientPackage}}; +package {{clientPackage}}.model; +import {{clientPackage}}.ApiResponse; import {{modelPackage}}.GetStoreResponse; import java.util.List; import java.util.Map; diff --git a/config/clients/java/template/client-ClientListObjectsRequest.java.mustache b/config/clients/java/template/client-ClientListObjectsRequest.java.mustache index 3f538643..6593cfb2 100644 --- a/config/clients/java/template/client-ClientListObjectsRequest.java.mustache +++ b/config/clients/java/template/client-ClientListObjectsRequest.java.mustache @@ -1,5 +1,5 @@ {{>licenseInfo}} -package {{invokerPackage}}; +package {{clientPackage}}.model; import java.util.List; diff --git a/config/clients/java/template/client-ClientListObjectsResponse.java.mustache b/config/clients/java/template/client-ClientListObjectsResponse.java.mustache index 3b0265a5..793a51f0 100644 --- a/config/clients/java/template/client-ClientListObjectsResponse.java.mustache +++ b/config/clients/java/template/client-ClientListObjectsResponse.java.mustache @@ -1,6 +1,7 @@ {{>licenseInfo}} -package {{clientPackage}}; +package {{clientPackage}}.model; +import {{clientPackage}}.ApiResponse; import {{modelPackage}}.ListObjectsResponse; import java.util.List; import java.util.Map; diff --git a/config/clients/java/template/client-ClientListRelationsRequest.java.mustache b/config/clients/java/template/client-ClientListRelationsRequest.java.mustache index e08e257d..0ce1146b 100644 --- a/config/clients/java/template/client-ClientListRelationsRequest.java.mustache +++ b/config/clients/java/template/client-ClientListRelationsRequest.java.mustache @@ -1,5 +1,5 @@ {{>licenseInfo}} -package {{invokerPackage}}; +package {{clientPackage}}.model; import java.util.List; diff --git a/config/clients/java/template/client-ClientListRelationsResponse.java.mustache b/config/clients/java/template/client-ClientListRelationsResponse.java.mustache index cc287e85..de81c308 100644 --- a/config/clients/java/template/client-ClientListRelationsResponse.java.mustache +++ b/config/clients/java/template/client-ClientListRelationsResponse.java.mustache @@ -1,5 +1,5 @@ {{>licenseInfo}} -package {{clientPackage}}; +package {{clientPackage}}.model; import java.util.List; import java.util.stream.Collectors; diff --git a/config/clients/java/template/client-ClientListStoresResponse.java.mustache b/config/clients/java/template/client-ClientListStoresResponse.java.mustache index ca37899b..e99c63fc 100644 --- a/config/clients/java/template/client-ClientListStoresResponse.java.mustache +++ b/config/clients/java/template/client-ClientListStoresResponse.java.mustache @@ -1,6 +1,7 @@ {{>licenseInfo}} -package {{clientPackage}}; +package {{clientPackage}}.model; +import {{clientPackage}}.ApiResponse; import {{modelPackage}}.ListStoresResponse; import java.util.List; import java.util.Map; diff --git a/config/clients/java/template/client-ClientReadAssertionsResponse.java.mustache b/config/clients/java/template/client-ClientReadAssertionsResponse.java.mustache index 3b8d5315..8a06dbdc 100644 --- a/config/clients/java/template/client-ClientReadAssertionsResponse.java.mustache +++ b/config/clients/java/template/client-ClientReadAssertionsResponse.java.mustache @@ -1,6 +1,7 @@ {{>licenseInfo}} -package {{clientPackage}}; +package {{clientPackage}}.model; +import {{clientPackage}}.ApiResponse; import {{modelPackage}}.ReadAssertionsResponse; import java.util.List; import java.util.Map; diff --git a/config/clients/java/template/client-ClientReadAuthorizationModelResponse.java.mustache b/config/clients/java/template/client-ClientReadAuthorizationModelResponse.java.mustache index 960b99e4..65ef5a22 100644 --- a/config/clients/java/template/client-ClientReadAuthorizationModelResponse.java.mustache +++ b/config/clients/java/template/client-ClientReadAuthorizationModelResponse.java.mustache @@ -1,6 +1,7 @@ {{>licenseInfo}} -package {{clientPackage}}; +package {{clientPackage}}.model; +import {{clientPackage}}.ApiResponse; import {{modelPackage}}.ReadAuthorizationModelResponse; import {{modelPackage}}.ReadAuthorizationModelsResponse; import java.util.List; diff --git a/config/clients/java/template/client-ClientReadAuthorizationModelsResponse.java.mustache b/config/clients/java/template/client-ClientReadAuthorizationModelsResponse.java.mustache index 59ff1675..679452a8 100644 --- a/config/clients/java/template/client-ClientReadAuthorizationModelsResponse.java.mustache +++ b/config/clients/java/template/client-ClientReadAuthorizationModelsResponse.java.mustache @@ -1,6 +1,7 @@ {{>licenseInfo}} -package {{clientPackage}}; +package {{clientPackage}}.model; +import {{clientPackage}}.ApiResponse; import {{modelPackage}}.ReadAuthorizationModelsResponse; import java.util.List; import java.util.Map; diff --git a/config/clients/java/template/client-ClientReadChangesRequest.java.mustache b/config/clients/java/template/client-ClientReadChangesRequest.java.mustache new file mode 100644 index 00000000..d0c7bb9a --- /dev/null +++ b/config/clients/java/template/client-ClientReadChangesRequest.java.mustache @@ -0,0 +1,15 @@ +{{>licenseInfo}} +package {{clientPackage}}.model; + +public class ClientReadChangesRequest { + private String type; + + public ClientReadChangesRequest type(String type) { + this.type = type; + return this; + } + + public String getType() { + return type; + } +} diff --git a/config/clients/java/template/client-ClientReadChangesResponse.java.mustache b/config/clients/java/template/client-ClientReadChangesResponse.java.mustache index ecc63c57..0cce817e 100644 --- a/config/clients/java/template/client-ClientReadChangesResponse.java.mustache +++ b/config/clients/java/template/client-ClientReadChangesResponse.java.mustache @@ -1,6 +1,7 @@ {{>licenseInfo}} -package {{clientPackage}}; +package {{clientPackage}}.model; +import {{clientPackage}}.ApiResponse; import {{modelPackage}}.ReadChangesResponse; import java.util.List; import java.util.Map; diff --git a/config/clients/java/template/client-ClientReadRequest.java.mustache b/config/clients/java/template/client-ClientReadRequest.java.mustache index a084dcfe..8d558396 100644 --- a/config/clients/java/template/client-ClientReadRequest.java.mustache +++ b/config/clients/java/template/client-ClientReadRequest.java.mustache @@ -1,5 +1,5 @@ {{>licenseInfo}} -package {{invokerPackage}}; +package {{clientPackage}}.model; public class ClientReadRequest { private String user; diff --git a/config/clients/java/template/client-ClientReadResponse.java.mustache b/config/clients/java/template/client-ClientReadResponse.java.mustache index 8a6d9c5f..5e65e25c 100644 --- a/config/clients/java/template/client-ClientReadResponse.java.mustache +++ b/config/clients/java/template/client-ClientReadResponse.java.mustache @@ -1,6 +1,7 @@ {{>licenseInfo}} -package {{clientPackage}}; +package {{clientPackage}}.model; +import {{clientPackage}}.ApiResponse; import {{modelPackage}}.ReadResponse; import java.util.List; import java.util.Map; diff --git a/config/clients/java/template/client-ClientRelationshipCondition.java.mustache b/config/clients/java/template/client-ClientRelationshipCondition.java.mustache index bc6ed094..258604fa 100644 --- a/config/clients/java/template/client-ClientRelationshipCondition.java.mustache +++ b/config/clients/java/template/client-ClientRelationshipCondition.java.mustache @@ -1,5 +1,5 @@ {{>licenseInfo}} -package {{invokerPackage}}; +package {{clientPackage}}.model; import {{modelPackage}}.RelationshipCondition; diff --git a/config/clients/java/template/client-ClientTupleKey.java.mustache b/config/clients/java/template/client-ClientTupleKey.java.mustache index 3b1a826a..a62355d3 100644 --- a/config/clients/java/template/client-ClientTupleKey.java.mustache +++ b/config/clients/java/template/client-ClientTupleKey.java.mustache @@ -1,5 +1,5 @@ {{>licenseInfo}} -package {{invokerPackage}}; +package {{clientPackage}}.model; import {{modelPackage}}.ContextualTupleKeys; import {{modelPackage}}.TupleKey; diff --git a/config/clients/java/template/client-ClientTupleKeyWithoutCondition.java.mustache b/config/clients/java/template/client-ClientTupleKeyWithoutCondition.java.mustache index fc8c597a..3e382ea2 100644 --- a/config/clients/java/template/client-ClientTupleKeyWithoutCondition.java.mustache +++ b/config/clients/java/template/client-ClientTupleKeyWithoutCondition.java.mustache @@ -1,5 +1,5 @@ {{>licenseInfo}} -package {{invokerPackage}}; +package {{clientPackage}}.model; import {{modelPackage}}.TupleKeyWithoutCondition; import {{modelPackage}}.WriteRequestDeletes; diff --git a/config/clients/java/template/client-ClientWriteAssertionsResponse.java.mustache b/config/clients/java/template/client-ClientWriteAssertionsResponse.java.mustache index 9332547c..e784e7df 100644 --- a/config/clients/java/template/client-ClientWriteAssertionsResponse.java.mustache +++ b/config/clients/java/template/client-ClientWriteAssertionsResponse.java.mustache @@ -1,6 +1,7 @@ {{>licenseInfo}} -package {{clientPackage}}; +package {{clientPackage}}.model; +import {{clientPackage}}.ApiResponse; import java.util.List; import java.util.Map; diff --git a/config/clients/java/template/client-ClientWriteAuthorizationModelResponse.java.mustache b/config/clients/java/template/client-ClientWriteAuthorizationModelResponse.java.mustache index b42230fb..a8d378fb 100644 --- a/config/clients/java/template/client-ClientWriteAuthorizationModelResponse.java.mustache +++ b/config/clients/java/template/client-ClientWriteAuthorizationModelResponse.java.mustache @@ -1,6 +1,7 @@ {{>licenseInfo}} -package {{clientPackage}}; +package {{clientPackage}}.model; +import {{clientPackage}}.ApiResponse; import {{modelPackage}}.WriteAuthorizationModelResponse; import java.util.List; import java.util.Map; diff --git a/config/clients/java/template/client-ClientWriteRequest.java.mustache b/config/clients/java/template/client-ClientWriteRequest.java.mustache index a3e0f8b9..77f2645c 100644 --- a/config/clients/java/template/client-ClientWriteRequest.java.mustache +++ b/config/clients/java/template/client-ClientWriteRequest.java.mustache @@ -1,5 +1,5 @@ {{>licenseInfo}} -package {{invokerPackage}}; +package {{clientPackage}}.model; import java.util.List; diff --git a/config/clients/java/template/client-ClientWriteResponse.java.mustache b/config/clients/java/template/client-ClientWriteResponse.java.mustache index e74dd1c0..1883f647 100644 --- a/config/clients/java/template/client-ClientWriteResponse.java.mustache +++ b/config/clients/java/template/client-ClientWriteResponse.java.mustache @@ -1,6 +1,7 @@ {{>licenseInfo}} -package {{clientPackage}}; +package {{clientPackage}}.model; +import {{clientPackage}}.ApiResponse; import java.util.List; import java.util.Map; diff --git a/config/clients/java/template/client-HttpRequestAttempt.java.mustache b/config/clients/java/template/client-HttpRequestAttempt.java.mustache index f2852e24..a0433812 100644 --- a/config/clients/java/template/client-HttpRequestAttempt.java.mustache +++ b/config/clients/java/template/client-HttpRequestAttempt.java.mustache @@ -1,6 +1,8 @@ package {{invokerPackage}}; import static {{utilPackage}}.StringUtil.isNullOrWhitespace; + +import {{clientPackage}}.ApiResponse; import {{configPackage}}.Configuration; import {{errorsPackage}}.*; import java.io.IOException; diff --git a/config/clients/java/template/client-OpenFgaClient.java.mustache b/config/clients/java/template/client-OpenFgaClient.java.mustache index 067f22b3..067ca548 100644 --- a/config/clients/java/template/client-OpenFgaClient.java.mustache +++ b/config/clients/java/template/client-OpenFgaClient.java.mustache @@ -2,12 +2,15 @@ package {{clientPackage}}; import static {{utilPackage}}.StringUtil.isNullOrWhitespace; +import static java.util.UUID.randomUUID; import {{apiPackage}}.*; +import {{clientPackage}}.model.*; import {{configPackage}}.*; import {{modelPackage}}.*; import {{errorsPackage}}.*; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.concurrent.*; import java.util.function.Consumer; @@ -62,10 +65,14 @@ public class OpenFgaClient { 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(); - return call(() -> api.listStores(options.getPageSize(), options.getContinuationToken())) + var overrides = new ConfigurationOverride().addHeaders(options); + return call(() -> api.listStores(options.getPageSize(), options.getContinuationToken(), overrides)) .thenApply(ClientListStoresResponse::new); } @@ -74,8 +81,17 @@ public class OpenFgaClient { */ 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(); - return call(() -> api.createStore(request)).thenApply(ClientCreateStoreResponse::new); + var overrides = new ConfigurationOverride().addHeaders(options); + return call(() -> api.createStore(request, overrides)).thenApply(ClientCreateStoreResponse::new); } /** @@ -83,9 +99,19 @@ public class OpenFgaClient { * @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(); - return call(() -> api.getStore(storeId)).thenApply(ClientGetStoreResponse::new); + var overrides = new ConfigurationOverride().addHeaders(options); + return call(() -> api.getStore(storeId, overrides)).thenApply(ClientGetStoreResponse::new); } /** @@ -94,9 +120,20 @@ public class OpenFgaClient { * @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(); - return call(() -> api.deleteStore(storeId)).thenApply(ClientDeleteStoreResponse::new); + var overrides = new ConfigurationOverride().addHeaders(options); + return call(() -> api.deleteStore(storeId, overrides)).thenApply(ClientDeleteStoreResponse::new); } /* ********************** @@ -135,7 +172,9 @@ public class OpenFgaClient { pageSize = null; } - return call(() -> api.readAuthorizationModels(storeId, pageSize, continuationToken)) + var overrides = new ConfigurationOverride().addHeaders(options); + + return call(() -> api.readAuthorizationModels(storeId, pageSize, continuationToken, overrides)) .thenApply(ClientReadAuthorizationModelsResponse::new); } @@ -146,9 +185,21 @@ public class OpenFgaClient { */ 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(); - return call(() -> api.writeAuthorizationModel(storeId, request)) + var overrides = new ConfigurationOverride().addHeaders(options); + return call(() -> api.writeAuthorizationModel(storeId, request, overrides)) .thenApply(ClientWriteAuthorizationModelResponse::new); } @@ -176,7 +227,8 @@ public class OpenFgaClient { configuration.assertValid(); String storeId = configuration.getStoreIdChecked(); String authorizationModelId = options.getAuthorizationModelIdChecked(); - return call(() -> api.readAuthorizationModel(storeId, authorizationModelId)) + var overrides = new ConfigurationOverride().addHeaders(options); + return call(() -> api.readAuthorizationModel(storeId, authorizationModelId, overrides)) .thenApply(ClientReadAuthorizationModelResponse::new); } @@ -187,9 +239,20 @@ public class OpenFgaClient { */ 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(); - return call(() -> api.readAuthorizationModels(storeId, 1, null)) + var overrides = new ConfigurationOverride().addHeaders(options); + return call(() -> api.readAuthorizationModels(storeId, 1, null, overrides)) .thenApply(ClientReadAuthorizationModelResponse::latestOf); } @@ -202,12 +265,25 @@ public class OpenFgaClient { * * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace */ - public CompletableFuture readChanges(ClientReadChangesOptions options) + 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, options.getType(), options.getPageSize(), options.getContinuationToken())) + storeId, request.getType(), options.getPageSize(), options.getContinuationToken(), overrides)) .thenApply(ClientReadChangesResponse::new); } @@ -243,7 +319,9 @@ public class OpenFgaClient { body.pageSize(options.getPageSize()).continuationToken(options.getContinuationToken()); } - return call(() -> api.read(storeId, body)).thenApply(ClientReadResponse::new); + var overrides = new ConfigurationOverride().addHeaders(options); + + return call(() -> api.read(storeId, body, overrides)).thenApply(ClientReadResponse::new); } /** @@ -295,14 +373,26 @@ public class OpenFgaClient { body.authorizationModelId(authorizationModelId); } - return call(() -> api.write(storeId, body)).thenApply(ClientWriteResponse::new); + var overrides = new ConfigurationOverride().addHeaders(options); + + return call(() -> api.write(storeId, body, overrides)).thenApply(ClientWriteResponse::new); } private CompletableFuture writeTransactions( - String storeId, ClientWriteRequest request, ClientWriteOptions options) { + String storeId, ClientWriteRequest request, ClientWriteOptions writeOptions) { + + var options = writeOptions != null + ? writeOptions + : new ClientWriteOptions().transactionChunkSize(DEFAULT_MAX_METHOD_PARALLEL_REQS); - int chunkSize = options == null ? DEFAULT_MAX_METHOD_PARALLEL_REQS : options.getTransactionChunkSize(); + 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); @@ -347,6 +437,16 @@ public class OpenFgaClient { */ 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(); @@ -359,7 +459,9 @@ public class OpenFgaClient { body.authorizationModelId(authorizationModelId); } - return call(() -> api.write(storeId, body)).thenApply(ClientWriteResponse::new); + var overrides = new ConfigurationOverride().addHeaders(options); + + return call(() -> api.write(storeId, body, overrides)).thenApply(ClientWriteResponse::new); } /** @@ -369,6 +471,17 @@ public class OpenFgaClient { */ 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(); @@ -381,7 +494,9 @@ public class OpenFgaClient { body.authorizationModelId(authorizationModelId); } - return call(() -> api.write(storeId, body)).thenApply(ClientWriteResponse::new); + var overrides = new ConfigurationOverride().addHeaders(options); + + return call(() -> api.write(storeId, body, overrides)).thenApply(ClientWriteResponse::new); } /* ********************** @@ -426,7 +541,19 @@ public class OpenFgaClient { body.authorizationModelId(authorizationModelId); } - return call(() -> api.check(storeId, body)).thenApply(ClientCheckResponse::new); + 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); } /** @@ -435,10 +562,21 @@ public class OpenFgaClient { * @throws FgaInvalidParameterException When the Store ID is null, empty, or whitespace */ public CompletableFuture> batchCheck( - List requests, ClientBatchCheckOptions options) throws FgaInvalidParameterException { + 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; @@ -498,7 +636,9 @@ public class OpenFgaClient { body.authorizationModelId(authorizationModelId); } - return call(() -> api.expand(storeId, body)).thenApply(ClientExpandResponse::new); + var overrides = new ConfigurationOverride().addHeaders(options); + + return call(() -> api.expand(storeId, body, overrides)).thenApply(ClientExpandResponse::new); } /** @@ -539,20 +679,40 @@ public class OpenFgaClient { body.authorizationModelId(authorizationModelId); } - return call(() -> api.listObjects(storeId, body)).thenApply(ClientListObjectsResponse::new); + 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 options) + 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()) @@ -560,7 +720,7 @@ public class OpenFgaClient { ._object(request.getObject())) .collect(Collectors.toList()); - return batchCheck(batchCheckRequests, options.asClientBatchCheckOptions()) + return this.batchCheck(batchCheckRequests, options.asClientBatchCheckOptions()) .thenCompose(responses -> call(() -> ClientListRelationsResponse.fromBatchCheckResponses(responses))); } @@ -594,7 +754,9 @@ public class OpenFgaClient { authorizationModelId = configuration.getAuthorizationModelIdChecked(); } - return call(() -> api.readAssertions(storeId, authorizationModelId)) + var overrides = new ConfigurationOverride().addHeaders(options); + + return call(() -> api.readAssertions(storeId, authorizationModelId, overrides)) .thenApply(ClientReadAssertionsResponse::new); } @@ -628,7 +790,9 @@ public class OpenFgaClient { WriteAssertionsRequest body = new WriteAssertionsRequest().assertions(ClientAssertion.asAssertions(assertions)); - return call(() -> api.writeAssertions(storeId, authorizationModelId, body)) + var overrides = new ConfigurationOverride().addHeaders(options); + + return call(() -> api.writeAssertions(storeId, authorizationModelId, body, overrides)) .thenApply(ClientWriteAssertionsResponse::new); } diff --git a/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache b/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache index 2d07b1ac..f8d5e91d 100644 --- a/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache +++ b/config/clients/java/template/client-OpenFgaClientIntegrationTest.java.mustache @@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.*; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import {{clientPackage}}.model.*; import {{configPackage}}.*; import {{modelPackage}}.*; import java.io.IOException; diff --git a/config/clients/java/template/client-OpenFgaClientTest.java.mustache b/config/clients/java/template/client-OpenFgaClientTest.java.mustache index 4fb565e6..792b67f4 100644 --- a/config/clients/java/template/client-OpenFgaClientTest.java.mustache +++ b/config/clients/java/template/client-OpenFgaClientTest.java.mustache @@ -8,19 +8,24 @@ import static org.mockito.Mockito.*; import com.fasterxml.jackson.databind.ObjectMapper; import com.pgssoft.httpclient.HttpClientMock; -import {{invokerPackage}}.*; -import {{modelPackage}}.*; +import {{clientPackage}}.model.*; import {{configPackage}}.*; import {{errorsPackage}}.*; +import {{invokerPackage}}.*; +import {{modelPackage}}.*; import java.net.http.HttpClient; import java.time.Duration; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -41,11 +46,12 @@ public class OpenFgaClientTest { new ClientRelationshipCondition().name("condition").context(Map.of("some", "context")); private static final int DEFAULT_MAX_RETRIES = 3; private static final Duration DEFAULT_RETRY_DELAY = Duration.ofMillis(100); + private static final String CLIENT_METHOD_HEADER = "X-OpenFGA-Client-Method"; + private static final String CLIENT_BULK_REQUEST_ID_HEADER = "X-OpenFGA-Client-Bulk-Request-Id"; private OpenFgaClient fga; private ClientConfiguration clientConfiguration; private HttpClientMock mockHttpClient; - @BeforeEach public void beforeEachTest() throws Exception { @@ -631,7 +637,7 @@ public class OpenFgaClientTest { String continuationToken = "eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ"; - ClientReadChangesOptions options = new ClientReadChangesOptions().type(changeType); + ClientReadChangesRequest request = new ClientReadChangesRequest().type(changeType); String getUrl = String.format("https://localhost/stores/%s/changes?type=%s", DEFAULT_STORE_ID, changeType); String responseBody = String.format( "{\"changes\":[{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"operation\":\"TUPLE_OPERATION_WRITE\"}],\"continuation_token\":\"%s\"}", @@ -639,7 +645,7 @@ public class OpenFgaClientTest { mockHttpClient.onGet(getUrl).doReturn(200, responseBody); // When - ClientReadChangesResponse response = fga.readChanges(options).get(); + ClientReadChangesResponse response = fga.readChanges(request).get(); // Then mockHttpClient.verify().get(getUrl).called(1); @@ -1117,6 +1123,8 @@ public class OpenFgaClientTest { mockHttpClient .onPost(postPath) .withBody(isOneOf(write2Body, write1Body, delete2Body, delete1Body)) + .withHeader(CLIENT_METHOD_HEADER, "Write") + .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) .doReturn(200, EMPTY_RESPONSE_BODY); ClientWriteRequest request = new ClientWriteRequest() .writes(List.of(writeTuple, writeTuple, writeTuple, writeTuple, writeTuple)) @@ -1128,10 +1136,34 @@ public class OpenFgaClientTest { var response = fga.write(request, options).get(); // Then - mockHttpClient.verify().post(postPath).withBody(is(write2Body)).called(2); - mockHttpClient.verify().post(postPath).withBody(is(write1Body)).called(1); - mockHttpClient.verify().post(postPath).withBody(is(delete2Body)).called(2); - mockHttpClient.verify().post(postPath).withBody(is(delete1Body)).called(1); + mockHttpClient + .verify() + .post(postPath) + .withBody(is(write2Body)) + .withHeader(CLIENT_METHOD_HEADER, "Write") + .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) + .called(2); + mockHttpClient + .verify() + .post(postPath) + .withBody(is(write1Body)) + .withHeader(CLIENT_METHOD_HEADER, "Write") + .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) + .called(1); + mockHttpClient + .verify() + .post(postPath) + .withBody(is(delete2Body)) + .withHeader(CLIENT_METHOD_HEADER, "Write") + .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) + .called(2); + mockHttpClient + .verify() + .post(postPath) + .withBody(is(delete1Body)) + .withHeader(CLIENT_METHOD_HEADER, "Write") + .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) + .called(1); assertEquals(200, response.getStatusCode()); } @@ -1148,10 +1180,14 @@ public class OpenFgaClientTest { mockHttpClient .onPost(postPath) .withBody(isOneOf(writeBody.apply(firstUser), writeBody.apply(skippedUser))) + .withHeader(CLIENT_METHOD_HEADER, "Write") + .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) .doReturn(200, EMPTY_RESPONSE_BODY); mockHttpClient .onPost(postPath) .withBody(is(writeBody.apply(failedUser))) + .withHeader(CLIENT_METHOD_HEADER, "Write") + .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) .doReturn(400, "{\"code\":\"validation_error\",\"message\":\"Generic validation error\"}"); ClientWriteRequest request = new ClientWriteRequest() .writes(Stream.of(firstUser, failedUser, skippedUser) @@ -1173,16 +1209,22 @@ public class OpenFgaClientTest { .verify() .post(postPath) .withBody(is(writeBody.apply(firstUser))) + .withHeader(CLIENT_METHOD_HEADER, "Write") + .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) .called(1); mockHttpClient .verify() .post(postPath) .withBody(is(writeBody.apply(failedUser))) + .withHeader(CLIENT_METHOD_HEADER, "Write") + .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) .called(1); mockHttpClient .verify() .post(postPath) .withBody(is(writeBody.apply(skippedUser))) + .withHeader(CLIENT_METHOD_HEADER, "Write") + .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) .called(0); var exception = assertInstanceOf(FgaApiValidationError.class, execException.getCause()); assertEquals(400, exception.getStatusCode()); @@ -1213,9 +1255,8 @@ public class OpenFgaClientTest { .relation(DEFAULT_RELATION) .user(DEFAULT_USER) .condition(DEFAULT_CONDITION); - ClientWriteRequest request = new ClientWriteRequest() - .writes(List.of(tuple, tuple, tuple)) - .deletes(List.of(tuple, tuple, tuple)); + ClientWriteRequest request = + new ClientWriteRequest().writes(List.of(tuple, tuple, tuple)).deletes(List.of(tuple, tuple, tuple)); // We expect transactionChunkSize will be ignored, and exactly one request will be sent. ClientWriteOptions options = @@ -1257,16 +1298,16 @@ public class OpenFgaClientTest { .relation(DEFAULT_RELATION) .user(DEFAULT_USER) .condition(DEFAULT_CONDITION); - ClientWriteRequest request = new ClientWriteRequest() - .writes(List.of(tuple, tuple, tuple)) - .deletes(List.of(tuple, tuple, tuple)); + ClientWriteRequest request = + new ClientWriteRequest().writes(List.of(tuple, tuple, tuple)).deletes(List.of(tuple, tuple, tuple)); // We expect transactionChunkSize will be ignored, and exactly one request will be sent. ClientWriteOptions options = new ClientWriteOptions().disableTransactions(true).transactionChunkSize(1); // When - var execException = assertThrows(ExecutionException.class, () -> fga.write(request, options).get()); + var execException = assertThrows( + ExecutionException.class, () -> fga.write(request, options).get()); // Then mockHttpClient.verify().post(postPath).withBody(is(expectedBody)).called(1); @@ -1531,7 +1572,12 @@ public class OpenFgaClientTest { String expectedBody = String.format( "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}", DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); - mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, "{\"allowed\":true}"); + mockHttpClient + .onPost(postUrl) + .withBody(is(expectedBody)) + .withHeader(CLIENT_METHOD_HEADER, "BatchCheck") + .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) + .doReturn(200, "{\"allowed\":true}"); ClientCheckRequest request = new ClientCheckRequest() ._object(DEFAULT_OBJECT) .relation(DEFAULT_RELATION) @@ -1543,7 +1589,13 @@ public class OpenFgaClientTest { fga.batchCheck(List.of(request), options).get(); // Then - mockHttpClient.verify().post(postUrl).withBody(is(expectedBody)).called(1); + mockHttpClient + .verify() + .post(postUrl) + .withBody(is(expectedBody)) + .withHeader(CLIENT_METHOD_HEADER, "BatchCheck") + .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) + .called(1); assertEquals(Boolean.TRUE, response.get(0).getAllowed()); } @@ -1554,7 +1606,12 @@ public class OpenFgaClientTest { String expectedBody = String.format( "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}", DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); - mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, "{\"allowed\":true}"); + mockHttpClient + .onPost(postUrl) + .withBody(is(expectedBody)) + .withHeader(CLIENT_METHOD_HEADER, "BatchCheck") + .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) + .doReturn(200, "{\"allowed\":true}"); List requests = IntStream.range(0, 20) .mapToObj(ignored -> new ClientCheckRequest() ._object(DEFAULT_OBJECT) @@ -1567,7 +1624,13 @@ public class OpenFgaClientTest { fga.batchCheck(requests, options).get(); // Then - mockHttpClient.verify().post(postUrl).withBody(is(expectedBody)).called(20); + mockHttpClient + .verify() + .post(postUrl) + .withBody(is(expectedBody)) + .withHeader(CLIENT_METHOD_HEADER, "BatchCheck") + .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) + .called(20); } @Test @@ -1676,9 +1739,8 @@ public class OpenFgaClientTest { "{\"tree\":{\"root\":{\"union\":{\"nodes\":[{\"leaf\":{\"users\":{\"users\":[\"%s\"]}}}]}}}}", DEFAULT_USER); mockHttpClient.onPost(postPath).withBody(is(expectedBody)).doReturn(200, responseBody); - ClientExpandRequest request = new ClientExpandRequest() - .relation(DEFAULT_RELATION) - ._object(DEFAULT_OBJECT); + ClientExpandRequest request = + new ClientExpandRequest().relation(DEFAULT_RELATION)._object(DEFAULT_OBJECT); ClientExpandOptions options = new ClientExpandOptions().authorizationModelId(DEFAULT_AUTH_MODEL_ID); // When @@ -1892,7 +1954,12 @@ public class OpenFgaClientTest { String expectedBody = String.format( "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"01G5JAVJ41T49E9TT3SKVS7X1J\",\"trace\":null,\"context\":null}", DEFAULT_USER, DEFAULT_RELATION, DEFAULT_OBJECT); - mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, "{\"allowed\":true}"); + mockHttpClient + .onPost(postUrl) + .withBody(is(expectedBody)) + .withHeader(CLIENT_METHOD_HEADER, "BatchCheck") + .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) + .doReturn(200, "{\"allowed\":true}"); ClientListRelationsRequest request = new ClientListRelationsRequest() .relations(List.of(DEFAULT_RELATION)) .user(DEFAULT_USER) @@ -1905,7 +1972,13 @@ public class OpenFgaClientTest { fga.listRelations(request, options).get(); // Then - mockHttpClient.verify().post(postUrl).withBody(is(expectedBody)).called(1); + mockHttpClient + .verify() + .post(postUrl) + .withBody(is(expectedBody)) + .withHeader(CLIENT_METHOD_HEADER, "BatchCheck") + .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) + .called(1); assertNotNull(response); assertNotNull(response.getRelations()); assertEquals(1, response.getRelations().size()); @@ -1919,7 +1992,12 @@ public class OpenFgaClientTest { String expectedBody = String.format( "{\"tuple_key\":{\"user\":\"%s\",\"relation\":\"%s\",\"object\":\"%s\"},\"contextual_tuples\":null,\"authorization_model_id\":\"%s\",\"trace\":null,\"context\":null}", DEFAULT_USER, "owner", DEFAULT_OBJECT, DEFAULT_AUTH_MODEL_ID); - mockHttpClient.onPost(postUrl).withBody(is(expectedBody)).doReturn(200, "{\"allowed\":false}"); + mockHttpClient + .onPost(postUrl) + .withBody(is(expectedBody)) + .withHeader(CLIENT_METHOD_HEADER, "BatchCheck") + .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) + .doReturn(200, "{\"allowed\":false}"); ClientListRelationsRequest request = new ClientListRelationsRequest() .relations(List.of("owner")) ._object(DEFAULT_OBJECT) @@ -1932,7 +2010,13 @@ public class OpenFgaClientTest { fga.listRelations(request, options).get(); // Then - mockHttpClient.verify().post(postUrl).withBody(is(expectedBody)).called(1); + mockHttpClient + .verify() + .post(postUrl) + .withBody(is(expectedBody)) + .withHeader(CLIENT_METHOD_HEADER, "BatchCheck") + .withHeader(CLIENT_BULK_REQUEST_ID_HEADER, anyValidUUID()) + .called(1); assertNotNull(response); assertNotNull(response.getRelations()); assertTrue(response.getRelations().isEmpty()); @@ -2374,4 +2458,44 @@ public class OpenFgaClientTest { "OpenFgaClient.setAuthorizationModelId(String) is expected to persist its Authorization Model ID in its ClientConfiguration." + "If this behavior ever changes, it could be a subtle breaking change."); } + + private Matcher anyValidUUID() { + return new UUIDMatcher(); + } + + private static class UUIDMatcher extends BaseMatcher { + private boolean wasNotString = false; + private boolean wasInvalidUUID = false; + + @Override + public void describeTo(Description description) { + description.appendText("any valid UUID"); + } + + @Override + public boolean matches(Object item) { + if (!(item instanceof String)) { + return wasNotString = false; + } + + var string = (String) item; + + try { + UUID.fromString(string); + return true; + } catch (IllegalArgumentException ex) { + wasInvalidUUID = true; + return false; + } + } + + @Override + public void describeMismatch(Object item, Description mismatchDescription) { + if (wasNotString) { + System.err.printf("Expected an instance of String but found class: %s\n", item.getClass()); + } else if (wasInvalidUUID) { + System.err.printf("Expected a valid UUID but found \"%s\"\n", item); + } + } + } } diff --git a/config/clients/java/template/config-AdditionalHeadersSupplier.java.mustache b/config/clients/java/template/config-AdditionalHeadersSupplier.java.mustache new file mode 100644 index 00000000..832b2862 --- /dev/null +++ b/config/clients/java/template/config-AdditionalHeadersSupplier.java.mustache @@ -0,0 +1,8 @@ +{{>licenseInfo}} +package {{configPackage}}; + +import java.util.Map; + +public interface AdditionalHeadersSupplier { + Map getAdditionalHeaders(); +} diff --git a/config/clients/java/template/config-ClientBatchCheckOptions.java.mustache b/config/clients/java/template/config-ClientBatchCheckOptions.java.mustache index 1a376c1b..bc245727 100644 --- a/config/clients/java/template/config-ClientBatchCheckOptions.java.mustache +++ b/config/clients/java/template/config-ClientBatchCheckOptions.java.mustache @@ -1,10 +1,23 @@ {{>licenseInfo}} package {{configPackage}}; -public class ClientBatchCheckOptions { +import java.util.Map; + +public class ClientBatchCheckOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; private Integer maxParallelRequests; private String authorizationModelId; + public ClientBatchCheckOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } + public ClientBatchCheckOptions maxParallelRequests(Integer maxParallelRequests) { this.maxParallelRequests = maxParallelRequests; return this; @@ -24,6 +37,6 @@ public class ClientBatchCheckOptions { } public ClientCheckOptions asClientCheckOptions() { - return new ClientCheckOptions().authorizationModelId(authorizationModelId); + return new ClientCheckOptions().additionalHeaders(additionalHeaders).authorizationModelId(authorizationModelId); } } diff --git a/config/clients/java/template/config-ClientCheckOptions.java.mustache b/config/clients/java/template/config-ClientCheckOptions.java.mustache index 7d7a9454..22036793 100644 --- a/config/clients/java/template/config-ClientCheckOptions.java.mustache +++ b/config/clients/java/template/config-ClientCheckOptions.java.mustache @@ -1,9 +1,22 @@ {{>licenseInfo}} package {{configPackage}}; -public class ClientCheckOptions { +import java.util.Map; + +public class ClientCheckOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; private String authorizationModelId; + public ClientCheckOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } + public ClientCheckOptions authorizationModelId(String authorizationModelId) { this.authorizationModelId = authorizationModelId; return this; diff --git a/config/clients/java/template/config-ClientConfiguration.java.mustache b/config/clients/java/template/config-ClientConfiguration.java.mustache index 2d2ac173..173be253 100644 --- a/config/clients/java/template/config-ClientConfiguration.java.mustache +++ b/config/clients/java/template/config-ClientConfiguration.java.mustache @@ -86,12 +86,6 @@ public class ClientConfiguration extends Configuration { /* Overrides beyond this point required for typing. */ - @Override - public ClientConfiguration override(ConfigurationOverride configurationOverride) { - super.override(configurationOverride); - return this; - } - @Override public ClientConfiguration apiUrl(String apiUrl) { super.apiUrl(apiUrl); diff --git a/config/clients/java/template/config-ClientCreateStoreOptions.java.mustache b/config/clients/java/template/config-ClientCreateStoreOptions.java.mustache new file mode 100644 index 00000000..9bac821e --- /dev/null +++ b/config/clients/java/template/config-ClientCreateStoreOptions.java.mustache @@ -0,0 +1,18 @@ +{{>licenseInfo}} +package {{configPackage}}; + +import java.util.Map; + +public class ClientCreateStoreOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; + + public ClientCreateStoreOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } +} diff --git a/config/clients/java/template/config-ClientDeleteStoreOptions.java.mustache b/config/clients/java/template/config-ClientDeleteStoreOptions.java.mustache new file mode 100644 index 00000000..3ac65ec1 --- /dev/null +++ b/config/clients/java/template/config-ClientDeleteStoreOptions.java.mustache @@ -0,0 +1,18 @@ +{{>licenseInfo}} +package {{configPackage}}; + +import java.util.Map; + +public class ClientDeleteStoreOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; + + public ClientDeleteStoreOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } +} diff --git a/config/clients/java/template/config-ClientDeleteTuplesOptions.java.mustache b/config/clients/java/template/config-ClientDeleteTuplesOptions.java.mustache new file mode 100644 index 00000000..55bc1708 --- /dev/null +++ b/config/clients/java/template/config-ClientDeleteTuplesOptions.java.mustache @@ -0,0 +1,18 @@ +{{>licenseInfo}} +package {{configPackage}}; + +import java.util.Map; + +public class ClientDeleteTuplesOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; + + public ClientDeleteTuplesOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } +} diff --git a/config/clients/java/template/config-ClientExpandOptions.java.mustache b/config/clients/java/template/config-ClientExpandOptions.java.mustache index 62201dec..c2204696 100644 --- a/config/clients/java/template/config-ClientExpandOptions.java.mustache +++ b/config/clients/java/template/config-ClientExpandOptions.java.mustache @@ -1,9 +1,22 @@ {{>licenseInfo}} package {{configPackage}}; -public class ClientExpandOptions { +import java.util.Map; + +public class ClientExpandOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; private String authorizationModelId; + public ClientExpandOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } + public ClientExpandOptions authorizationModelId(String authorizationModelId) { this.authorizationModelId = authorizationModelId; return this; diff --git a/config/clients/java/template/config-ClientGetStoreOptions.java.mustache b/config/clients/java/template/config-ClientGetStoreOptions.java.mustache new file mode 100644 index 00000000..4578e027 --- /dev/null +++ b/config/clients/java/template/config-ClientGetStoreOptions.java.mustache @@ -0,0 +1,18 @@ +{{>licenseInfo}} +package {{configPackage}}; + +import java.util.Map; + +public class ClientGetStoreOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; + + public ClientGetStoreOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } +} diff --git a/config/clients/java/template/config-ClientListObjectsOptions.java.mustache b/config/clients/java/template/config-ClientListObjectsOptions.java.mustache index 142a2bac..8c3bcfd2 100644 --- a/config/clients/java/template/config-ClientListObjectsOptions.java.mustache +++ b/config/clients/java/template/config-ClientListObjectsOptions.java.mustache @@ -1,9 +1,22 @@ {{>licenseInfo}} package {{configPackage}}; -public class ClientListObjectsOptions { +import java.util.Map; + +public class ClientListObjectsOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; private String authorizationModelId; + public ClientListObjectsOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } + public ClientListObjectsOptions authorizationModelId(String authorizationModelId) { this.authorizationModelId = authorizationModelId; return this; diff --git a/config/clients/java/template/config-ClientListRelationsOptions.java.mustache b/config/clients/java/template/config-ClientListRelationsOptions.java.mustache index 60b6d90b..6469ffc2 100644 --- a/config/clients/java/template/config-ClientListRelationsOptions.java.mustache +++ b/config/clients/java/template/config-ClientListRelationsOptions.java.mustache @@ -1,10 +1,23 @@ {{>licenseInfo}} package {{configPackage}}; -public class ClientListRelationsOptions { +import java.util.Map; + +public class ClientListRelationsOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; private Integer maxParallelRequests; private String authorizationModelId; + public ClientListRelationsOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } + public ClientListRelationsOptions maxParallelRequests(Integer maxParallelRequests) { this.maxParallelRequests = maxParallelRequests; return this; diff --git a/config/clients/java/template/config-ClientListStoresOptions.java.mustache b/config/clients/java/template/config-ClientListStoresOptions.java.mustache index 432b89ca..5ea5c0bb 100644 --- a/config/clients/java/template/config-ClientListStoresOptions.java.mustache +++ b/config/clients/java/template/config-ClientListStoresOptions.java.mustache @@ -1,10 +1,23 @@ {{>licenseInfo}} package {{configPackage}}; -public class ClientListStoresOptions { +import java.util.Map; + +public class ClientListStoresOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; private Integer pageSize; private String continuationToken; + public ClientListStoresOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } + public ClientListStoresOptions pageSize(Integer pageSize) { this.pageSize = pageSize; return this; diff --git a/config/clients/java/template/config-ClientReadAssertionsOptions.java.mustache b/config/clients/java/template/config-ClientReadAssertionsOptions.java.mustache index 2a0c6c0b..cde900ac 100644 --- a/config/clients/java/template/config-ClientReadAssertionsOptions.java.mustache +++ b/config/clients/java/template/config-ClientReadAssertionsOptions.java.mustache @@ -4,10 +4,22 @@ package {{configPackage}}; import static {{utilPackage}}.StringUtil.isNullOrWhitespace; import {{errorsPackage}}.FgaInvalidParameterException; +import java.util.Map; -public class ClientReadAssertionsOptions { +public class ClientReadAssertionsOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; private String authorizationModelId; + public ClientReadAssertionsOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } + public boolean hasValidAuthorizationModelId() throws FgaInvalidParameterException { return !isNullOrWhitespace(authorizationModelId); } diff --git a/config/clients/java/template/config-ClientReadAuthorizationModelOptions.java.mustache b/config/clients/java/template/config-ClientReadAuthorizationModelOptions.java.mustache index 8f238602..322d6f6b 100644 --- a/config/clients/java/template/config-ClientReadAuthorizationModelOptions.java.mustache +++ b/config/clients/java/template/config-ClientReadAuthorizationModelOptions.java.mustache @@ -4,10 +4,22 @@ package {{configPackage}}; import static {{utilPackage}}.StringUtil.isNullOrWhitespace; import {{errorsPackage}}.FgaInvalidParameterException; +import java.util.Map; -public class ClientReadAuthorizationModelOptions { +public class ClientReadAuthorizationModelOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; private String authorizationModelId; + public ClientReadAuthorizationModelOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } + public void assertValidAuthorizationModelId() throws FgaInvalidParameterException { if (isNullOrWhitespace(authorizationModelId)) { throw new FgaInvalidParameterException("authorizationModelId", "ClientConfiguration"); diff --git a/config/clients/java/template/config-ClientReadAuthorizationModelsOptions.java.mustache b/config/clients/java/template/config-ClientReadAuthorizationModelsOptions.java.mustache index 4113d9c7..082a5403 100644 --- a/config/clients/java/template/config-ClientReadAuthorizationModelsOptions.java.mustache +++ b/config/clients/java/template/config-ClientReadAuthorizationModelsOptions.java.mustache @@ -1,10 +1,23 @@ {{>licenseInfo}} package {{configPackage}}; -public class ClientReadAuthorizationModelsOptions { +import java.util.Map; + +public class ClientReadAuthorizationModelsOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; private Integer pageSize; private String continuationToken; + public ClientReadAuthorizationModelsOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } + public ClientReadAuthorizationModelsOptions pageSize(Integer pageSize) { this.pageSize = pageSize; return this; diff --git a/config/clients/java/template/config-ClientReadChangesOptions.java.mustache b/config/clients/java/template/config-ClientReadChangesOptions.java.mustache index 8afd0b13..71468f5e 100644 --- a/config/clients/java/template/config-ClientReadChangesOptions.java.mustache +++ b/config/clients/java/template/config-ClientReadChangesOptions.java.mustache @@ -1,18 +1,21 @@ {{>licenseInfo}} package {{configPackage}}; -public class ClientReadChangesOptions { - private String type; +import java.util.Map; + +public class ClientReadChangesOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; private Integer pageSize; private String continuationToken; - public ClientReadChangesOptions type(String type) { - this.type = type; + public ClientReadChangesOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; return this; } - public String getType() { - return type; + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; } public ClientReadChangesOptions pageSize(Integer pageSize) { diff --git a/config/clients/java/template/config-ClientReadLatestAuthorizationModelOptions.java.mustache b/config/clients/java/template/config-ClientReadLatestAuthorizationModelOptions.java.mustache new file mode 100644 index 00000000..f6850e07 --- /dev/null +++ b/config/clients/java/template/config-ClientReadLatestAuthorizationModelOptions.java.mustache @@ -0,0 +1,18 @@ +{{>licenseInfo}} +package {{configPackage}}; + +import java.util.Map; + +public class ClientReadLatestAuthorizationModelOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; + + public ClientReadLatestAuthorizationModelOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } +} diff --git a/config/clients/java/template/config-ClientReadOptions.java.mustache b/config/clients/java/template/config-ClientReadOptions.java.mustache index 7b4aebca..efaff1ec 100644 --- a/config/clients/java/template/config-ClientReadOptions.java.mustache +++ b/config/clients/java/template/config-ClientReadOptions.java.mustache @@ -1,10 +1,23 @@ {{>licenseInfo}} package {{configPackage}}; -public class ClientReadOptions { +import java.util.Map; + +public class ClientReadOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; private Integer pageSize; private String continuationToken; + public ClientReadOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } + public ClientReadOptions pageSize(Integer pageSize) { this.pageSize = pageSize; return this; diff --git a/config/clients/java/template/config-ClientWriteAssertionsOptions.java.mustache b/config/clients/java/template/config-ClientWriteAssertionsOptions.java.mustache index 420ede3a..6e0f8920 100644 --- a/config/clients/java/template/config-ClientWriteAssertionsOptions.java.mustache +++ b/config/clients/java/template/config-ClientWriteAssertionsOptions.java.mustache @@ -4,10 +4,22 @@ package {{configPackage}}; import static {{utilPackage}}.StringUtil.isNullOrWhitespace; import {{errorsPackage}}.FgaInvalidParameterException; +import java.util.Map; -public class ClientWriteAssertionsOptions { +public class ClientWriteAssertionsOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; private String authorizationModelId; + public ClientWriteAssertionsOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } + public boolean hasValidAuthorizationModelId() throws FgaInvalidParameterException { return !isNullOrWhitespace(authorizationModelId); } diff --git a/config/clients/java/template/config-ClientWriteAuthorizationModelOptions.java.mustache b/config/clients/java/template/config-ClientWriteAuthorizationModelOptions.java.mustache new file mode 100644 index 00000000..30e0eec0 --- /dev/null +++ b/config/clients/java/template/config-ClientWriteAuthorizationModelOptions.java.mustache @@ -0,0 +1,18 @@ +{{>licenseInfo}} +package {{configPackage}}; + +import java.util.Map; + +public class ClientWriteAuthorizationModelOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; + + public ClientWriteAuthorizationModelOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } +} diff --git a/config/clients/java/template/config-ClientWriteOptions.java.mustache b/config/clients/java/template/config-ClientWriteOptions.java.mustache index 612d2208..22ef7643 100644 --- a/config/clients/java/template/config-ClientWriteOptions.java.mustache +++ b/config/clients/java/template/config-ClientWriteOptions.java.mustache @@ -1,11 +1,24 @@ {{>licenseInfo}} package {{configPackage}}; -public class ClientWriteOptions { +import java.util.Map; + +public class ClientWriteOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; private String authorizationModelId; private Boolean disableTransactions = false; private int transactionChunkSize; + public ClientWriteOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } + public ClientWriteOptions authorizationModelId(String authorizationModelId) { this.authorizationModelId = authorizationModelId; return this; diff --git a/config/clients/java/template/config-ClientWriteTuplesOptions.java.mustache b/config/clients/java/template/config-ClientWriteTuplesOptions.java.mustache new file mode 100644 index 00000000..930a9289 --- /dev/null +++ b/config/clients/java/template/config-ClientWriteTuplesOptions.java.mustache @@ -0,0 +1,18 @@ +{{>licenseInfo}} +package {{configPackage}}; + +import java.util.Map; + +public class ClientWriteTuplesOptions implements AdditionalHeadersSupplier { + private Map additionalHeaders; + + public ClientWriteTuplesOptions additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + @Override + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } +} diff --git a/config/clients/java/template/config-Configuration.java.mustache b/config/clients/java/template/config-Configuration.java.mustache index 0a827a97..91590739 100644 --- a/config/clients/java/template/config-Configuration.java.mustache +++ b/config/clients/java/template/config-Configuration.java.mustache @@ -11,6 +11,8 @@ import java.net.http.HttpClient; import java.net.http.HttpConnectTimeoutException; import java.net.http.HttpRequest; import java.time.Duration; +import java.util.HashMap; +import java.util.Map; /** * Configurations for an api client. @@ -30,6 +32,7 @@ public class Configuration implements BaseConfiguration { private Duration connectTimeout; private int maxRetries; private Duration minimumRetryDelay; + private Map defaultHeaders; public Configuration() { this.apiUrl = DEFAULT_API_URL; @@ -98,6 +101,22 @@ public class Configuration implements BaseConfiguration { Duration overrideMinimumRetryDelay = configurationOverride.getMinimumRetryDelay(); result.minimumRetryDelay(overrideMinimumRetryDelay != null ? overrideMinimumRetryDelay : minimumRetryDelay); + Map headers = new HashMap<>(); + if (defaultHeaders != null) { + headers.putAll(defaultHeaders); + } + Map additionalHeaders = configurationOverride.getAdditionalHeaders(); + if (additionalHeaders != null) { + additionalHeaders.forEach((header, value) -> { + if (value == null) { + headers.remove(header); + } else { + headers.put(header, value); + } + }); + } + result.defaultHeaders(headers); + return result; } @@ -129,6 +148,10 @@ public class Configuration implements BaseConfiguration { /** * Set the user agent. * + *

Within the context of a single request, a "User-Agent" header from either + * {@link Configuration#defaultHeaders(Map)} or {@link ConfigurationOverride#additionalHeaders(Map)} + * will take precedence over this value.

+ * * @param userAgent The user agent. * @return This object. */ @@ -248,4 +271,16 @@ public class Configuration implements BaseConfiguration { public Duration getMinimumRetryDelay() { return minimumRetryDelay; } + + public Configuration defaultHeaders(Map defaultHeaders) { + this.defaultHeaders = defaultHeaders; + return this; + } + + public Map getDefaultHeaders() { + if (this.defaultHeaders == null) { + this.defaultHeaders = Map.of(); + } + return this.defaultHeaders; + } } diff --git a/config/clients/java/template/config-ConfigurationOverride.java.mustache b/config/clients/java/template/config-ConfigurationOverride.java.mustache index 8bd53143..9e38c801 100644 --- a/config/clients/java/template/config-ConfigurationOverride.java.mustache +++ b/config/clients/java/template/config-ConfigurationOverride.java.mustache @@ -5,6 +5,7 @@ import java.net.http.HttpClient; import java.net.http.HttpConnectTimeoutException; import java.net.http.HttpRequest; import java.time.Duration; +import java.util.Map; /** * Configuration overrides for an api client. Values are initialized to null, and any values unset are intended to fall @@ -20,6 +21,7 @@ public class ConfigurationOverride implements BaseConfiguration { private Duration connectTimeout; private Integer maxRetries; private Duration minimumRetryDelay; + private Map additionalHeaders; public ConfigurationOverride() { this.apiUrl = null; @@ -27,6 +29,7 @@ public class ConfigurationOverride implements BaseConfiguration { this.userAgent = null; this.readTimeout = null; this.connectTimeout = null; + this.additionalHeaders = null; } /** @@ -168,4 +171,28 @@ public class ConfigurationOverride implements BaseConfiguration { public Duration getMinimumRetryDelay() { return minimumRetryDelay; } + + public ConfigurationOverride additionalHeaders(Map additionalHeaders) { + this.additionalHeaders = additionalHeaders; + return this; + } + + public ConfigurationOverride addHeaders(AdditionalHeadersSupplier supplier) { + if (supplier == null || supplier.getAdditionalHeaders() == null) { + // No headers to add. + return this; + } + + var additionalHeaders = supplier.getAdditionalHeaders(); + if (this.additionalHeaders != null) { + this.additionalHeaders.putAll(additionalHeaders); + } else { + this.additionalHeaders = additionalHeaders; + } + return this; + } + + public Map getAdditionalHeaders() { + return this.additionalHeaders; + } } diff --git a/config/clients/java/template/config-ConfigurationTest.java.mustache b/config/clients/java/template/config-ConfigurationTest.java.mustache index 7455e42a..c1508a7e 100644 --- a/config/clients/java/template/config-ConfigurationTest.java.mustache +++ b/config/clients/java/template/config-ConfigurationTest.java.mustache @@ -6,12 +6,15 @@ import static org.junit.jupiter.api.Assertions.*; import {{errorsPackage}}.*; import org.junit.jupiter.api.Test; import java.time.Duration; +import java.util.HashMap; +import java.util.Map; class ConfigurationTest { private static final String DEFAULT_API_URL = "http://localhost:8080"; private static final String DEFAULT_USER_AGENT = "{{{userAgent}}}"; private static final Duration DEFAULT_READ_TIMEOUT = Duration.ofSeconds(10); private static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofSeconds(10); + private static final Map DEFAULT_HEADERS = Map.of(); @Test void apiUrl_nullDefaults() throws FgaInvalidParameterException { @@ -118,13 +121,83 @@ class ConfigurationTest { Configuration config = new Configuration(); // NOTE: Failures in this test indicate that default values in Configuration have changed. Changing - // the defaults of Configuration can be a suprising and breaking change for consumers. + // the defaults of Configuration can be a surprising and breaking change for consumers. // Then assertEquals(DEFAULT_API_URL, config.getApiUrl()); assertEquals(DEFAULT_USER_AGENT, config.getUserAgent()); assertEquals(DEFAULT_READ_TIMEOUT, config.getReadTimeout()); assertEquals(DEFAULT_CONNECT_TIMEOUT, config.getConnectTimeout()); + assertEquals(DEFAULT_HEADERS, config.getDefaultHeaders()); + } + + @Test + void override_addHeader() { + // Given + var originalHeaders = Map.of("Original-Header", "from original"); + var original = new Configuration().defaultHeaders(originalHeaders); + + var overrideHeaders = Map.of("Override-Header", "from override"); + var override = new ConfigurationOverride().additionalHeaders(overrideHeaders); + + // When + Configuration result = original.override(override); + + // Then + assertEquals("from original", result.getDefaultHeaders().get("Original-Header")); + assertEquals("from override", result.getDefaultHeaders().get("Override-Header")); + assertEquals( + 2, + result.getDefaultHeaders().size(), + "Resulting configuration should have one header from the original configuration and one header from the override."); + assertEquals(1, originalHeaders.size(), "Original headers should not be modified."); + assertEquals(1, overrideHeaders.size(), "Override headers should not be modified."); + } + + @Test + void override_overwriteHeader() { + // Given + var originalHeaders = Map.of("Header-To-Overwrite", "from original"); + var original = new Configuration().defaultHeaders(originalHeaders); + + var overrideHeaders = Map.of("Header-To-Overwrite", "from override"); + var override = new ConfigurationOverride().additionalHeaders(overrideHeaders); + + // When + Configuration result = original.override(override); + + // Then + assertEquals("from override", result.getDefaultHeaders().get("Header-To-Overwrite")); + + assertEquals( + "from original", + originalHeaders.get("Header-To-Overwrite"), + "Original headers should not be modified."); + assertEquals(1, result.getDefaultHeaders().size(), "Original headers should not be modified."); + assertEquals(1, originalHeaders.size(), "Original headers should not be modified."); + assertEquals(1, overrideHeaders.size(), "Override headers should not be modified."); + } + + @Test + void override_unsetHeader() { + // Given + var originalHeaders = Map.of("Header-To-Unset", "from original"); + var original = new Configuration().defaultHeaders(originalHeaders); + + Map overrideHeaders = new HashMap<>(); + overrideHeaders.put("Header-To-Unset", null); + var override = new ConfigurationOverride().additionalHeaders(overrideHeaders); + + // When + Configuration result = original.override(override); + + // Then + assertEquals(0, result.getDefaultHeaders().size()); + + assertEquals( + "from original", originalHeaders.get("Header-To-Unset"), "Original headers should not be modified."); + assertEquals(1, originalHeaders.size(), "Original headers should not be modified."); + assertEquals(1, overrideHeaders.size(), "Override headers should not be modified."); } @Test diff --git a/config/clients/java/template/creds-OAuth2Client.java.mustache b/config/clients/java/template/creds-OAuth2Client.java.mustache index 5af946c2..8becbf27 100644 --- a/config/clients/java/template/creds-OAuth2Client.java.mustache +++ b/config/clients/java/template/creds-OAuth2Client.java.mustache @@ -13,25 +13,27 @@ import java.time.Instant; import java.util.concurrent.CompletableFuture; public class OAuth2Client { - public static final String DEFAULT_TOKEN_ENDPOINT_PATH = "/oauth/token"; + private static final String DEFAULT_API_TOKEN_ISSUER_PATH = "/oauth/token"; + private final ApiClient apiClient; + private final String apiTokenIssuer; private final AccessToken token = new AccessToken(); private final CredentialsFlowRequest authRequest; - private final String tokenEndpointUrl; /** * Initializes a new instance of the {@link OAuth2Client} class * * @param configuration Configuration, including credentials, that can be used to retrieve an access tokens */ - public OAuth2Client(Configuration configuration, ApiClient apiClient) { - Credentials credentials = configuration.getCredentials(); + public OAuth2Client(Configuration configuration, ApiClient apiClient) throws FgaInvalidParameterException { + var clientCredentials = configuration.getCredentials().getClientCredentials(); + this.apiClient = apiClient; - this.tokenEndpointUrl = buildTokenEndpointUrl(credentials.getClientCredentials().getApiTokenIssuer()); + this.apiTokenIssuer = buildApiTokenIssuer(clientCredentials.getApiTokenIssuer()); this.authRequest = new CredentialsFlowRequest(); - this.authRequest.setClientId(credentials.getClientCredentials().getClientId()); - this.authRequest.setClientSecret(credentials.getClientCredentials().getClientSecret()); - this.authRequest.setAudience(credentials.getClientCredentials().getApiAudience()); + this.authRequest.setClientId(clientCredentials.getClientId()); + this.authRequest.setClientSecret(clientCredentials.getClientSecret()); + this.authRequest.setAudience(clientCredentials.getApiAudience()); this.authRequest.setGrantType("client_credentials"); } @@ -62,7 +64,7 @@ public class OAuth2Client { try { byte[] body = apiClient.getObjectMapper().writeValueAsBytes(authRequest); - Configuration config = new Configuration().apiUrl(tokenEndpointUrl); + Configuration config = new Configuration().apiUrl(apiTokenIssuer); HttpRequest.Builder requestBuilder = ApiClient.requestBuilder("POST", "", body, config); @@ -76,15 +78,23 @@ public class OAuth2Client { } } - private static String buildTokenEndpointUrl(String issuer) { - var uri = URI.create(issuer); + private static String buildApiTokenIssuer(String issuer) throws FgaInvalidParameterException { + URI uri; + try { + uri = URI.create(issuer); + } catch (IllegalArgumentException cause) { + throw new FgaInvalidParameterException("apiTokenIssuer", "ClientCredentials", cause); + } - if (uri.getScheme() == null) { + var scheme = uri.getScheme(); + if (scheme == null) { uri = URI.create("https://" + issuer); + } else if (!"https".equals(scheme) && !"http".equals(scheme)) { + throw new FgaInvalidParameterException("scheme", "apiTokenIssuer"); } if (uri.getPath().isEmpty() || uri.getPath().equals("/")) { - uri = URI.create(uri.getScheme() + "://" + uri.getAuthority() + DEFAULT_TOKEN_ENDPOINT_PATH); + uri = URI.create(uri.getScheme() + "://" + uri.getAuthority() + DEFAULT_API_TOKEN_ISSUER_PATH); } return uri.toString(); diff --git a/config/clients/java/template/creds-OAuth2ClientTest.java.mustache b/config/clients/java/template/creds-OAuth2ClientTest.java.mustache index 81ba8df5..13d0f10c 100644 --- a/config/clients/java/template/creds-OAuth2ClientTest.java.mustache +++ b/config/clients/java/template/creds-OAuth2ClientTest.java.mustache @@ -1,7 +1,7 @@ package {{authPackage}}; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -9,13 +9,14 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.pgssoft.httpclient.HttpClientMock; import {{clientPackage}}.ApiClient; import {{configPackage}}.*; +import {{errorsPackage}}.FgaInvalidParameterException; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import java.util.stream.Stream; - - class OAuth2ClientTest { private static final String CLIENT_ID = "client"; private static final String CLIENT_SECRET = "secret"; @@ -26,7 +27,7 @@ class OAuth2ClientTest { private final ObjectMapper mapper = new ObjectMapper(); private HttpClientMock mockHttpClient; - private static Stream issuersTokenEndpoint() { + private static Stream apiTokenIssuers() { return Stream.of( Arguments.of("issuer.fga.example", "https://issuer.fga.example/oauth/token"), Arguments.of("https://issuer.fga.example", "https://issuer.fga.example/oauth/token"), @@ -35,15 +36,16 @@ class OAuth2ClientTest { Arguments.of("https://issuer.fga.example:8080/", "https://issuer.fga.example:8080/oauth/token"), Arguments.of("issuer.fga.example/some_endpoint", "https://issuer.fga.example/some_endpoint"), Arguments.of("https://issuer.fga.example/some_endpoint", "https://issuer.fga.example/some_endpoint"), - Arguments.of("https://issuer.fga.example:8080/some_endpoint", "https://issuer.fga.example:8080/some_endpoint") - ); + Arguments.of( + "https://issuer.fga.example:8080/some_endpoint", + "https://issuer.fga.example:8080/some_endpoint")); } @ParameterizedTest - @MethodSource("issuersTokenEndpoint") + @MethodSource("apiTokenIssuers") public void exchangeToken(String apiTokenIssuer, String tokenEndpointUrl) throws Exception { // Given - OAuth2Client oAuth2 = configureClient(apiTokenIssuer); + OAuth2Client oAuth2 = newOAuth2Client(apiTokenIssuer); String expectedPostBody = String.format( "{\"client_id\":\"%s\",\"client_secret\":\"%s\",\"audience\":\"%s\",\"grant_type\":\"%s\"}", CLIENT_ID, CLIENT_SECRET, AUDIENCE, GRANT_TYPE); @@ -54,11 +56,43 @@ class OAuth2ClientTest { String result = oAuth2.getAccessToken().get(); // Then - mockHttpClient.verify().post(tokenEndpointUrl).withBody(is(expectedPostBody)).called(); + mockHttpClient + .verify() + .post(tokenEndpointUrl) + .withBody(is(expectedPostBody)) + .called(); assertEquals(ACCESS_TOKEN, result); } - private OAuth2Client configureClient(String apiTokenIssuer) { + @Test + public void apiTokenIssuer_invalidScheme() { + // When + var exception = assertThrows(FgaInvalidParameterException.class, () -> newOAuth2Client("ftp://issuer.fga.example")); + + // Then + assertEquals("Required parameter scheme was invalid when calling apiTokenIssuer.", exception.getMessage()); + } + + private static Stream invalidApiTokenIssuers() { + return Stream.of( + Arguments.of("://issuer.fga.example"), + Arguments.of("http://issuer.fga.example#bad#fragment"), + Arguments.of("http://issuer.fga.example/space in path"), + Arguments.of("http://")); + } + + @ParameterizedTest + @MethodSource("invalidApiTokenIssuers") + public void apiTokenIssuers_invalidURI(String invalidApiTokenIssuer) { + // When + var exception = assertThrows(FgaInvalidParameterException.class, () -> newOAuth2Client(invalidApiTokenIssuer)); + + // Then + assertEquals("Required parameter apiTokenIssuer was invalid when calling ClientCredentials.", exception.getMessage()); + assertInstanceOf(IllegalArgumentException.class, exception.getCause()); + } + + private OAuth2Client newOAuth2Client(String apiTokenIssuer) throws FgaInvalidParameterException { System.setProperty("HttpRequestAttempt.debug-logging", "enable"); mockHttpClient = new HttpClientMock(); diff --git a/config/clients/java/template/libraries/native/ApiClient.mustache b/config/clients/java/template/libraries/native/ApiClient.mustache index 0ef4f46c..3a2c63df 100644 --- a/config/clients/java/template/libraries/native/ApiClient.mustache +++ b/config/clients/java/template/libraries/native/ApiClient.mustache @@ -314,6 +314,19 @@ public class ApiClient { return this; } + /** + * Add a custom request interceptor. This interceptor will be run after any + * other interceptor(s) already in place. + * + *

For details on request interceptors, see {@link ApiClient#setRequestInterceptor(Consumer)}

+ * + * @param interceptor A function invoked before creating each request. A value + * of null resets the interceptor to a no-op. + */ + public void addRequestInterceptor(Consumer interceptor) { + this.interceptor = this.interceptor != null ? this.interceptor.andThen(interceptor) : interceptor; + } + /** * Get the custom interceptor. * diff --git a/config/clients/java/template/libraries/native/ApiResponse.mustache b/config/clients/java/template/libraries/native/ApiResponse.mustache index b183abc0..d7cd9bc8 100644 --- a/config/clients/java/template/libraries/native/ApiResponse.mustache +++ b/config/clients/java/template/libraries/native/ApiResponse.mustache @@ -1,6 +1,5 @@ {{>licenseInfo}} - -package {{invokerPackage}}; +package {{clientPackage}}; import java.util.List; import java.util.Map; diff --git a/config/clients/java/template/libraries/native/api.mustache b/config/clients/java/template/libraries/native/api.mustache index 7c6db23b..8eaa663d 100644 --- a/config/clients/java/template/libraries/native/api.mustache +++ b/config/clients/java/template/libraries/native/api.mustache @@ -74,6 +74,11 @@ public class OpenFgaApi { } else { this.oAuth2Client = null; } + + var defaultHeaders = configuration.getDefaultHeaders(); + if (defaultHeaders != null) { + apiClient.addRequestInterceptor(httpRequest -> defaultHeaders.forEach(httpRequest::setHeader)); + } } {{#operation}} @@ -262,6 +267,14 @@ public class OpenFgaApi { localVarRequestBuilder.header("Authorization", "Bearer " + accessToken); } + if (configuration.getUserAgent() != null) { + localVarRequestBuilder.header("User-Agent", configuration.getUserAgent()); + } + + if (configuration.getDefaultHeaders() != null) { + configuration.getDefaultHeaders().forEach(localVarRequestBuilder::header); + } + {{#bodyParam}} {{#isString}} localVarRequestBuilder.method("{{httpMethod}}", HttpRequest.BodyPublishers.ofString({{paramName}}));