Skip to content

Commit b4b2a12

Browse files
feat: add support for Write API with on_duplicate and on_missing options (#233)
* OpenFGA API Protobuf for Idempotent Writes * Update openfga/v1/openfga_service.proto Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * CodeReview fixes * CodeReview fixes * changing on_missing and on_duplicate to string value instead of Enum for proper JSON values * Make sure on_duplicate, on_missing are optional params --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent c0b62b2 commit b4b2a12

5 files changed

Lines changed: 2595 additions & 2451 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ may be overly strict. In those cases you can bypass it with `commit --no-verify`
2121

2222
1. Generate the sources as above
2323
2. In the `proto` directory execute the following commands:
24-
```
24+
```shell
2525
go mod init go.buf.build/openfga/go/openfga/api
2626
go mod tidy
2727
```
2828
3. In OpenFGA, add the following line to your `go.mod`:
29-
```
29+
```shell
3030
replace github.com/openfga/api/proto => /path/to/proto
3131
```
3232

docs/openapiv2/apidocs.swagger.json

Lines changed: 21 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

openfga/v1/openfga_service.proto

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,10 @@ service OpenFGAService {
154154
"type definitions allow OpenFGA to determine whether a "
155155
"relationship exists between an object and an user.\n"
156156
"In the body, `writes` adds new tuples and `deletes` removes existing tuples. When deleting a tuple, any `condition` specified with it is ignored.\n"
157-
"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.\n"
157+
"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.\n"
158+
"To allow writes when an identical tuple already exists in the database, set `\"on_duplicate\": \"ignore\"` on the `writes` object.\n"
159+
"To allow deletes when a tuple was already removed from the database, set `\"on_missing\": \"ignore\"` on the `deletes` object.\n"
160+
"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.\n"
158161
"The API will not allow you to write tuples such as `document:2021-budget#viewer@document:2021-budget#viewer`, because they are implicit.\n"
159162
"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) "
160163
"is valid for the model specified. If it is not specified, the latest authorization model ID will be used.\n"
@@ -171,7 +174,8 @@ service OpenFGAService {
171174
" \"relation\": \"writer\",\n"
172175
" \"object\": \"document:2021-budget\"\n"
173176
" }\n"
174-
" ]\n"
177+
" ],\n"
178+
" \"on_duplicate\": \"ignore\"\n"
175179
" },\n"
176180
" \"authorization_model_id\": \"01G50QVV17PECNVAHX1GG4Y5NC\"\n"
177181
"}\n"
@@ -188,7 +192,8 @@ service OpenFGAService {
188192
" \"relation\": \"reader\",\n"
189193
" \"object\": \"document:2021-budget\"\n"
190194
" }\n"
191-
" ]\n"
195+
" ],\n"
196+
" \"on_missing\": \"ignore\"\n"
192197
" }\n"
193198
"}\n"
194199
"```\n"
@@ -1248,6 +1253,19 @@ message WriteRequestWrites {
12481253
(validate.rules).repeated.min_items = 1,
12491254
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {min_items: 1}
12501255
];
1256+
string on_duplicate = 2 [
1257+
json_name = "on_duplicate",
1258+
(google.api.field_behavior) = OPTIONAL,
1259+
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
1260+
enum: [
1261+
"error", // Return an error if a tuple already exists.
1262+
"ignore" // Treat identical writes as no-ops if all attributes (including the RelationshipCondition) match.
1263+
]
1264+
default: "error"
1265+
example: "\"ignore\""
1266+
description: "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)."
1267+
}
1268+
];
12511269
}
12521270

12531271
message WriteRequestDeletes {
@@ -1257,6 +1275,19 @@ message WriteRequestDeletes {
12571275
(validate.rules).repeated.min_items = 1,
12581276
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {min_items: 1}
12591277
];
1278+
string on_missing = 2 [
1279+
json_name = "on_missing",
1280+
(google.api.field_behavior) = OPTIONAL,
1281+
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
1282+
enum: [
1283+
"error", // Return an error if a tuple does not exist.
1284+
"ignore" // Do not return an error if a tuple does not exist.
1285+
]
1286+
default: "error"
1287+
example: "\"ignore\""
1288+
description: "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."
1289+
}
1290+
];
12601291
}
12611292

12621293
message WriteRequest {

0 commit comments

Comments
 (0)