Skip to content

Commit 3edf7c1

Browse files
committed
fix: make backend URL validation lazy so generateSbom works without it
ExhortApi constructor no longer validates TRUSTIFY_DA_BACKEND_URL eagerly. The endpoint is resolved lazily on first use (when an HTTP call is about to be made). This allows generateSbom() and the CLI 'sbom' command to operate without a backend URL configured, since they only perform local SBOM generation. Implements TC-3991 Assisted-by: Claude Code
1 parent c03a8e1 commit 3edf7c1

2 files changed

Lines changed: 19 additions & 11 deletions

File tree

src/main/java/io/github/guacsec/trustifyda/impl/ExhortApi.java

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,10 @@ public final class ExhortApi implements Api {
8383
public static final String S_API_V5_LICENSES_IDENTIFY = "%s/api/v5/licenses/identify";
8484
private static final String TRUSTIFY_DA_LICENSE_CHECK = "TRUSTIFY_DA_LICENSE_CHECK";
8585

86-
private final String endpoint;
86+
private String endpoint;
8787

8888
public String getEndpoint() {
89-
return endpoint;
89+
return getOrResolveEndpoint();
9090
}
9191

9292
private final HttpClient client;
@@ -117,7 +117,6 @@ static HttpClient.Version getHttpVersion() {
117117
commonHookBeginning(true);
118118
this.client = client;
119119
this.mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
120-
this.endpoint = getExhortUrl();
121120
}
122121

123122
public static HttpClient createHttpClient() {
@@ -163,6 +162,13 @@ private static String getClientRequestId() {
163162
return RequestManager.getInstance().getTraceIdOfRequest();
164163
}
165164

165+
private String getOrResolveEndpoint() {
166+
if (this.endpoint == null) {
167+
this.endpoint = getExhortUrl();
168+
}
169+
return this.endpoint;
170+
}
171+
166172
private String getExhortUrl() {
167173
String endpoint = Environment.get(TRUSTIFY_DA_BACKEND_URL);
168174
if (endpoint == null || endpoint.trim().isEmpty()) {
@@ -346,7 +352,7 @@ public CompletableFuture<AnalysisReport> componentAnalysis(
346352
String exClientTraceId = commonHookBeginning(false);
347353
var manifestPath = Path.of(manifest);
348354
var provider = Ecosystem.getProvider(manifestPath);
349-
var uri = URI.create(String.format(S_API_V_5_ANALYSIS, this.endpoint));
355+
var uri = URI.create(String.format(S_API_V_5_ANALYSIS, getOrResolveEndpoint()));
350356
var content = provider.provideComponent();
351357
commonHookAfterProviderCreatedSbomAndBeforeExhort();
352358
return getAnalysisReportForComponent(uri, content, exClientTraceId);
@@ -390,7 +396,7 @@ public CompletableFuture<AnalysisReport> componentAnalysis(String manifestFile)
390396
String exClientTraceId = commonHookBeginning(false);
391397
var manifestPath = Path.of(manifestFile);
392398
var provider = Ecosystem.getProvider(manifestPath);
393-
var uri = URI.create(String.format(S_API_V_5_ANALYSIS, this.endpoint));
399+
var uri = URI.create(String.format(S_API_V_5_ANALYSIS, getOrResolveEndpoint()));
394400
var content = provider.provideComponent();
395401
commonHookAfterProviderCreatedSbomAndBeforeExhort();
396402
return getAnalysisReportForComponent(uri, content, exClientTraceId);
@@ -431,7 +437,7 @@ private HttpRequest buildStackRequest(final String manifestFile, final MediaType
431437
throws IOException {
432438
var manifestPath = Path.of(manifestFile);
433439
var provider = Ecosystem.getProvider(manifestPath);
434-
var uri = URI.create(String.format(S_API_V_5_ANALYSIS, this.endpoint));
440+
var uri = URI.create(String.format(S_API_V_5_ANALYSIS, getOrResolveEndpoint()));
435441
var content = provider.provideStack();
436442
commonHookAfterProviderCreatedSbomAndBeforeExhort();
437443

@@ -518,7 +524,7 @@ <H, T> CompletableFuture<T> performBatchAnalysis(
518524
final String analysisName)
519525
throws IOException {
520526
String exClientTraceId = commonHookBeginning(false);
521-
var uri = URI.create(String.format(S_API_V_5_BATCH_ANALYSIS, this.endpoint));
527+
var uri = URI.create(String.format(S_API_V_5_BATCH_ANALYSIS, getOrResolveEndpoint()));
522528
var sboms = sbomsGenerator.get();
523529
var content =
524530
new Provider.Content(
@@ -580,7 +586,7 @@ public CompletableFuture<ComponentAnalysisResult> componentAnalysisWithLicense(
580586
String exClientTraceId = commonHookBeginning(false);
581587
var manifestPath = Path.of(manifestFile);
582588
var provider = Ecosystem.getProvider(manifestPath);
583-
var uri = URI.create(String.format(S_API_V_5_ANALYSIS, this.endpoint));
589+
var uri = URI.create(String.format(S_API_V_5_ANALYSIS, getOrResolveEndpoint()));
584590
var content = provider.provideComponent();
585591
String sbomJson = new String(content.buffer);
586592
commonHookAfterProviderCreatedSbomAndBeforeExhort();
@@ -611,7 +617,7 @@ public CompletableFuture<ComponentAnalysisResult> componentAnalysisWithLicense(
611617
*/
612618
public CompletableFuture<JsonNode> getLicenseDetails(String spdxId) {
613619
String encodedId = URLEncoder.encode(spdxId, StandardCharsets.UTF_8).replace("+", "%20");
614-
URI uri = URI.create(String.format(S_API_V5_LICENSES, this.endpoint, encodedId));
620+
URI uri = URI.create(String.format(S_API_V5_LICENSES, getOrResolveEndpoint(), encodedId));
615621
HttpRequest request = buildGetRequest(uri, "License Details");
616622

617623
return this.client
@@ -657,7 +663,7 @@ public CompletableFuture<String> identifyLicense(Path licenseFilePath) {
657663
LOG.warning(String.format("Failed to read license file: %s", e.getMessage()));
658664
return CompletableFuture.completedFuture(null);
659665
}
660-
URI uri = URI.create(String.format(S_API_V5_LICENSES_IDENTIFY, this.endpoint));
666+
URI uri = URI.create(String.format(S_API_V5_LICENSES_IDENTIFY, getOrResolveEndpoint()));
661667
HttpRequest request =
662668
buildPostRequest(
663669
uri,

src/test/java/io/github/guacsec/trustifyda/impl/Exhort_Api_Test.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,9 +413,11 @@ void componentAnalysis_with_pom_xml_as_path_should_return_json_object_from_the_b
413413
@Test
414414
@ClearSystemProperty(key = "TRUSTIFY_DA_BACKEND_URL")
415415
void check_TRUSTIFY_DA_Url_Throws_Exception_When_Not_Set() {
416+
// Backend URL validation is lazy — construction succeeds, but accessing the endpoint throws
417+
ExhortApi api = new ExhortApi();
416418
IllegalStateException exception =
417419
org.junit.jupiter.api.Assertions.assertThrows(
418-
IllegalStateException.class, () -> new ExhortApi());
420+
IllegalStateException.class, api::getEndpoint);
419421
then(exception.getMessage())
420422
.isEqualTo(
421423
"Backend URL not configured. Please set the TRUSTIFY_DA_BACKEND_URL environment"

0 commit comments

Comments
 (0)