Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(java-sdk): implement batchCheck, listRelations, and non-transaction write #235

Merged
merged 8 commits into from
Nov 22, 2023
7 changes: 7 additions & 0 deletions config/clients/java/CHANGELOG.md.mustache
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## v0.2.3

### [0.2.3](https://{{gitHost}}/{{gitUserId}}/{{gitRepoId}}/compare/v0.2.2...v0.2.3) (2023-11-21)

- feat(client): implement batchCheck, listRelations, and non-transaction write
- fix(client): adds missing "contextual tuples" field to check request

## v0.2.2

### [0.2.2](https://{{gitHost}}/{{gitUserId}}/{{gitRepoId}}/compare/v0.2.1...v0.2.2) (2023-10-31)
Expand Down
18 changes: 17 additions & 1 deletion config/clients/java/config.overrides.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"gitRepoId": "java-sdk",
"artifactId": "openfga-sdk",
"groupId": "dev.openfga",
"packageVersion": "0.2.2",
"packageVersion": "0.2.3",
"apiPackage": "dev.openfga.sdk.api",
"authPackage": "dev.openfga.sdk.api.auth",
"clientPackage": "dev.openfga.sdk.api.client",
Expand Down Expand Up @@ -47,6 +47,10 @@
"destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientAssertion.java",
"templateType": "SupportingFiles"
},
"client-ClientBatchCheckResponse.java.mustache" : {
"destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientBatchCheckResponse.java",
"templateType": "SupportingFiles"
},
"client-ClientCheckRequest.java.mustache" : {
"destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientCheckRequest.java",
"templateType": "SupportingFiles"
Expand Down Expand Up @@ -91,6 +95,10 @@
"destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientListRelationsRequest.java",
"templateType": "SupportingFiles"
},
"client-ClientListRelationsResponse.java.mustache" : {
"destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientListRelationsResponse.java",
"templateType": "SupportingFiles"
},
"client-ClientReadAssertionsResponse.java.mustache" : {
"destinationFilename": "src/main/java/dev/openfga/sdk/api/client/ClientReadAssertionsResponse.java",
"templateType": "SupportingFiles"
Expand Down Expand Up @@ -179,6 +187,10 @@
"destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/BaseConfiguration.java",
"templateType": "SupportingFiles"
},
"config-ClientBatchCheckOptions.java.mustache" : {
"destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientBatchCheckOptions.java",
"templateType": "SupportingFiles"
},
"config-ClientCheckOptions.java.mustache" : {
"destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientCheckOptions.java",
"templateType": "SupportingFiles"
Expand All @@ -203,6 +215,10 @@
"destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientListObjectsOptions.java",
"templateType": "SupportingFiles"
},
"config-ClientListRelationsOptions.java.mustache" : {
"destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientListRelationsOptions.java",
"templateType": "SupportingFiles"
},
"config-ClientListStoresOptions.java.mustache" : {
"destinationFilename": "src/main/java/dev/openfga/sdk/api/configuration/ClientListStoresOptions.java",
"templateType": "SupportingFiles"
Expand Down
126 changes: 123 additions & 3 deletions config/clients/java/template/README_calling_api.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,30 @@ 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.

```java
// Coming soon
var request = new ClientWriteRequest()
.writes(List.of(
new ClientTupleKey()
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
.relation("viewer")
._object("document:roadmap"),
new ClientTupleKey()
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
.relation("viewer")
._object("document:budget")
))
.deletes(List.of(
new ClientTupleKey()
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
.relation("writer")
._object("document:roadmap")
));
var options = new ClientWriteOptions()
// You can rely on the model id set in the configuration or override it for this specific request
.authorizationModelId("01GXSA8YR785C4FYS3C0RTG7B1")
.disableTransactions(true)
.transactionChunkSize(5); // Maximum number of requests to be sent in a transaction in a particular chunk

var response = fgaClient.write(request, options).get();
```

#### Relationship Queries
Expand Down Expand Up @@ -295,7 +318,85 @@ Run a set of [checks](#check). Batch Check will return `allowed: false` if it en
If 429s or 5xxs are encountered, the underlying check will retry up to {{defaultMaxRetry}} times before giving up.

```java
// Coming soon
var request = List.of(
new ClientCheckRequest()
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
.relation("viewer")
._object("document:roadmap")
.contextualTuples(List.of(
new ClientTupleKey()
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
.relation("editor")
._object("document:roadmap")
)),
new ClientCheckRequest()
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
.relation("admin")
._object("document:roadmap"),
.contextualTuples(List.of(
new ClientTupleKey()
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
.relation("editor")
._object("document:roadmap")
)),
new ClientCheckRequest()
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
.relation("creator")
._object("document:roadmap"),
new ClientCheckRequest()
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
.relation("deleter")
._object("document:roadmap")
);
var options = new ClientBatchCheckOptions()
// 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}}

var response = fgaClient.batchCheck(request, options).get();

/*
response.getResponses() = [{
allowed: false,
request: {
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
relation: "viewer",
_object: "document:roadmap",
contextualTuples: [{
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
relation: "editor",
_object: "document:roadmap"
}]
}
}, {
allowed: false,
request: {
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
relation: "admin",
_object: "document:roadmap",
contextualTuples: [{
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
relation: "editor",
_object: "document:roadmap"
}]
}
}, {
allowed: false,
request: {
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
relation: "creator",
_object: "document:roadmap",
},
error: <FgaError ...>
}, {
allowed: true,
request: {
user: "user:81684243-9356-4421-8fbf-a4f8d36aa31b",
relation: "deleter",
_object: "document:roadmap",
}},
]
*/
```

##### Expand
Expand Down Expand Up @@ -348,7 +449,26 @@ var response = fgaClient.listObjects(request, options).get();
List the relations a user has on an object.

```java
// Coming soon.
var request = new ClientListRelationsRequest()
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
._object("document:roadmap")
.relations(List.of("can_view", "can_edit", "can_delete", "can_rename"))
.contextualTuples(List.of(
new ClientTupleKey()
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
.relation("editor")
._object("document:roadmap")
)
);
var options = new ClientListRelationsOptions()
// When unspecified, defaults to {{clientMaxMethodParallelRequests}}
.maxParallelRequests()
// You can rely on the model id set in the configuration or override it for this specific request
.authorizationModelId(DEFAULT_AUTH_MODEL_ID);

var response = fgaClient.listRelations(request, options).get();

// response.getRelations() = ["can_view", "can_edit"]
```

#### Assertions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
{{>licenseInfo}}
package {{clientPackage}};

import {{modelPackage}}.CheckResponse;
import {{errorsPackage}}.FgaError;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;

public class ClientBatchCheckResponse extends CheckResponse {
private final ClientCheckRequest request;
private final Throwable throwable;
private final Integer statusCode;
private final Map<String, List<String>> headers;
private final String rawResponse;

public ClientBatchCheckResponse(
ClientCheckRequest request, ClientCheckResponse clientCheckResponse, Throwable throwable) {
this.request = request;
this.throwable = throwable;

if (clientCheckResponse != null) {
this.statusCode = clientCheckResponse.getStatusCode();
this.headers = clientCheckResponse.getHeaders();
this.rawResponse = clientCheckResponse.getRawResponse();
this.setAllowed(clientCheckResponse.getAllowed());
this.setResolution(clientCheckResponse.getResolution());
} else if (throwable instanceof FgaError) {
FgaError error = (FgaError) throwable;
this.statusCode = error.getStatusCode();
this.headers = error.getResponseHeaders().map();
this.rawResponse = error.getResponseData();
} else {
// Should be unreachable, but required for type completion
this.statusCode = null;
this.headers = null;
this.rawResponse = null;
}
}

public ClientCheckRequest getRequest() {
return request;
}

/**
* Returns the result of the check.
* <p>
* If the HTTP request was unsuccessful, this result will be null. If this is the case, you can examine the
* original request with {@link ClientBatchCheckResponse#getRequest()} and the exception with
* {@link ClientBatchCheckResponse#getThrowable()}.
*
* @return the check result. Is null if the HTTP request was unsuccessful.
*/
@Override
public Boolean getAllowed() {
return super.getAllowed();
}

/**
* Returns the caught exception if the HTTP request was unsuccessful.
* <p>
* If the HTTP request was unsuccessful, this result will be null. If this is the case, you can examine the
* original request with {@link ClientBatchCheckResponse#getRequest()} and the exception with
* {@link ClientBatchCheckResponse#getThrowable()}.
*
* @return the caught exception. Is null if the HTTP request was successful.
*/
public Throwable getThrowable() {
return throwable;
}

public int getStatusCode() {
return statusCode;
}

public Map<String, List<String>> getHeaders() {
return headers;
}

public String getRawResponse() {
return rawResponse;
}

public String getRelation() {
return request == null ? null : request.getRelation();
}

public static BiFunction<ClientCheckResponse, Throwable, ClientBatchCheckResponse> asyncHandler(
ClientCheckRequest request) {
return (response, throwable) -> new ClientBatchCheckResponse(request, response, throwable);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
{{>licenseInfo}}
package {{invokerPackage}};

import java.util.List;

public class ClientCheckRequest {
private String user;
private String relation;
private String _object;
private List<ClientTupleKey> contextualTuples;

public ClientCheckRequest _object(String _object) {
this._object = _object;
Expand Down Expand Up @@ -44,4 +47,13 @@ public class ClientCheckRequest {
public String getUser() {
return user;
}

public ClientCheckRequest contextualTuples(List<ClientTupleKey> contextualTuples) {
this.contextualTuples = contextualTuples;
return this;
}

public List<ClientTupleKey> getContextualTuples() {
return contextualTuples;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{{>licenseInfo}}
package {{clientPackage}};

import java.util.List;
import java.util.stream.Collectors;

public class ClientListRelationsResponse {
private final List<String> relations;

public ClientListRelationsResponse(List<String> relations) {
this.relations = relations;
}

public List<String> getRelations() {
return relations;
}

public static ClientListRelationsResponse fromBatchCheckResponses(List<ClientBatchCheckResponse> responses)
throws Throwable {
// If any response ultimately failed (with retries) we throw the first exception encountered.
var failedResponse = responses.stream()
.filter(response -> response.getThrowable() != null)
.findFirst();
if (failedResponse.isPresent()) {
throw failedResponse.get().getThrowable();
}

var relations = responses.stream()
.filter(ClientBatchCheckResponse::getAllowed)
.map(ClientBatchCheckResponse::getRelation)
.collect(Collectors.toList());
return new ClientListRelationsResponse(relations);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {{modelPackage}}.ContextualTupleKeys;
import {{modelPackage}}.TupleKey;
import {{modelPackage}}.TupleKeys;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class ClientTupleKey {
Expand Down Expand Up @@ -55,12 +56,12 @@ public class ClientTupleKey {
return new TupleKey().user(user).relation(relation)._object(_object);
}

public static TupleKeys asTupleKeys(List<ClientTupleKey> clientTupleKeys) {
public static Optional<TupleKeys> asTupleKeys(List<ClientTupleKey> clientTupleKeys) {
if (clientTupleKeys == null || clientTupleKeys.size() == 0) {
return new TupleKeys();
return Optional.empty();
}

return new TupleKeys().tupleKeys(asListOfTupleKey(clientTupleKeys));
return Optional.of(new TupleKeys().tupleKeys(asListOfTupleKey(clientTupleKeys)));
}

public static ContextualTupleKeys asContextualTupleKeys(List<ClientTupleKey> clientTupleKeys) {
Expand Down
Loading
Loading