Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 86 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ This is an autogenerated Java SDK for OpenFGA. It provides a wrapper around the
- [Assertions](#assertions)
- [Read Assertions](#read-assertions)
- [Write Assertions](#write-assertions)
- [Calling Other Endpoints](#calling-other-endpoints)
- [Retries](#retries)
- [API Endpoints](#api-endpoints)
- [Models](#models)
Expand Down Expand Up @@ -746,7 +747,7 @@ Similar to [check](#check), but instead of checking a single user-object relatio
> Passing `ClientBatchCheckOptions` is optional. All fields of `ClientBatchCheckOptions` are optional.

```java
var reequst = new ClientBatchCheckRequest().checks(
var request = new ClientBatchCheckRequest().checks(
List.of(
new ClientBatchCheckItem()
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
Expand Down Expand Up @@ -774,7 +775,7 @@ var reequst = new ClientBatchCheckRequest().checks(
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
.relation("creator")
._object("document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a")
.correlationId("cor-3), // optional, one will be generated for you if not provided
.correlationId("cor-3"), // optional, one will be generated for you if not provided
new ClientCheckRequest()
.user("user:81684243-9356-4421-8fbf-a4f8d36aa31b")
.relation("deleter")
Expand Down Expand Up @@ -1167,6 +1168,89 @@ try {
}
```

### Calling Other Endpoints

The Raw API provides direct HTTP access to OpenFGA endpoints not yet wrapped by the SDK. It maintains the SDK's client configuration including authentication, telemetry, retries, and error handling.

Use cases:
- Calling endpoints not yet supported by the SDK
- Using an SDK version that lacks support for a particular endpoint
- Accessing custom endpoints that extend the OpenFGA API

Initialize the SDK normally and access the Raw API via the `fgaClient` instance:

```java
// Initialize the client, same as above
ClientConfiguration config = new ClientConfiguration()
.apiUrl("http://localhost:8080")
.storeId("01YCP46JKYM8FJCQ37NMBYHE5X");
OpenFgaClient fgaClient = new OpenFgaClient(config);

// Custom new endpoint that doesn't exist in the SDK yet
Map<String, Object> requestBody = Map.of(
"user", "user:bob",
"action", "custom_action",
"resource", "resource:123"
);

// Build the request
RawRequestBuilder request = RawRequestBuilder.builder("POST", "/stores/{store_id}/custom-endpoint")
.pathParam("store_id", storeId)
.queryParam("page_size", "20")
.queryParam("continuation_token", "eyJwayI6...")
.body(requestBody)
.header("X-Experimental-Feature", "enabled")
.build();
```

#### Example: Calling a new "Custom Endpoint" endpoint and handling raw response

```java
// Get raw response without automatic decoding
ApiResponse<String> rawResponse = fgaClient.raw().send(request).get();

String rawJson = rawResponse.getData();
System.out.println("Response: " + rawJson);

// You can access fields like headers, status code, etc. from rawResponse:
System.out.println("Status Code: " + rawResponse.getStatusCode());
System.out.println("Headers: " + rawResponse.getHeaders());
```

#### Example: Calling a new "Custom Endpoint" endpoint and decoding response into a struct

```java
// Define a class to hold the response
class CustomEndpointResponse {
private boolean allowed;
private String reason;

public boolean isAllowed() { return allowed; }
public void setAllowed(boolean allowed) { this.allowed = allowed; }
public String getReason() { return reason; }
public void setReason(String reason) { this.reason = reason; }
}

// Get response decoded into CustomEndpointResponse class
ApiResponse<CustomEndpointResponse> response = fgaClient.raw()
.send(request, CustomEndpointResponse.class)
.get();

CustomEndpointResponse customEndpointResponse = response.getData();
System.out.println("Allowed: " + customEndpointResponse.isAllowed());
System.out.println("Reason: " + customEndpointResponse.getReason());

// You can access fields like headers, status code, etc. from response:
System.out.println("Status Code: " + response.getStatusCode());
System.out.println("Headers: " + response.getHeaders());
```

For a complete working example, see [examples/raw-api](examples/raw-api).

#### Documentation

See [docs/RawApi.md](docs/RawApi.md) for complete API reference and examples.

### API Endpoints

| Method | HTTP request | Description |
Expand Down
167 changes: 167 additions & 0 deletions docs/RawApi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Raw API

Direct HTTP access to OpenFGA endpoints.

## Quick Start

```java
OpenFgaClient client = new OpenFgaClient(config);

// Build request
RawRequestBuilder request = RawRequestBuilder.builder("POST", "/stores/{store_id}/check")
.pathParam("store_id", storeId)
.body(Map.of("tuple_key", Map.of("user", "user:jon", "relation", "reader", "object", "doc:1")))
.build();

// Execute - typed response
ApiResponse<CheckResponse> response = client.raw().send(request, CheckResponse.class).get();

// Execute - raw JSON
ApiResponse<String> rawResponse = client.raw().send(request).get();
```

## API Reference

### RawRequestBuilder

**Factory:**
```java
RawRequestBuilder.builder(String method, String path)
```

**Methods:**
```java
.pathParam(String key, String value) // Replace {key} in path, URL-encoded
.queryParam(String key, String value) // Add query parameter, URL-encoded
.header(String key, String value) // Add HTTP header
.body(Object body) // Set request body (auto-serialized to JSON)
.build() // Complete the builder (required)
```

**Example:**
```java
RawRequestBuilder request = RawRequestBuilder.builder("POST", "/stores/{store_id}/write")
.pathParam("store_id", "01ABC")
.queryParam("dry_run", "true")
.header("X-Request-ID", "uuid")
.body(requestObject)
.build();
```

### RawApi

**Access:**
```java
RawApi rawApi = client.raw();
```

**Methods:**
```java
CompletableFuture<ApiResponse<String>> send(RawRequestBuilder request)
CompletableFuture<ApiResponse<T>> send(RawRequestBuilder request, Class<T> responseType)
```

### ApiResponse<T>

```java
int getStatusCode() // HTTP status
Map<String, List<String>> getHeaders() // Response headers
String getRawResponse() // Raw JSON body
T getData() // Deserialized data
```

## Examples

### GET Request
```java
RawRequestBuilder request = RawRequestBuilder.builder("GET", "/stores/{store_id}/feature")
.pathParam("store_id", storeId)
.build();

client.raw().send(request, FeatureResponse.class)
.thenAccept(r -> System.out.println("Status: " + r.getStatusCode()));
```

### POST with Body
```java
RawRequestBuilder request = RawRequestBuilder.builder("POST", "/stores/{store_id}/bulk-delete")
.pathParam("store_id", storeId)
.queryParam("force", "true")
.body(new BulkDeleteRequest("2023-01-01", "user", 1000))
.build();

client.raw().send(request, BulkDeleteResponse.class).get();
```

### Raw JSON Response
```java
ApiResponse<String> response = client.raw().send(request).get();
String json = response.getRawResponse(); // Raw JSON
```

### Query Parameters
```java
RawRequestBuilder.builder("GET", "/stores/{store_id}/items")
.pathParam("store_id", storeId)
.queryParam("page", "1")
.queryParam("limit", "50")
.queryParam("sort", "created_at")
.build();
```

### Custom Headers
```java
RawRequestBuilder.builder("POST", "/stores/{store_id}/action")
.header("X-Request-ID", UUID.randomUUID().toString())
.header("X-Idempotency-Key", "key-123")
.body(data)
.build();
```

### Error Handling
```java
client.raw().send(request, ResponseType.class)
.exceptionally(e -> {
if (e.getCause() instanceof FgaError) {
FgaError error = (FgaError) e.getCause();
System.err.println("API Error: " + error.getStatusCode());
}
return null;
});
```

### Map as Request Body
```java
RawRequestBuilder.builder("POST", "/stores/{store_id}/settings")
.pathParam("store_id", storeId)
.body(Map.of(
"setting", "value",
"enabled", true,
"threshold", 100,
"options", List.of("opt1", "opt2")
))
.build();
```

## Notes

- Path/query parameters are URL-encoded automatically
- Authentication tokens injected from client config
- `{store_id}` auto-replaced if not provided via `.pathParam()`

## Migration to Typed Methods

When SDK adds typed methods for an endpoint, you can migrate from Raw API:

```java
// Raw API
RawRequestBuilder request = RawRequestBuilder.builder("POST", "/stores/{store_id}/check")
.body(req)
.build();

client.raw().send(request, CheckResponse.class).get();

// Typed SDK (when available)
client.check(req).get();
```

7 changes: 7 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@ A simple example that creates a store, runs a set of calls against it including

#### OpenTelemetry Examples
- `opentelemetry/` - Demonstrates OpenTelemetry integration both via manual code configuration, as well as no-code instrumentation using the OpenTelemetry java agent

#### Streaming Examples
- `streamed-list-objects/` - Demonstrates using the StreamedListObjects API to retrieve large result sets without pagination limits

#### Raw API Examples
- `raw-api/` - Demonstrates direct HTTP access to OpenFGA endpoints not yet wrapped by the SDK, maintaining SDK configuration (authentication, retries, error handling)

17 changes: 17 additions & 0 deletions examples/raw-api/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.PHONY: build run run-openfga
all: build

project_name=.
openfga_version=latest
language=java

build:
../../gradlew -P language=$(language) build

run:
../../gradlew -P language=$(language) run

run-openfga:
docker pull docker.io/openfga/openfga:${openfga_version} && \
docker run -p 8080:8080 docker.io/openfga/openfga:${openfga_version} run

Loading
Loading