Skip to content

Commit 3db5535

Browse files
committed
Add JSONL support for tuple write
1 parent cf8b50b commit 3db5535

4 files changed

Lines changed: 58 additions & 1 deletion

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,7 @@ fga tuple **write** <user> <relation> <object> --store-id=<store-id>
703703
* `--store-id`: Specifies the store id
704704
* `--model-id`: Specifies the model id to target (optional)
705705
* `--file`: Specifies the file name, `json`, `yaml` and `csv` files are supported
706+
* When using a `json` file, tuples can be provided as an array or newline-delimited (`jsonl`) objects
706707
* `--max-tuples-per-write`: Max tuples to send in a single write (optional, default=1, or 40 if `--max-rps` is set and this flag is omitted)
707708
* `--max-parallel-requests`: Max requests to send in parallel (optional, default=4, or `max-rps/5` if `--max-rps` is set and this flag is omitted)
708709
* `--hide-imported-tuples`: When importing from a file, do not output successfully imported tuples in the command output (optional, default=false)
@@ -772,6 +773,7 @@ If using a `json` file, the format should be:
772773
}
773774
]
774775
```
776+
JSON files can also be provided in JSONL format, with one JSON object per line.
775777

776778
###### Response
777779
```json5
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{"user": "user:anne", "relation": "owner", "object": "folder:product"}
2+
{"user": "folder:product", "relation": "parent", "object": "folder:product-2021"}
3+
{"user": "user:beth", "relation": "viewer", "object": "folder:product-2021"}

cmd/tuple/write_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,27 @@ func TestParseTuplesFileData(t *testing.T) {
148148
},
149149
},
150150
},
151+
{
152+
name: "it can correctly parse a jsonl file",
153+
file: "testdata/tuples_jsonl.json",
154+
expectedTuples: []client.ClientTupleKey{
155+
{
156+
User: "user:anne",
157+
Relation: "owner",
158+
Object: "folder:product",
159+
},
160+
{
161+
User: "folder:product",
162+
Relation: "parent",
163+
Object: "folder:product-2021",
164+
},
165+
{
166+
User: "user:beth",
167+
Relation: "viewer",
168+
Object: "folder:product-2021",
169+
},
170+
},
171+
},
151172
{
152173
name: "it can correctly parse a yaml file",
153174
file: "testdata/tuples.yaml",

internal/tuplefile/read.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,15 @@ func ReadTupleFile(fileName string) ([]client.ClientTupleKey, error) {
2121
var tuples []client.ClientTupleKey
2222

2323
switch path.Ext(fileName) {
24-
case ".json", ".yaml", ".yml":
24+
case ".json":
25+
err = yaml.Unmarshal(data, &tuples)
26+
if err != nil {
27+
err = parseTuplesFromJSONL(data, &tuples)
28+
}
29+
if err == nil && len(tuples) == 0 {
30+
err = clierrors.EmptyTuplesFileError("json")
31+
}
32+
case ".yaml", ".yml":
2533
err = yaml.Unmarshal(data, &tuples)
2634
if err == nil && len(tuples) == 0 {
2735
err = clierrors.EmptyTuplesFileError(strings.TrimPrefix(path.Ext(fileName), "."))
@@ -38,3 +46,26 @@ func ReadTupleFile(fileName string) ([]client.ClientTupleKey, error) {
3846

3947
return tuples, nil
4048
}
49+
50+
func parseTuplesFromJSONL(data []byte, tuples *[]client.ClientTupleKey) error {
51+
lines := strings.Split(strings.TrimSpace(string(data)), "\n")
52+
for index, line := range lines {
53+
trimmed := strings.TrimSpace(line)
54+
if trimmed == "" {
55+
continue
56+
}
57+
58+
var tuple client.ClientTupleKey
59+
if err := yaml.Unmarshal([]byte(trimmed), &tuple); err != nil {
60+
return fmt.Errorf("failed to parse tuple on line %d: %w", index+1, err)
61+
}
62+
63+
*tuples = append(*tuples, tuple)
64+
}
65+
66+
if len(*tuples) == 0 {
67+
return clierrors.EmptyTuplesFileError("json")
68+
}
69+
70+
return nil
71+
}

0 commit comments

Comments
 (0)