oscerd opened a new pull request, #27195: URL: https://github.com/apache/camel/pull/27195
Resolves [CAMEL-25184](https://issues.apache.org/jira/browse/CAMEL-25184). `camel-openfga` shipped with the five decision operations (`check`, `batchCheck`, `listObjects`, `listRelations`, `listUsers`) and the two write operations. That covers "may this subject do this", but not "what access exists" — which is what you need to build an access-review report, to drive a projection or cache off the change log, or to work out *why* a check answered the way it did. This adds the three read operations that answer those questions. ## Operations | Operation | Answers | Body on the way out | |---|---|---| | `readTuples` | which tuples match a filter | `List<Map<String, Object>>` keyed `user` / `relation` / `object` / `timestamp` | | `readChanges` | what changed in the store, oldest first | same, plus `operation` = `WRITE` or `DELETE` | | `expand` | how a relation resolves, as a tree | the SDK's `UsersetTree` | `readTuples` deliberately emits **exactly the keys `writeTuples` and `deleteTuples` already accept**, so revoking what you just read needs no transformation in between: ```java from("direct:revokeEverythingBobHas") .to("openfga:readTuples?storeId={{fga.store}}&object=document:&user=user:bob") .to("openfga:deleteTuples?storeId={{fga.store}}"); ``` `whatReadTuplesReturnsCanBeRevokedWithoutReshaping` is the test that holds that contract in place — it is the property most likely to be broken by a well-meant tidy-up of either shape. `expand` is the one operation that does not leave a `List` on the body. Its answer *is* a tree, and flattening it would destroy the only information the operation exists to provide, so the `UsersetTree` goes on the body as the SDK returns it. ## Paging `pageSize` bounds a request; the token comes back on `CamelOpenFgaContinuationToken` and is fed back in through `continuationToken` (Simple-evaluated, so it can come off an exchange property): ```java from("timer:sync?period=30000") .to("openfga:readChanges?storeId={{fga.store}}&type=document" + "&continuationToken=${exchangeProperty.fgaToken}") .setProperty("fgaToken", header("CamelOpenFgaContinuationToken")) .split(body()).to("direct:applyChange"); ``` On the last page the header is **removed** rather than left holding the previous value, so a route looping on it terminates instead of re-reading the final page for ever. `startTime` (ISO-8601) bounds where a first `readChanges` begins — without it, a first read against a busy store pages through the entire history before it reaches anything current. It is parsed when the endpoint starts, not on the first exchange, so a typo fails the route rather than the message. ## What OpenFGA actually accepts as a read filter This is the part I got wrong first and only found because an integration test failed against a real server. The Read API filter is **not** "every part is optional". Probed against OpenFGA 1.21.0: * no filter at all — reads the whole store, a page at a time: **accepted** * `object=document:` **plus** a `user` — **accepted** * `object=document:budget`, with or without user/relation — **accepted** * `user` alone, `relation` alone, or `object=document:` alone — **rejected by the server** OpenFGA wants an object *type* as soon as any filter is given, and will not take an empty object id and an empty user together. `validateReadFilter` enforces that before the call and names the option to change, instead of letting an opaque HTTP 400 surface as if the component had malfunctioned. That rule is also why a type-only `document:` is accepted as a read filter but still rejected as an `object` everywhere else — a filter and an identifier are not the same thing, and the existing identifier validation is unchanged. ## Tests * `OpenFgaReadOperationsTest` — 11 tests: the three operations, the round-trip contract above, the filter rules the server imposes, the token-removal-on-last-page behaviour, and `refusesAStartTimeThatIsNotATimestampWhenTheEndpointStarts`. * `OpenFgaIT` — 3 new integration tests against a real OpenFGA container, which is where the filter rule came from. * 109 unit tests and 15 integration tests green; full reactor `BUILD SUCCESS`. Generated metadata gained `pageSize`, `continuationToken`, `startTime` and `CamelOpenFgaContinuationToken` and lost nothing. ## Note for whoever merges This overlaps #27185 (CAMEL-25183, contextual tuples) in `OpenFgaProducer` and `OpenFgaConfiguration`. Whichever lands second needs a rebase — neither change depends on the other, so the order does not matter. _Claude Code on behalf of oscerd_ -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
