Skip to content

Commit 75dbea2

Browse files
authored
feat(java-sdk): add support for write conflict settings (#641)
2 parents ad10a02 + 7b37038 commit 75dbea2

11 files changed

Lines changed: 467 additions & 22 deletions

.github/workflows/main.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ jobs:
175175
- name: Run All Tests
176176
run: |-
177177
make test-integration-client-java
178+
env:
179+
OPEN_API_REF: 0ac19aac54f21f3c78970126b84b4c69c6e3b9a2
178180

179181
- name: Check for SDK changes
180182
run: |

config/clients/java/template/README_calling_api.mustache

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,76 @@ var options = new ClientWriteOptions()
330330
var response = fgaClient.write(request, options).get();
331331
```
332332

333+
###### Conflict options for write operations
334+
335+
Write conflict handling can be controlled using the `onDuplicate` option for writes and the `onMissing` option for deletes.
336+
337+
> Note: this requires OpenFGA [v1.10.0](https://github.com/openfga/openfga/releases/tag/v1.10.0) or later.
338+
339+
- `onDuplicate`: Controls behavior when attempting to create a tuple that already exists
340+
- `WriteRequestWrites.OnDuplicateEnum.ERROR` (default): Return an error
341+
- `WriteRequestWrites.OnDuplicateEnum.IGNORE`: Skip the duplicate tuple and continue
342+
343+
- `onMissing`: Controls behavior when attempting to delete a tuple that doesn't exist
344+
- `WriteRequestDeletes.OnMissingEnum.ERROR` (default): Return an error
345+
- `WriteRequestDeletes.OnMissingEnum.IGNORE`: Skip the missing tuple and continue
346+
347+
**Using conflict options with the `write()` method:**
348+
349+
```java
350+
var request = new ClientWriteRequest()
351+
.writes(List.of(
352+
new ClientTupleKey()
353+
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
354+
.relation("viewer")
355+
._object("document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a")
356+
))
357+
.deletes(List.of(
358+
new ClientTupleKeyWithoutCondition()
359+
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
360+
.relation("writer")
361+
._object("document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a")
362+
));
363+
364+
var options = new ClientWriteOptions()
365+
.onDuplicate(WriteRequestWrites.OnDuplicateEnum.IGNORE)
366+
.onMissing(WriteRequestDeletes.OnMissingEnum.IGNORE);
367+
368+
var response = fgaClient.write(request, options).get();
369+
```
370+
371+
**Using conflict options with the `writeTuples()` convenience method:**
372+
373+
```java
374+
var tuples = List.of(
375+
new ClientTupleKey()
376+
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
377+
.relation("viewer")
378+
._object("document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a")
379+
);
380+
381+
var options = new ClientWriteTuplesOptions()
382+
.onDuplicate(WriteRequestWrites.OnDuplicateEnum.IGNORE);
383+
384+
var response = fgaClient.writeTuples(tuples, options).get();
385+
```
386+
387+
**Using conflict options with the `deleteTuples()` convenience method:**
388+
389+
```java
390+
var tuples = List.of(
391+
new ClientTupleKeyWithoutCondition()
392+
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
393+
.relation("writer")
394+
._object("document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a")
395+
);
396+
397+
var options = new ClientDeleteTuplesOptions()
398+
.onMissing(WriteRequestDeletes.OnMissingEnum.IGNORE);
399+
400+
var response = fgaClient.deleteTuples(tuples, options).get();
401+
```
402+
333403
#### Relationship Queries
334404

335405
##### Check

config/clients/java/template/src/main/api/client/OpenFgaClient.java.mustache

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -469,12 +469,14 @@ public class OpenFgaClient {
469469
470470
var writeTuples = request.getWrites();
471471
if (writeTuples != null && !writeTuples.isEmpty()) {
472-
body.writes(ClientTupleKey.asWriteRequestWrites(writeTuples));
472+
var onDuplicate = options != null ? options.getOnDuplicate() : null;
473+
body.writes(ClientTupleKey.asWriteRequestWrites(writeTuples, onDuplicate));
473474
}
474475

475476
var deleteTuples = request.getDeletes();
476477
if (deleteTuples != null && !deleteTuples.isEmpty()) {
477-
body.deletes(ClientTupleKeyWithoutCondition.asWriteRequestDeletes(deleteTuples));
478+
var onMissing = options != null ? options.getOnMissing() : null;
479+
body.deletes(ClientTupleKeyWithoutCondition.asWriteRequestDeletes(deleteTuples, onMissing));
478480
}
479481

480482
if (options != null && !isNullOrWhitespace(options.getAuthorizationModelId())) {
@@ -677,7 +679,8 @@ public class OpenFgaClient {
677679
678680
var body = new WriteRequest();
679681
680-
body.writes(ClientTupleKey.asWriteRequestWrites(tupleKeys));
682+
var onDuplicate = options != null ? options.getOnDuplicate() : null;
683+
body.writes(ClientTupleKey.asWriteRequestWrites(tupleKeys, onDuplicate));
681684
682685
String authorizationModelId = configuration.getAuthorizationModelId();
683686
if (!isNullOrWhitespace(authorizationModelId)) {
@@ -717,7 +720,8 @@ public class OpenFgaClient {
717720
718721
var body = new WriteRequest();
719722
720-
body.deletes(ClientTupleKeyWithoutCondition.asWriteRequestDeletes(tupleKeys));
723+
var onMissing = options != null ? options.getOnMissing() : null;
724+
body.deletes(ClientTupleKeyWithoutCondition.asWriteRequestDeletes(tupleKeys, onMissing));
721725
722726
String authorizationModelId = configuration.getAuthorizationModelId();
723727
if (!isNullOrWhitespace(authorizationModelId)) {

config/clients/java/template/src/main/api/client/model/ClientTupleKey.java.mustache

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,19 @@ public class ClientTupleKey extends ClientTupleKeyWithoutCondition {
3737
}
3838

3939
public static WriteRequestWrites asWriteRequestWrites(Collection<ClientTupleKey> tupleKeys) {
40-
return new WriteRequestWrites()
40+
return asWriteRequestWrites(tupleKeys, null);
41+
}
42+
43+
public static WriteRequestWrites asWriteRequestWrites(
44+
Collection<ClientTupleKey> tupleKeys, WriteRequestWrites.OnDuplicateEnum onDuplicate) {
45+
WriteRequestWrites writes = new WriteRequestWrites()
4146
.tupleKeys(tupleKeys.stream()
4247
.map(ClientTupleKey::asTupleKey)
4348
.collect(Collectors.toList()));
49+
if (onDuplicate != null) {
50+
writes.onDuplicate(onDuplicate);
51+
}
52+
return writes;
4453
}
4554

4655
/* Overrides for correct typing */

config/clients/java/template/src/main/api/client/model/ClientTupleKeyWithoutCondition.java.mustache

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,19 @@ public class ClientTupleKeyWithoutCondition {
1616
}
1717

1818
public static WriteRequestDeletes asWriteRequestDeletes(Collection<ClientTupleKeyWithoutCondition> tupleKeys) {
19-
return new WriteRequestDeletes()
19+
return asWriteRequestDeletes(tupleKeys, null);
20+
}
21+
22+
public static WriteRequestDeletes asWriteRequestDeletes(
23+
Collection<ClientTupleKeyWithoutCondition> tupleKeys, WriteRequestDeletes.OnMissingEnum onMissing) {
24+
WriteRequestDeletes deletes = new WriteRequestDeletes()
2025
.tupleKeys(tupleKeys.stream()
2126
.map(ClientTupleKeyWithoutCondition::asTupleKeyWithoutCondition)
2227
.collect(Collectors.toList()));
28+
if (onMissing != null) {
29+
deletes.onMissing(onMissing);
30+
}
31+
return deletes;
2332
}
2433

2534
public ClientTupleKeyWithoutCondition _object(String _object) {

config/clients/java/template/src/main/api/configuration/ClientDeleteTuplesOptions.java.mustache

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
{{>licenseInfo}}
22
package {{configPackage}};
33

4+
import dev.openfga.sdk.api.model.WriteRequestDeletes;
45
import java.util.Map;
56

67
public class ClientDeleteTuplesOptions implements AdditionalHeadersSupplier {
78
private Map<String, String> additionalHeaders;
9+
private WriteRequestDeletes.OnMissingEnum onMissing;
810
911
public ClientDeleteTuplesOptions additionalHeaders(Map<String, String> additionalHeaders) {
1012
this.additionalHeaders = additionalHeaders;
@@ -15,4 +17,13 @@ public class ClientDeleteTuplesOptions implements AdditionalHeadersSupplier {
1517
public Map<String, String> getAdditionalHeaders() {
1618
return this.additionalHeaders;
1719
}
20+
21+
public ClientDeleteTuplesOptions onMissing(WriteRequestDeletes.OnMissingEnum onMissing) {
22+
this.onMissing = onMissing;
23+
return this;
24+
}
25+
26+
public WriteRequestDeletes.OnMissingEnum getOnMissing() {
27+
return onMissing;
28+
}
1829
}

config/clients/java/template/src/main/api/configuration/ClientWriteOptions.java.mustache

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
{{>licenseInfo}}
22
package {{configPackage}};
33

4+
import dev.openfga.sdk.api.model.WriteRequestDeletes;
5+
import dev.openfga.sdk.api.model.WriteRequestWrites;
46
import java.util.Map;
57

68
public class ClientWriteOptions implements AdditionalHeadersSupplier {
79
private Map<String, String> additionalHeaders;
810
private String authorizationModelId;
911
private Boolean disableTransactions = false;
1012
private int transactionChunkSize;
13+
private WriteRequestWrites.OnDuplicateEnum onDuplicate;
14+
private WriteRequestDeletes.OnMissingEnum onMissing;
1115
1216
public ClientWriteOptions additionalHeaders(Map<String, String> additionalHeaders) {
1317
this.additionalHeaders = additionalHeaders;
@@ -45,4 +49,22 @@ public class ClientWriteOptions implements AdditionalHeadersSupplier {
4549
public int getTransactionChunkSize() {
4650
return transactionChunkSize > 0 ? transactionChunkSize : 1;
4751
}
52+
53+
public ClientWriteOptions onDuplicate(WriteRequestWrites.OnDuplicateEnum onDuplicate) {
54+
this.onDuplicate = onDuplicate;
55+
return this;
56+
}
57+
58+
public WriteRequestWrites.OnDuplicateEnum getOnDuplicate() {
59+
return onDuplicate;
60+
}
61+
62+
public ClientWriteOptions onMissing(WriteRequestDeletes.OnMissingEnum onMissing) {
63+
this.onMissing = onMissing;
64+
return this;
65+
}
66+
67+
public WriteRequestDeletes.OnMissingEnum getOnMissing() {
68+
return onMissing;
69+
}
4870
}

config/clients/java/template/src/main/api/configuration/ClientWriteTuplesOptions.java.mustache

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
{{>licenseInfo}}
22
package {{configPackage}};
33

4+
import dev.openfga.sdk.api.model.WriteRequestWrites;
45
import java.util.Map;
56

67
public class ClientWriteTuplesOptions implements AdditionalHeadersSupplier {
78
private Map<String, String> additionalHeaders;
9+
private WriteRequestWrites.OnDuplicateEnum onDuplicate;
810
911
public ClientWriteTuplesOptions additionalHeaders(Map<String, String> additionalHeaders) {
1012
this.additionalHeaders = additionalHeaders;
@@ -15,4 +17,13 @@ public class ClientWriteTuplesOptions implements AdditionalHeadersSupplier {
1517
public Map<String, String> getAdditionalHeaders() {
1618
return this.additionalHeaders;
1719
}
20+
21+
public ClientWriteTuplesOptions onDuplicate(WriteRequestWrites.OnDuplicateEnum onDuplicate) {
22+
this.onDuplicate = onDuplicate;
23+
return this;
24+
}
25+
26+
public WriteRequestWrites.OnDuplicateEnum getOnDuplicate() {
27+
return onDuplicate;
28+
}
1829
}

0 commit comments

Comments
 (0)