Skip to content

Commit e45fc26

Browse files
authored
feat: add support for write conflict settings (#234)
1 parent 4c55350 commit e45fc26

19 files changed

+657
-36
lines changed

README.md

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

644+
###### Conflict options for write operations
645+
646+
Write conflict handling can be controlled using the `onDuplicate` option for writes and the `onMissing` option for deletes.
647+
648+
> Note: this requires OpenFGA [v1.10.0](https://github.com/openfga/openfga/releases/tag/v1.10.0) or later.
649+
650+
- `onDuplicate`: Controls behavior when attempting to create a tuple that already exists
651+
- `WriteRequestWrites.OnDuplicateEnum.ERROR` (default): Return an error
652+
- `WriteRequestWrites.OnDuplicateEnum.IGNORE`: Skip the duplicate tuple and continue
653+
654+
- `onMissing`: Controls behavior when attempting to delete a tuple that doesn't exist
655+
- `WriteRequestDeletes.OnMissingEnum.ERROR` (default): Return an error
656+
- `WriteRequestDeletes.OnMissingEnum.IGNORE`: Skip the missing tuple and continue
657+
658+
**Using conflict options with the `write()` method:**
659+
660+
```java
661+
var request = new ClientWriteRequest()
662+
.writes(List.of(
663+
new ClientTupleKey()
664+
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
665+
.relation("viewer")
666+
._object("document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a")
667+
))
668+
.deletes(List.of(
669+
new ClientTupleKeyWithoutCondition()
670+
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
671+
.relation("writer")
672+
._object("document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a")
673+
));
674+
675+
var options = new ClientWriteOptions()
676+
.onDuplicate(WriteRequestWrites.OnDuplicateEnum.IGNORE)
677+
.onMissing(WriteRequestDeletes.OnMissingEnum.IGNORE);
678+
679+
var response = fgaClient.write(request, options).get();
680+
```
681+
682+
**Using conflict options with the `writeTuples()` convenience method:**
683+
684+
```java
685+
var tuples = List.of(
686+
new ClientTupleKey()
687+
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
688+
.relation("viewer")
689+
._object("document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a")
690+
);
691+
692+
var options = new ClientWriteTuplesOptions()
693+
.onDuplicate(WriteRequestWrites.OnDuplicateEnum.IGNORE);
694+
695+
var response = fgaClient.writeTuples(tuples, options).get();
696+
```
697+
698+
**Using conflict options with the `deleteTuples()` convenience method:**
699+
700+
```java
701+
var tuples = List.of(
702+
new ClientTupleKeyWithoutCondition()
703+
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
704+
.relation("writer")
705+
._object("document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a")
706+
);
707+
708+
var options = new ClientDeleteTuplesOptions()
709+
.onMissing(WriteRequestDeletes.OnMissingEnum.IGNORE);
710+
711+
var response = fgaClient.deleteTuples(tuples, options).get();
712+
```
713+
644714
#### Relationship Queries
645715

646716
##### Check

docs/OpenFgaApi.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2303,7 +2303,7 @@ No authorization required
23032303
23042304
Add or delete tuples from the store
23052305

2306-
The Write API will transactionally update the tuples for a certain store. Tuples and type definitions allow OpenFGA to determine whether a relationship exists between an object and an user. In the body, `writes` adds new tuples and `deletes` removes existing tuples. When deleting a tuple, any `condition` specified with it is ignored. The API is not idempotent: if, later on, you try to add the same tuple key (even if the `condition` is different), or if you try to delete a non-existing tuple, it will throw an error. The API will not allow you to write tuples such as `document:2021-budget#viewer@document:2021-budget#viewer`, because they are implicit. An `authorization_model_id` may be specified in the body. If it is, it will be used to assert that each written tuple (not deleted) is valid for the model specified. If it is not specified, the latest authorization model ID will be used. ## Example ### Adding relationships To add `user:anne` as a `writer` for `document:2021-budget`, call write API with the following ```json { \"writes\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Removing relationships To remove `user:bob` as a `reader` for `document:2021-budget`, call write API with the following ```json { \"deletes\": { \"tuple_keys\": [ { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } ] } } ```
2306+
The Write API will transactionally update the tuples for a certain store. Tuples and type definitions allow OpenFGA to determine whether a relationship exists between an object and an user. In the body, `writes` adds new tuples and `deletes` removes existing tuples. When deleting a tuple, any `condition` specified with it is ignored. The API is not idempotent by default: if, later on, you try to add the same tuple key (even if the `condition` is different), or if you try to delete a non-existing tuple, it will throw an error. To allow writes when an identical tuple already exists in the database, set `\"on_duplicate\": \"ignore\"` on the `writes` object. To allow deletes when a tuple was already removed from the database, set `\"on_missing\": \"ignore\"` on the `deletes` object. If a Write request contains both idempotent (ignore) and non-idempotent (error) operations, the most restrictive action (error) will take precedence. If a condition fails for a sub-request with an error flag, the entire transaction will be rolled back. This gives developers explicit control over the atomicity of the requests. The API will not allow you to write tuples such as `document:2021-budget#viewer@document:2021-budget#viewer`, because they are implicit. An `authorization_model_id` may be specified in the body. If it is, it will be used to assert that each written tuple (not deleted) is valid for the model specified. If it is not specified, the latest authorization model ID will be used. ## Example ### Adding relationships To add `user:anne` as a `writer` for `document:2021-budget`, call write API with the following ```json { \"writes\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" } ], \"on_duplicate\": \"ignore\" }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Removing relationships To remove `user:bob` as a `reader` for `document:2021-budget`, call write API with the following ```json { \"deletes\": { \"tuple_keys\": [ { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } ], \"on_missing\": \"ignore\" } } ```
23072307

23082308
### Example
23092309

@@ -2378,7 +2378,7 @@ No authorization required
23782378
23792379
Add or delete tuples from the store
23802380

2381-
The Write API will transactionally update the tuples for a certain store. Tuples and type definitions allow OpenFGA to determine whether a relationship exists between an object and an user. In the body, `writes` adds new tuples and `deletes` removes existing tuples. When deleting a tuple, any `condition` specified with it is ignored. The API is not idempotent: if, later on, you try to add the same tuple key (even if the `condition` is different), or if you try to delete a non-existing tuple, it will throw an error. The API will not allow you to write tuples such as `document:2021-budget#viewer@document:2021-budget#viewer`, because they are implicit. An `authorization_model_id` may be specified in the body. If it is, it will be used to assert that each written tuple (not deleted) is valid for the model specified. If it is not specified, the latest authorization model ID will be used. ## Example ### Adding relationships To add `user:anne` as a `writer` for `document:2021-budget`, call write API with the following ```json { \"writes\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" } ] }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Removing relationships To remove `user:bob` as a `reader` for `document:2021-budget`, call write API with the following ```json { \"deletes\": { \"tuple_keys\": [ { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } ] } } ```
2381+
The Write API will transactionally update the tuples for a certain store. Tuples and type definitions allow OpenFGA to determine whether a relationship exists between an object and an user. In the body, `writes` adds new tuples and `deletes` removes existing tuples. When deleting a tuple, any `condition` specified with it is ignored. The API is not idempotent by default: if, later on, you try to add the same tuple key (even if the `condition` is different), or if you try to delete a non-existing tuple, it will throw an error. To allow writes when an identical tuple already exists in the database, set `\"on_duplicate\": \"ignore\"` on the `writes` object. To allow deletes when a tuple was already removed from the database, set `\"on_missing\": \"ignore\"` on the `deletes` object. If a Write request contains both idempotent (ignore) and non-idempotent (error) operations, the most restrictive action (error) will take precedence. If a condition fails for a sub-request with an error flag, the entire transaction will be rolled back. This gives developers explicit control over the atomicity of the requests. The API will not allow you to write tuples such as `document:2021-budget#viewer@document:2021-budget#viewer`, because they are implicit. An `authorization_model_id` may be specified in the body. If it is, it will be used to assert that each written tuple (not deleted) is valid for the model specified. If it is not specified, the latest authorization model ID will be used. ## Example ### Adding relationships To add `user:anne` as a `writer` for `document:2021-budget`, call write API with the following ```json { \"writes\": { \"tuple_keys\": [ { \"user\": \"user:anne\", \"relation\": \"writer\", \"object\": \"document:2021-budget\" } ], \"on_duplicate\": \"ignore\" }, \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\" } ``` ### Removing relationships To remove `user:bob` as a `reader` for `document:2021-budget`, call write API with the following ```json { \"deletes\": { \"tuple_keys\": [ { \"user\": \"user:bob\", \"relation\": \"reader\", \"object\": \"document:2021-budget\" } ], \"on_missing\": \"ignore\" } } ```
23822382

23832383
### Example
23842384

docs/WriteRequestDeletes.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@
88
| Name | Type | Description | Notes |
99
|------------ | ------------- | ------------- | -------------|
1010
|**tupleKeys** | [**List<TupleKeyWithoutCondition>**](TupleKeyWithoutCondition.md) | | |
11+
|**onMissing** | [**OnMissingEnum**](#OnMissingEnum) | On 'error', the API returns an error when deleting a tuple that does not exist. On 'ignore', deletes of non-existent tuples are treated as no-ops. | [optional] |
12+
13+
14+
15+
## Enum: OnMissingEnum
16+
17+
| Name | Value |
18+
|---- | -----|
19+
| ERROR | "error" |
20+
| IGNORE | "ignore" |
21+
| UNKNOWN_DEFAULT_OPEN_API | "unknown_default_open_api" |
1122

1223

1324

docs/WriteRequestWrites.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@
88
| Name | Type | Description | Notes |
99
|------------ | ------------- | ------------- | -------------|
1010
|**tupleKeys** | [**List<TupleKey>**](TupleKey.md) | | |
11+
|**onDuplicate** | [**OnDuplicateEnum**](#OnDuplicateEnum) | On 'error' ( or unspecified ), the API returns an error if an identical tuple already exists. On 'ignore', identical writes are treated as no-ops (matching on user, relation, object, and RelationshipCondition). | [optional] |
12+
13+
14+
15+
## Enum: OnDuplicateEnum
16+
17+
| Name | Value |
18+
|---- | -----|
19+
| ERROR | "error" |
20+
| IGNORE | "ignore" |
21+
| UNKNOWN_DEFAULT_OPEN_API | "unknown_default_open_api" |
1122

1223

1324

0 commit comments

Comments
 (0)