Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
41 changes: 22 additions & 19 deletions src/main/java/dev/openfga/sdk/api/OpenFgaApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ public OpenFgaApi(Configuration configuration) throws FgaInvalidParameterExcepti
public OpenFgaApi(Configuration configuration, ApiClient apiClient) throws FgaInvalidParameterException {
this.apiClient = apiClient;
this.configuration = configuration;
this.telemetry = new Telemetry(this.configuration);
this.telemetry = configuration.getTelemetry();

if (configuration.getCredentials().getCredentialsMethod() == CredentialsMethod.CLIENT_CREDENTIALS) {
this.oAuth2Client = new OAuth2Client(configuration, apiClient);
this.oAuth2Client = new OAuth2Client(configuration, apiClient, this.telemetry);
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Avoid NPE when a ConfigurationOverride switches to CLIENT_CREDENTIALS

getAccessToken(Configuration) uses the instance field oAuth2Client (built only from the root Configuration). If a per-call ConfigurationOverride specifies CredentialsMethod.CLIENT_CREDENTIALS while the root did not, oAuth2Client is null and a NullPointerException will occur.

Create/choose an OAuth2Client based on the effective configuration (root vs override). Example:

// replace existing getAccessToken with:
private String getAccessToken(Configuration configuration) throws ApiException {
    CredentialsMethod credentialsMethod = configuration.getCredentials().getCredentialsMethod();

    if (credentialsMethod == CredentialsMethod.API_TOKEN) {
        return configuration.getCredentials().getApiToken().getToken();
    }

    if (credentialsMethod == CredentialsMethod.CLIENT_CREDENTIALS) {
        try {
            OAuth2Client client =
                (configuration == this.configuration && this.oAuth2Client != null)
                    ? this.oAuth2Client
                    : new OAuth2Client(configuration, apiClient, configuration.getTelemetry());
            return client.getAccessToken().get();
        } catch (Exception e) {
            throw new ApiException(e);
        }
    }

    throw new IllegalStateException("Configuration is invalid.");
}

This preserves reuse for the root client while supporting overrides with different credentials.

Would you like me to push a follow-up PR adjusting getAccessToken and updating the call sites accordingly?


🏁 Script executed:

#!/bin/bash
set -eu

# Locate the OpenFgaApi.java file
file=$(fd OpenFgaApi.java -t f | head -n1)
echo "Inspecting: $file"

# Show where oAuth2Client is declared and used
echo -e "\n=== oAuth2Client references ==="
rg -n 'oAuth2Client' -C3 "$file"

# Show the getAccessToken method signature and body
echo -e "\n=== getAccessToken implementation ==="
rg -n 'private.*getAccessToken' -C0 "$file"
rg -n 'getAccessToken' -C10 "$file"

Length of output: 3892


Ensure OAuth2Client is initialized for overrides using CLIENT_CREDENTIALS

getAccessToken(Configuration) currently always uses the field oAuth2Client, which is only non-null when the root configuration uses CLIENT_CREDENTIALS. If a per-call Configuration override switches to CLIENT_CREDENTIALS, oAuth2Client remains null, causing an NPE.

Please update the method to reuse the root client when appropriate and instantiate a new one for overrides:

• File: src/main/java/dev/openfga/sdk/api/OpenFgaApi.java
Location: private String getAccessToken(Configuration configuration) (around lines 1261–1268)

Suggested replacement:

-private String getAccessToken(Configuration configuration) throws ApiException {
-    CredentialsMethod credentialsMethod = configuration.getCredentials().getCredentialsMethod();
-
-    if (credentialsMethod == CredentialsMethod.API_TOKEN) {
-        return configuration.getCredentials().getApiToken().getToken();
-    }
-
-    if (credentialsMethod == CredentialsMethod.CLIENT_CREDENTIALS) {
-        try {
-            return oAuth2Client.getAccessToken().get();
-        } catch (Exception e) {
-            throw new ApiException(e);
-        }
-    }
-
-    throw new IllegalStateException("Configuration is invalid.");
-}
+private String getAccessToken(Configuration configuration) throws ApiException {
+    CredentialsMethod credentialsMethod = configuration.getCredentials().getCredentialsMethod();
+
+    if (credentialsMethod == CredentialsMethod.API_TOKEN) {
+        return configuration.getCredentials().getApiToken().getToken();
+    }
+
+    if (credentialsMethod == CredentialsMethod.CLIENT_CREDENTIALS) {
+        try {
+            OAuth2Client client =
+                (configuration == this.configuration && this.oAuth2Client != null)
+                    ? this.oAuth2Client
+                    : new OAuth2Client(configuration, apiClient, configuration.getTelemetry());
+            return client.getAccessToken().get();
+        } catch (Exception e) {
+            throw new ApiException(e);
+        }
+    }
+
+    throw new IllegalStateException("Configuration is invalid.");
+}

This preserves reuse of the root client while safely supporting per-call overrides with different credential methods.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/dev/openfga/sdk/api/OpenFgaApi.java around the
getAccessToken(Configuration) method (approx lines 1261–1268), the code always
references the field oAuth2Client which can be null for per-call Configuration
overrides using CLIENT_CREDENTIALS; update the method to: check if the effective
configuration's auth method is CLIENT_CREDENTIALS, and if so reuse
this.oAuth2Client when the passed configuration is the same as the root
configuration (instantiating and assigning it if the root intends
CLIENT_CREDENTIALS but the field is null), otherwise create a new local
OAuth2Client with the override configuration and use it to get the token
(without assigning it to the field); for non-CLIENT_CREDENTIALS flows continue
using existing logic—this ensures root reuse and safe per-call overrides without
NPEs.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SoulPancake, great! The fix I suggested will prevent the NPE and ensure OAuth2Client works correctly with both root configurations and per-call overrides. Let me know if you'd like me to help with anything else related to this telemetry implementation.

} else {
Comment on lines 78 to 85
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Telemetry initialization: good reuse, but beware configurationOverride semantics drift

Using a single Telemetry instance from the root Configuration and injecting it into OAuth2Client is consistent with the PR goal of reuse. However, downstream calls that accept a ConfigurationOverride will still emit telemetry through the root instance, ignoring any telemetryConfiguration set on the override. This can lead to telemetry being recorded with the wrong configuration (e.g., disabled/enabled, different exporters, different attributes) when an override is used.

If overrides are intended to have their own telemetry semantics (Configuration.override() explicitly resets telemetry to lazily reinitialize), pass telemetry from the effective configuration per call rather than from the root.

Two viable options:

  • Preferred: derive telemetry per method using configuration.getTelemetry() when constructing HttpRequestAttempt.
  • Or: drop the explicit telemetry parameter and use the HttpRequestAttempt constructor that pulls from its Configuration.

See suggested call-site fixes in the next comment.

🤖 Prompt for AI Agents
In src/main/java/dev/openfga/sdk/api/OpenFgaApi.java around lines 78-85, you are
capturing telemetry from the root Configuration and passing it into
OAuth2Client, which causes ConfigurationOverride telemetry settings to be
ignored; change the code to stop using the stored root telemetry for downstream
calls — either (preferred) stop injecting telemetry into OAuth2Client and
instead derive telemetry per call via configuration.getTelemetry() when
constructing HttpRequestAttempt (or wherever requests are built), or modify
OAuth2Client to pull telemetry from the effective Configuration at the time of
the request (e.g., pass the Configuration or ConfigurationOverride into
OAuth2Client methods and call getTelemetry() there) so overrides’
telemetryConfiguration is honored.

this.oAuth2Client = null;
}
Expand Down Expand Up @@ -145,7 +145,7 @@ private CompletableFuture<ApiResponse<BatchCheckResponse>> batchCheck(

try {
HttpRequest request = buildHttpRequest("POST", path, body, configuration);
return new HttpRequestAttempt<>(request, "batchCheck", BatchCheckResponse.class, apiClient, configuration)
return new HttpRequestAttempt<>(request, "batchCheck", BatchCheckResponse.class, apiClient, configuration, this.telemetry)
.addTelemetryAttributes(telemetryAttributes)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure per-call ConfigurationOverride’s telemetry is honored

All these call sites pass the root telemetry (this.telemetry), which prevents telemetryConfiguration provided via ConfigurationOverride from taking effect. Use the effective configuration’s telemetry instead.

Apply this pattern change across the listed lines:

-            return new HttpRequestAttempt<>(request, "batchCheck", BatchCheckResponse.class, apiClient, configuration, this.telemetry)
+            return new HttpRequestAttempt<>(request, "batchCheck", BatchCheckResponse.class, apiClient, configuration, configuration.getTelemetry())
-            return new HttpRequestAttempt<>(request, "check", CheckResponse.class, apiClient, configuration, this.telemetry)
+            return new HttpRequestAttempt<>(request, "check", CheckResponse.class, apiClient, configuration, configuration.getTelemetry())
-            return new HttpRequestAttempt<>(request, "createStore", CreateStoreResponse.class, apiClient, configuration, this.telemetry)
+            return new HttpRequestAttempt<>(request, "createStore", CreateStoreResponse.class, apiClient, configuration, configuration.getTelemetry())
-            return new HttpRequestAttempt<>(request, "deleteStore", Void.class, apiClient, configuration, this.telemetry)
+            return new HttpRequestAttempt<>(request, "deleteStore", Void.class, apiClient, configuration, configuration.getTelemetry())
-            return new HttpRequestAttempt<>(request, "expand", ExpandResponse.class, apiClient, configuration, this.telemetry)
+            return new HttpRequestAttempt<>(request, "expand", ExpandResponse.class, apiClient, configuration, configuration.getTelemetry())
-            return new HttpRequestAttempt<>(request, "getStore", GetStoreResponse.class, apiClient, configuration, this.telemetry)
+            return new HttpRequestAttempt<>(request, "getStore", GetStoreResponse.class, apiClient, configuration, configuration.getTelemetry())
-            return new HttpRequestAttempt<>(request, "listObjects", ListObjectsResponse.class, apiClient, configuration, this.telemetry)
+            return new HttpRequestAttempt<>(request, "listObjects", ListObjectsResponse.class, apiClient, configuration, configuration.getTelemetry())
-            return new HttpRequestAttempt<>(request, "listStores", ListStoresResponse.class, apiClient, configuration, this.telemetry)
+            return new HttpRequestAttempt<>(request, "listStores", ListStoresResponse.class, apiClient, configuration, configuration.getTelemetry())
-            return new HttpRequestAttempt<>(request, "listUsers", ListUsersResponse.class, apiClient, configuration, this.telemetry)
+            return new HttpRequestAttempt<>(request, "listUsers", ListUsersResponse.class, apiClient, configuration, configuration.getTelemetry())
-            return new HttpRequestAttempt<>(request, "read", ReadResponse.class, apiClient, configuration, this.telemetry)
+            return new HttpRequestAttempt<>(request, "read", ReadResponse.class, apiClient, configuration, configuration.getTelemetry())
-            return new HttpRequestAttempt<>(
-                            request, "readAssertions", ReadAssertionsResponse.class, apiClient, configuration, this.telemetry)
+            return new HttpRequestAttempt<>(
+                            request, "readAssertions", ReadAssertionsResponse.class, apiClient, configuration, configuration.getTelemetry())
-            return new HttpRequestAttempt<>(
-                            request,
-                            "readAuthorizationModel",
-                            ReadAuthorizationModelResponse.class,
-                            apiClient,
-                            configuration,
-                            this.telemetry)
+            return new HttpRequestAttempt<>(
+                            request,
+                            "readAuthorizationModel",
+                            ReadAuthorizationModelResponse.class,
+                            apiClient,
+                            configuration,
+                            configuration.getTelemetry())
-            return new HttpRequestAttempt<>(
-                            request,
-                            "readAuthorizationModels",
-                            ReadAuthorizationModelsResponse.class,
-                            apiClient,
-                            configuration,
-                            this.telemetry)
+            return new HttpRequestAttempt<>(
+                            request,
+                            "readAuthorizationModels",
+                            ReadAuthorizationModelsResponse.class,
+                            apiClient,
+                            configuration,
+                            configuration.getTelemetry())
-            return new HttpRequestAttempt<>(request, "readChanges", ReadChangesResponse.class, apiClient, configuration, this.telemetry)
+            return new HttpRequestAttempt<>(request, "readChanges", ReadChangesResponse.class, apiClient, configuration, configuration.getTelemetry())
-            return new HttpRequestAttempt<>(request, "write", Object.class, apiClient, configuration, this.telemetry)
+            return new HttpRequestAttempt<>(request, "write", Object.class, apiClient, configuration, configuration.getTelemetry())
-            return new HttpRequestAttempt<>(request, "writeAssertions", Void.class, apiClient, configuration, this.telemetry)
+            return new HttpRequestAttempt<>(request, "writeAssertions", Void.class, apiClient, configuration, configuration.getTelemetry())
-            return new HttpRequestAttempt<>(
-                            request,
-                            "writeAuthorizationModel",
-                            WriteAuthorizationModelResponse.class,
-                            apiClient,
-                            configuration,
-                            this.telemetry)
+            return new HttpRequestAttempt<>(
+                            request,
+                            "writeAuthorizationModel",
+                            WriteAuthorizationModelResponse.class,
+                            apiClient,
+                            configuration,
+                            configuration.getTelemetry())

Also applies to: 205-205, 255-255, 304-304, 360-360, 410-410, 466-466, 517-517, 573-573, 629-629, 687-687, 749-751, 815-816, 901-902, 956-956, 1023-1024, 1085-1087

.attemptHttpRequest();
} catch (ApiException e) {
Expand Down Expand Up @@ -201,7 +201,7 @@ private CompletableFuture<ApiResponse<CheckResponse>> check(

try {
HttpRequest request = buildHttpRequest("POST", path, body, configuration);
return new HttpRequestAttempt<>(request, "check", CheckResponse.class, apiClient, configuration)
return new HttpRequestAttempt<>(request, "check", CheckResponse.class, apiClient, configuration, this.telemetry)
.addTelemetryAttributes(telemetryAttributes)
.attemptHttpRequest();
} catch (ApiException e) {
Expand Down Expand Up @@ -251,7 +251,7 @@ private CompletableFuture<ApiResponse<CreateStoreResponse>> createStore(

try {
HttpRequest request = buildHttpRequest("POST", path, body, configuration);
return new HttpRequestAttempt<>(request, "createStore", CreateStoreResponse.class, apiClient, configuration)
return new HttpRequestAttempt<>(request, "createStore", CreateStoreResponse.class, apiClient, configuration, this.telemetry)
.addTelemetryAttributes(telemetryAttributes)
.attemptHttpRequest();
} catch (ApiException e) {
Expand Down Expand Up @@ -300,7 +300,7 @@ private CompletableFuture<ApiResponse<Void>> deleteStore(String storeId, Configu

try {
HttpRequest request = buildHttpRequest("DELETE", path, configuration);
return new HttpRequestAttempt<>(request, "deleteStore", Void.class, apiClient, configuration)
return new HttpRequestAttempt<>(request, "deleteStore", Void.class, apiClient, configuration, this.telemetry)
.addTelemetryAttributes(telemetryAttributes)
.attemptHttpRequest();
} catch (ApiException e) {
Expand Down Expand Up @@ -356,7 +356,7 @@ private CompletableFuture<ApiResponse<ExpandResponse>> expand(

try {
HttpRequest request = buildHttpRequest("POST", path, body, configuration);
return new HttpRequestAttempt<>(request, "expand", ExpandResponse.class, apiClient, configuration)
return new HttpRequestAttempt<>(request, "expand", ExpandResponse.class, apiClient, configuration, this.telemetry)
.addTelemetryAttributes(telemetryAttributes)
.attemptHttpRequest();
} catch (ApiException e) {
Expand Down Expand Up @@ -406,7 +406,7 @@ private CompletableFuture<ApiResponse<GetStoreResponse>> getStore(String storeId

try {
HttpRequest request = buildHttpRequest("GET", path, configuration);
return new HttpRequestAttempt<>(request, "getStore", GetStoreResponse.class, apiClient, configuration)
return new HttpRequestAttempt<>(request, "getStore", GetStoreResponse.class, apiClient, configuration, this.telemetry)
.addTelemetryAttributes(telemetryAttributes)
.attemptHttpRequest();
} catch (ApiException e) {
Expand Down Expand Up @@ -462,7 +462,7 @@ private CompletableFuture<ApiResponse<ListObjectsResponse>> listObjects(

try {
HttpRequest request = buildHttpRequest("POST", path, body, configuration);
return new HttpRequestAttempt<>(request, "listObjects", ListObjectsResponse.class, apiClient, configuration)
return new HttpRequestAttempt<>(request, "listObjects", ListObjectsResponse.class, apiClient, configuration, this.telemetry)
.addTelemetryAttributes(telemetryAttributes)
.attemptHttpRequest();
} catch (ApiException e) {
Expand Down Expand Up @@ -513,7 +513,7 @@ private CompletableFuture<ApiResponse<ListStoresResponse>> listStores(

try {
HttpRequest request = buildHttpRequest("GET", path, configuration);
return new HttpRequestAttempt<>(request, "listStores", ListStoresResponse.class, apiClient, configuration)
return new HttpRequestAttempt<>(request, "listStores", ListStoresResponse.class, apiClient, configuration, this.telemetry)
.addTelemetryAttributes(telemetryAttributes)
.attemptHttpRequest();
} catch (ApiException e) {
Expand Down Expand Up @@ -569,7 +569,7 @@ private CompletableFuture<ApiResponse<ListUsersResponse>> listUsers(

try {
HttpRequest request = buildHttpRequest("POST", path, body, configuration);
return new HttpRequestAttempt<>(request, "listUsers", ListUsersResponse.class, apiClient, configuration)
return new HttpRequestAttempt<>(request, "listUsers", ListUsersResponse.class, apiClient, configuration, this.telemetry)
.addTelemetryAttributes(telemetryAttributes)
.attemptHttpRequest();
} catch (ApiException e) {
Expand Down Expand Up @@ -625,7 +625,7 @@ private CompletableFuture<ApiResponse<ReadResponse>> read(

try {
HttpRequest request = buildHttpRequest("POST", path, body, configuration);
return new HttpRequestAttempt<>(request, "read", ReadResponse.class, apiClient, configuration)
return new HttpRequestAttempt<>(request, "read", ReadResponse.class, apiClient, configuration, this.telemetry)
.addTelemetryAttributes(telemetryAttributes)
.attemptHttpRequest();
} catch (ApiException e) {
Expand Down Expand Up @@ -684,7 +684,7 @@ private CompletableFuture<ApiResponse<ReadAssertionsResponse>> readAssertions(
try {
HttpRequest request = buildHttpRequest("GET", path, configuration);
return new HttpRequestAttempt<>(
request, "readAssertions", ReadAssertionsResponse.class, apiClient, configuration)
request, "readAssertions", ReadAssertionsResponse.class, apiClient, configuration, this.telemetry)
.addTelemetryAttributes(telemetryAttributes)
.attemptHttpRequest();
} catch (ApiException e) {
Expand Down Expand Up @@ -746,7 +746,8 @@ private CompletableFuture<ApiResponse<ReadAuthorizationModelResponse>> readAutho
"readAuthorizationModel",
ReadAuthorizationModelResponse.class,
apiClient,
configuration)
configuration,
this.telemetry)
.addTelemetryAttributes(telemetryAttributes)
.attemptHttpRequest();
} catch (ApiException e) {
Expand Down Expand Up @@ -810,7 +811,8 @@ private CompletableFuture<ApiResponse<ReadAuthorizationModelsResponse>> readAuth
"readAuthorizationModels",
ReadAuthorizationModelsResponse.class,
apiClient,
configuration)
configuration,
this.telemetry)
.addTelemetryAttributes(telemetryAttributes)
.attemptHttpRequest();
} catch (ApiException e) {
Expand Down Expand Up @@ -896,7 +898,7 @@ private CompletableFuture<ApiResponse<ReadChangesResponse>> readChanges(

try {
HttpRequest request = buildHttpRequest("GET", path, configuration);
return new HttpRequestAttempt<>(request, "readChanges", ReadChangesResponse.class, apiClient, configuration)
return new HttpRequestAttempt<>(request, "readChanges", ReadChangesResponse.class, apiClient, configuration, this.telemetry)
.addTelemetryAttributes(telemetryAttributes)
.attemptHttpRequest();
} catch (ApiException e) {
Expand Down Expand Up @@ -951,7 +953,7 @@ private CompletableFuture<ApiResponse<Object>> write(String storeId, WriteReques

try {
HttpRequest request = buildHttpRequest("POST", path, body, configuration);
return new HttpRequestAttempt<>(request, "write", Object.class, apiClient, configuration)
return new HttpRequestAttempt<>(request, "write", Object.class, apiClient, configuration, this.telemetry)
.addTelemetryAttributes(telemetryAttributes)
.attemptHttpRequest();
} catch (ApiException e) {
Expand Down Expand Up @@ -1018,7 +1020,7 @@ private CompletableFuture<ApiResponse<Void>> writeAssertions(

try {
HttpRequest request = buildHttpRequest("PUT", path, body, configuration);
return new HttpRequestAttempt<>(request, "writeAssertions", Void.class, apiClient, configuration)
return new HttpRequestAttempt<>(request, "writeAssertions", Void.class, apiClient, configuration, this.telemetry)
.addTelemetryAttributes(telemetryAttributes)
.attemptHttpRequest();
} catch (ApiException e) {
Expand Down Expand Up @@ -1080,7 +1082,8 @@ private CompletableFuture<ApiResponse<WriteAuthorizationModelResponse>> writeAut
"writeAuthorizationModel",
WriteAuthorizationModelResponse.class,
apiClient,
configuration)
configuration,
this.telemetry)
.addTelemetryAttributes(telemetryAttributes)
.attemptHttpRequest();
} catch (ApiException e) {
Expand Down
15 changes: 13 additions & 2 deletions src/main/java/dev/openfga/sdk/api/auth/OAuth2Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ public class OAuth2Client {
* @param configuration Configuration, including credentials, that can be used to retrieve an access tokens
*/
public OAuth2Client(Configuration configuration, ApiClient apiClient) throws FgaInvalidParameterException {
this(configuration, apiClient, configuration.getTelemetry());
}

/**
* Initializes a new instance of the {@link OAuth2Client} class
*
* @param configuration Configuration, including credentials, that can be used to retrieve an access tokens
* @param apiClient The API client to use for HTTP requests
* @param telemetry The shared telemetry instance to use
*/
public OAuth2Client(Configuration configuration, ApiClient apiClient, Telemetry telemetry) throws FgaInvalidParameterException {
var clientCredentials = configuration.getCredentials().getClientCredentials();

this.apiClient = apiClient;
Expand All @@ -53,7 +64,7 @@ public OAuth2Client(Configuration configuration, ApiClient apiClient) throws Fga
.maxRetries(configuration.getMaxRetries())
.minimumRetryDelay(configuration.getMinimumRetryDelay())
.telemetryConfiguration(configuration.getTelemetryConfiguration());
this.telemetry = new Telemetry(this.config);
this.telemetry = telemetry;
}

/**
Expand Down Expand Up @@ -90,7 +101,7 @@ private CompletableFuture<CredentialsFlowResponse> exchangeToken()
ApiClient.formRequestBuilder("POST", "", this.authRequest.buildFormRequestBody(), config);
HttpRequest request = requestBuilder.build();

return new HttpRequestAttempt<>(request, "exchangeToken", CredentialsFlowResponse.class, apiClient, config)
return new HttpRequestAttempt<>(request, "exchangeToken", CredentialsFlowResponse.class, apiClient, config, telemetry)
.attemptHttpRequest()
.thenApply(ApiResponse::getData);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,20 @@ public HttpRequestAttempt(
this.name = name;
this.request = request;
this.clazz = clazz;
this.telemetry = new Telemetry(configuration);
this.telemetry = configuration.getTelemetry();
this.telemetryAttributes = new HashMap<>();
}

public HttpRequestAttempt(
HttpRequest request, String name, Class<T> clazz, ApiClient apiClient, Configuration configuration, Telemetry telemetry)
throws FgaInvalidParameterException {
assertParamExists(configuration.getMaxRetries(), "maxRetries", "Configuration");
this.apiClient = apiClient;
this.configuration = configuration;
this.name = name;
this.request = request;
this.clazz = clazz;
this.telemetry = telemetry;
this.telemetryAttributes = new HashMap<>();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import static dev.openfga.sdk.util.Validation.assertParamExists;

import dev.openfga.sdk.errors.FgaInvalidParameterException;
import dev.openfga.sdk.telemetry.Telemetry;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
Expand Down Expand Up @@ -54,6 +55,7 @@ public class Configuration implements BaseConfiguration {
private Duration minimumRetryDelay;
private Map<String, String> defaultHeaders;
private TelemetryConfiguration telemetryConfiguration;
private Telemetry telemetry;

public Configuration() {
this.apiUrl = DEFAULT_API_URL;
Expand Down Expand Up @@ -139,6 +141,9 @@ public Configuration override(ConfigurationOverride configurationOverride) {
result.telemetryConfiguration =
overrideTelemetryConfiguration != null ? overrideTelemetryConfiguration : telemetryConfiguration;

// Don't copy the telemetry instance - let it be lazily initialized for the new configuration
result.telemetry = null;

return result;
}

Expand Down Expand Up @@ -339,4 +344,17 @@ public Configuration telemetryConfiguration(TelemetryConfiguration telemetryConf
this.telemetryConfiguration = telemetryConfiguration;
return this;
}

/**
* Get the shared Telemetry instance for this configuration.
* Creates the instance lazily if it hasn't been created yet.
*
* @return The shared Telemetry instance.
*/
public Telemetry getTelemetry() {
if (telemetry == null) {
telemetry = new Telemetry(this);
}
return telemetry;
}
}
2 changes: 2 additions & 0 deletions src/test/java/dev/openfga/sdk/api/OpenFgaApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import dev.openfga.sdk.api.configuration.*;
import dev.openfga.sdk.api.model.*;
import dev.openfga.sdk.errors.*;
import dev.openfga.sdk.telemetry.Telemetry;
import java.net.http.HttpClient;
import java.time.Duration;
import java.time.OffsetDateTime;
Expand Down Expand Up @@ -74,6 +75,7 @@ public void beforeEachTest() throws Exception {
when(mockConfiguration.getMaxRetries()).thenReturn(DEFAULT_MAX_RETRIES);
when(mockConfiguration.getMinimumRetryDelay()).thenReturn(DEFAULT_RETRY_DELAY);
when(mockConfiguration.getTelemetryConfiguration()).thenReturn(DEFAULT_TELEMETRY_CONFIG);
when(mockConfiguration.getTelemetry()).thenReturn(new Telemetry(mockConfiguration));

mockApiClient = mock(ApiClient.class);
when(mockApiClient.getObjectMapper()).thenReturn(mapper);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* OpenFGA
* A high performance and flexible authorization/permission engine built for developers and inspired by Google Zanzibar.
*
* The version of the OpenAPI document: 1.x
* Contact: [email protected]
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/

package dev.openfga.sdk.api.configuration;

import static org.assertj.core.api.Assertions.assertThat;

import dev.openfga.sdk.telemetry.Telemetry;
import org.junit.jupiter.api.Test;

class ConfigurationTelemetryTest {
@Test
void shouldReturnSameTelemetryInstanceOnMultipleCalls() {
// given
Configuration configuration = new Configuration();

// when
Telemetry firstCall = configuration.getTelemetry();
Telemetry secondCall = configuration.getTelemetry();

// then
assertThat(firstCall).isNotNull().isSameAs(secondCall);
}

@Test
void shouldCreateNewTelemetryInstanceForNewConfiguration() {
// given
Configuration config1 = new Configuration();
Configuration config2 = new Configuration();

// when
Telemetry telemetry1 = config1.getTelemetry();
Telemetry telemetry2 = config2.getTelemetry();

// then
assertThat(telemetry1).isNotNull();
assertThat(telemetry2).isNotNull();
assertThat(telemetry1).isNotSameAs(telemetry2);
}

@Test
void shouldNotCopyTelemetryInstanceWhenUsingOverride() {
// given
Configuration originalConfig = new Configuration();
Telemetry originalTelemetry = originalConfig.getTelemetry(); // Initialize telemetry

ConfigurationOverride override = new ConfigurationOverride();

// when
Configuration overriddenConfig = originalConfig.override(override);
Telemetry overriddenTelemetry = overriddenConfig.getTelemetry();

// then
assertThat(originalTelemetry).isNotNull();
assertThat(overriddenTelemetry).isNotNull();
assertThat(originalTelemetry).isNotSameAs(overriddenTelemetry);
}
}
Loading