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
10 changes: 5 additions & 5 deletions docs/ApiExecutor.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Direct HTTP access to OpenFGA endpoints.
OpenFgaClient client = new OpenFgaClient(config);

// Build request
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("POST", "/stores/{store_id}/check")
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.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();
Expand All @@ -26,7 +26,7 @@ ApiResponse<String> rawResponse = client.apiExecutor().send(request).get();

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

**Methods:**
Expand All @@ -40,7 +40,7 @@ ApiExecutorRequestBuilder.builder(String method, String path)

**Example:**
```java
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("POST", "/stores/{store_id}/write")
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.POST, "/stores/{store_id}/write")
.pathParam("store_id", "01ABC")
.queryParam("dry_run", "true")
.header("X-Request-ID", "uuid")
Expand Down Expand Up @@ -74,7 +74,7 @@ T getData() // Deserialized data

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

Expand All @@ -84,7 +84,7 @@ client.apiExecutor().send(request, FeatureResponse.class)

### POST with Body
```java
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("POST", "/stores/{store_id}/bulk-delete")
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.POST, "/stores/{store_id}/bulk-delete")
.pathParam("store_id", storeId)
.queryParam("force", "true")
.body(new BulkDeleteRequest("2023-01-01", "user", 1000))
Expand Down
2 changes: 1 addition & 1 deletion examples/api-executor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ All requests will succeed (except #5 which intentionally triggers an error for d
Build requests using the builder pattern:

```java
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("POST", "/stores/{store_id}/custom-endpoint")
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.POST, "/stores/{store_id}/custom-endpoint")
.pathParam("store_id", storeId)
.queryParam("page_size", "20")
.queryParam("continuation_token", "eyJwayI6...")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.openfga.sdk.example;

import dev.openfga.sdk.api.client.HttpMethod;
import dev.openfga.sdk.api.client.OpenFgaClient;
import dev.openfga.sdk.api.client.ApiExecutorRequestBuilder;
import dev.openfga.sdk.api.configuration.ClientConfiguration;
Expand Down Expand Up @@ -63,7 +64,7 @@ public static void main(String[] args) throws Exception {
private static String listStoresExample(OpenFgaClient fgaClient) {
try {
// Build the raw request for GET /stores
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("GET", "/stores").build();
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.GET, "/stores").build();

// Execute with typed response
var response = fgaClient
Expand Down Expand Up @@ -101,7 +102,7 @@ private static String listStoresExample(OpenFgaClient fgaClient) {
*/
private static String createStoreForExamples(OpenFgaClient fgaClient) throws Exception {
String storeName = "api-executor-example-" + UUID.randomUUID().toString().substring(0, 8);
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("POST", "/stores")
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.POST, "/stores")
.body(Map.of("name", storeName))
.build();

Expand All @@ -116,7 +117,7 @@ private static String createStoreForExamples(OpenFgaClient fgaClient) throws Exc
*/
private static void getStoreRawJsonExample(OpenFgaClient fgaClient, String storeId) {
try {
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("GET", "/stores/{store_id}")
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.GET, "/stores/{store_id}")
.pathParam("store_id", storeId)
.build();

Expand All @@ -137,7 +138,7 @@ private static void getStoreRawJsonExample(OpenFgaClient fgaClient, String store
*/
private static void listStoresWithPaginationExample(OpenFgaClient fgaClient) {
try {
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("GET", "/stores")
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.GET, "/stores")
.queryParam("page_size", "2")
.build();

Expand Down Expand Up @@ -167,7 +168,7 @@ private static void listStoresWithPaginationExample(OpenFgaClient fgaClient) {
private static void createStoreWithHeadersExample(OpenFgaClient fgaClient) {
try {
String storeName = "raw-api-custom-headers-" + UUID.randomUUID().toString().substring(0, 8);
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("POST", "/stores")
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.POST, "/stores")
.header("X-Example-Header", "custom-value")
.header("X-Request-ID", "req-" + UUID.randomUUID())
.body(Map.of("name", storeName))
Expand All @@ -192,8 +193,7 @@ private static void createStoreWithHeadersExample(OpenFgaClient fgaClient) {
*/
private static void errorHandlingExample(OpenFgaClient fgaClient) {
try {
// Try to get a non-existent store
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("GET", "/stores/{store_id}")
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.GET, "/stores/{store_id}")
.pathParam("store_id", "01ZZZZZZZZZZZZZZZZZZZZZZZ9")
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
*
* <p>Example:</p>
* <pre>{@code
* ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("POST", "/stores/{store_id}/endpoint")
* ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.POST, "/stores/{store_id}/endpoint")
* .pathParam("store_id", storeId)
* .body(requestData)
* .build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
* Fluent builder for constructing HTTP requests to OpenFGA API endpoints.
Expand All @@ -11,25 +10,22 @@
*
* <p>Example:</p>
* <pre>{@code
* ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("POST", "/stores/{store_id}/endpoint")
* ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.POST, "/stores/{store_id}/endpoint")
* .pathParam("store_id", storeId)
* .queryParam("limit", "50")
* .body(requestObject)
* .build();
* }</pre>
*/
public class ApiExecutorRequestBuilder {
private static final Set<String> VALID_HTTP_METHODS =
Set.of("GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS");

private final String method;
private final HttpMethod method;
private final String path;
private final Map<String, String> pathParams;
private final Map<String, String> queryParams;
private final Map<String, String> headers;
private Object body;

private ApiExecutorRequestBuilder(String method, String path) {
private ApiExecutorRequestBuilder(HttpMethod method, String path) {
this.method = method;
this.path = path;
this.pathParams = new HashMap<>();
Expand All @@ -41,26 +37,20 @@ private ApiExecutorRequestBuilder(String method, String path) {
/**
* Creates a new ApiExecutorRequestBuilder instance.
*
* @param method HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)
* @param method HTTP method enum value (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)
* @param path API path with optional placeholders like {store_id}
* @return New ApiExecutorRequestBuilder instance
* @throws IllegalArgumentException if method or path is invalid
*/
public static ApiExecutorRequestBuilder builder(String method, String path) {
if (method == null || method.trim().isEmpty()) {
throw new IllegalArgumentException("HTTP method cannot be null or empty");
public static ApiExecutorRequestBuilder builder(HttpMethod method, String path) {
if (method == null) {
throw new IllegalArgumentException("HTTP method cannot be null");
}
if (path == null || path.trim().isEmpty()) {
throw new IllegalArgumentException("Path cannot be null or empty");
}

String upperMethod = method.toUpperCase();
if (!VALID_HTTP_METHODS.contains(upperMethod)) {
throw new IllegalArgumentException(
"Invalid HTTP method: " + method + ". Valid methods: " + VALID_HTTP_METHODS);
}

return new ApiExecutorRequestBuilder(upperMethod, path);
return new ApiExecutorRequestBuilder(method, path);
}

/**
Expand Down Expand Up @@ -115,6 +105,7 @@ public ApiExecutorRequestBuilder body(Object body) {
this.body = body;
return this;
}

/**
* Builds and returns the request for use with the API Executor.
* This method must be called to complete request construction.
Expand All @@ -129,7 +120,7 @@ public ApiExecutorRequestBuilder build() {
}

String getMethod() {
return method;
return method.name();
}

String getPath() {
Expand Down
44 changes: 44 additions & 0 deletions src/main/java/dev/openfga/sdk/api/client/HttpMethod.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package dev.openfga.sdk.api.client;

/**
* Enumeration of standard HTTP methods supported by the OpenFGA API.
* This enum provides type safety and prevents invalid HTTP methods from being used.
*
* @since 0.8.0
*/
public enum HttpMethod {
/**
* HTTP GET method - used for retrieving resources.
*/
GET,

/**
* HTTP POST method - used for creating resources or submitting data.
*/
POST,

/**
* HTTP PUT method - used for updating or replacing resources.
*/
PUT,

/**
* HTTP DELETE method - used for deleting resources.
*/
DELETE,

/**
* HTTP PATCH method - used for partially updating resources.
*/
PATCH,

/**
* HTTP HEAD method - used for retrieving resource metadata without the body.
*/
HEAD,

/**
* HTTP OPTIONS method - used for describing communication options.
*/
OPTIONS
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public void rawRequest_listStores() throws Exception {

// Use ApiExecutor to list stores (equivalent to GET /stores)
ApiExecutorRequestBuilder request =
ApiExecutorRequestBuilder.builder("GET", "/stores").build();
ApiExecutorRequestBuilder.builder(HttpMethod.GET, "/stores").build();

ApiResponse<ListStoresResponse> response =
fga.apiExecutor().send(request, ListStoresResponse.class).get();
Expand Down Expand Up @@ -84,7 +84,7 @@ public void rawRequest_createStore_typedResponse() throws Exception {
requestBody.put("name", storeName);

// Use ApiExecutor to create store (equivalent to POST /stores)
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("POST", "/stores")
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.POST, "/stores")
.body(requestBody)
.build();

Expand Down Expand Up @@ -115,7 +115,7 @@ public void rawRequest_createStore_rawJsonResponse() throws Exception {
requestBody.put("name", storeName);

// Use ApiExecutor to create store and get raw JSON response
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("POST", "/stores")
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.POST, "/stores")
.body(requestBody)
.build();

Expand Down Expand Up @@ -147,7 +147,7 @@ public void rawRequest_getStore_withPathParams() throws Exception {
String storeId = createStoreUsingRawRequest(storeName);

// Use ApiExecutor to get store details (equivalent to GET /stores/{store_id})
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("GET", "/stores/{store_id}")
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.GET, "/stores/{store_id}")
.pathParam("store_id", storeId)
.build();

Expand Down Expand Up @@ -176,8 +176,8 @@ public void rawRequest_automaticStoreIdReplacement() throws Exception {
fga.setStoreId(storeId);

// Use ApiExecutor WITHOUT providing store_id path param - it should be auto-replaced
ApiExecutorRequestBuilder request =
ApiExecutorRequestBuilder.builder("GET", "/stores/{store_id}").build();
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.GET, "/stores/{store_id}")
.build();

ApiResponse<GetStoreResponse> response =
fga.apiExecutor().send(request, GetStoreResponse.class).get();
Expand Down Expand Up @@ -228,7 +228,7 @@ public void rawRequest_writeAuthorizationModel() throws Exception {

// Use ApiExecutor to write authorization model
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(
"POST", "/stores/{store_id}/authorization-models")
HttpMethod.POST, "/stores/{store_id}/authorization-models")
.body(requestBody)
.build();

Expand Down Expand Up @@ -261,7 +261,7 @@ public void rawRequest_readAuthorizationModels_withQueryParams() throws Exceptio

// Use ApiExecutor to read authorization models with query parameters
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(
"GET", "/stores/{store_id}/authorization-models")
HttpMethod.GET, "/stores/{store_id}/authorization-models")
.queryParam("page_size", "10")
.queryParam("continuation_token", "")
.build();
Expand Down Expand Up @@ -308,7 +308,8 @@ public void rawRequest_check() throws Exception {
tupleKey.put("object", "document:budget");
checkBody.put("tuple_key", tupleKey);

ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("POST", "/stores/{store_id}/check")
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(
HttpMethod.POST, "/stores/{store_id}/check")
.body(checkBody)
.build();

Expand All @@ -331,7 +332,7 @@ public void rawRequest_withCustomHeaders() throws Exception {
requestBody.put("name", storeName);

// Use ApiExecutor with custom headers
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("POST", "/stores")
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.POST, "/stores")
.body(requestBody)
.header("X-Custom-Header", "custom-value")
.header("X-Request-ID", "test-123")
Expand All @@ -353,7 +354,7 @@ public void rawRequest_withCustomHeaders() throws Exception {
@Test
public void rawRequest_errorHandling_notFound() throws Exception {
// Try to get a non-existent store
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("GET", "/stores/{store_id}")
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.GET, "/stores/{store_id}")
.pathParam("store_id", "non-existent-store-id")
.build();

Expand Down Expand Up @@ -383,7 +384,7 @@ public void rawRequest_listStores_withPagination() throws Exception {
}

// Use ApiExecutor to list stores with pagination
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("GET", "/stores")
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.GET, "/stores")
.queryParam("page_size", "2")
.build();

Expand All @@ -409,7 +410,7 @@ private String createStoreUsingRawRequest(String storeName) throws Exception {
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("name", storeName);

ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("POST", "/stores")
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(HttpMethod.POST, "/stores")
.body(requestBody)
.build();

Expand Down Expand Up @@ -445,7 +446,7 @@ private String writeSimpleAuthorizationModel(String storeId) throws Exception {
requestBody.put("type_definitions", typeDefinitions);

ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(
"POST", "/stores/{store_id}/authorization-models")
HttpMethod.POST, "/stores/{store_id}/authorization-models")
.pathParam("store_id", storeId)
.body(requestBody)
.build();
Expand All @@ -467,7 +468,8 @@ private void writeTupleUsingRawRequest(String storeId, String user, String relat
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("writes", Map.of("tuple_keys", List.of(tupleKey)));

ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder("POST", "/stores/{store_id}/write")
ApiExecutorRequestBuilder request = ApiExecutorRequestBuilder.builder(
HttpMethod.POST, "/stores/{store_id}/write")
.pathParam("store_id", storeId)
.body(requestBody)
.build();
Expand Down
Loading
Loading