Skip to content

Commit

Permalink
Fixed RD-15380: Strip HTTP headers from errors + use proper SDK excep…
Browse files Browse the repository at this point in the history
…tions
  • Loading branch information
bgaidioz committed Mar 3, 2025
1 parent 07435a9 commit a64ff26
Show file tree
Hide file tree
Showing 28 changed files with 224 additions and 150 deletions.
2 changes: 1 addition & 1 deletion das-jira-connector/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
<maven-shade-plugin-version>3.4.1</maven-shade-plugin-version>

<!-- RAW Labs Dependencies -->
<das-server-scala-version>0.3.0</das-server-scala-version>
<das-server-scala-version>0.4.1</das-server-scala-version>
<jira-rest-client-version>1.0-SNAPSHOT</jira-rest-client-version>
<protocol-das.version>1.0.0</protocol-das.version>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.rawlabs.das.jira;

/**
* DASJiraApiException wraps an ApiException (from either the platform or software API) to propagate
* only the response body as the error message. The original ApiException message often includes
* extensive HTTP header details that are not useful for DAS clients.
*/
public class DASJiraUnexpectedError extends RuntimeException {

public DASJiraUnexpectedError(Throwable t) {
super("unexpected error", t);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@

import com.rawlabs.das.jira.initializer.auth.DASJiraAuthStrategy;
import com.rawlabs.das.jira.initializer.auth.DASJiraAuthStrategyFactory;
import com.rawlabs.das.sdk.DASSdkInvalidArgumentException;

import java.util.Map;

public class DASJiraInitializer {

public static com.rawlabs.das.jira.rest.platform.ApiClient initializePlatform(
Map<String, String> options) {
if (options.get("base_url") == null) {
throw new IllegalArgumentException("base_url is required");
throw new DASSdkInvalidArgumentException("base_url is required");
}
com.rawlabs.das.jira.rest.platform.ApiClient apiClient =
new com.rawlabs.das.jira.rest.platform.ApiClient();
Expand All @@ -22,7 +24,7 @@ public static com.rawlabs.das.jira.rest.platform.ApiClient initializePlatform(
public static com.rawlabs.das.jira.rest.software.ApiClient initializeSoftware(
Map<String, String> options) {
if (options.get("base_url") == null) {
throw new IllegalArgumentException("base_url is required");
throw new DASSdkInvalidArgumentException("base_url is required");
}
com.rawlabs.das.jira.rest.software.ApiClient apiClient =
new com.rawlabs.das.jira.rest.software.ApiClient();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.rawlabs.das.jira.initializer.auth;

import com.rawlabs.das.sdk.DASSdkInvalidArgumentException;

import java.util.Map;

public class DASJiraAuthStrategyFactory {
public static DASJiraAuthStrategy createAuthStrategy(Map<String, String> options) {
if (isBearerAuth(options)) return new DASJiraOAuth2AuthStrategy();
else if (isBasicAuth(options)) return new DasJiraBasicAuthStrategy();
else throw new IllegalArgumentException("Invalid authentication options");
else throw new DASSdkInvalidArgumentException("Invalid authentication option");
}

private static boolean isBearerAuth(Map<String, String> options) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.rawlabs.das.jira.tables;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.rawlabs.das.sdk.DASSdkException;
import com.rawlabs.das.jira.DASJiraUnexpectedError;
import com.rawlabs.protocol.das.v1.tables.Row;
import java.util.*;

Expand Down Expand Up @@ -83,7 +83,7 @@ protected void processFields(
// 'description' is an object, so we need to serialize it. It's then sent as 'any'.
addToRow("description", rowBuilder, objectMapper.writeValueAsString(description), columns);
} catch (JsonProcessingException e) {
throw new DASSdkException("error processing 'description'", e);
throw new DASJiraUnexpectedError(e);
}

addToRow(
Expand Down Expand Up @@ -147,7 +147,7 @@ protected void processFields(
try {
addToRow("fields", rowBuilder, objectMapper.writeValueAsString(fields), columns);
} catch (JsonProcessingException e) {
throw new DASSdkException(e.getMessage());
throw new DASJiraUnexpectedError(e);
}

addToRow(
Expand All @@ -173,7 +173,7 @@ protected void processFields(
try {
addToRow("tags", rowBuilder, objectMapper.writeValueAsString(tags), columns);
} catch (JsonProcessingException e) {
throw new DASSdkException(e.getMessage());
throw new DASJiraUnexpectedError(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package com.rawlabs.das.jira.tables;

import static com.rawlabs.das.jira.utils.factory.qual.ExtractQualFactory.extractEqDistinct;
import static com.rawlabs.das.jira.utils.factory.qual.QualFactory.createEq;
import static com.rawlabs.das.jira.utils.factory.table.TableFactory.createTable;
import static com.rawlabs.das.jira.utils.factory.type.TypeFactory.createLongType;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.rawlabs.das.jira.rest.platform.ApiException;
import com.rawlabs.das.jira.utils.factory.value.*;
import com.rawlabs.das.sdk.DASExecuteResult;
import com.rawlabs.das.sdk.DASSdkException;
import com.rawlabs.das.sdk.DASTable;
import com.rawlabs.das.sdk.*;
import com.rawlabs.protocol.das.v1.query.Qual;
import com.rawlabs.protocol.das.v1.query.SortKey;
import com.rawlabs.protocol.das.v1.tables.Column;
Expand All @@ -15,11 +19,6 @@
import java.time.format.DateTimeFormatter;
import java.util.*;

import static com.rawlabs.das.jira.utils.factory.qual.ExtractQualFactory.extractEqDistinct;
import static com.rawlabs.das.jira.utils.factory.qual.QualFactory.createEq;
import static com.rawlabs.das.jira.utils.factory.table.TableFactory.createTable;
import static com.rawlabs.das.jira.utils.factory.type.TypeFactory.createLongType;

public abstract class DASJiraTable implements DASTable {

protected static final String TITLE_DESC = "Title of the resource.";
Expand Down Expand Up @@ -98,7 +97,7 @@ public String withOrderBy(List<SortKey> sortKeys) {
return null;
}
if (sortKeys.size() > 1) {
throw new DASSdkException("Only one sort key is allowed.");
throw new DASSdkInvalidArgumentException("Only one sort key is allowed.");
}
SortKey key = sortKeys.getFirst();
return (key.getIsReversed() ? "-" : "+") + key.getName().replace("title", "name");
Expand All @@ -125,4 +124,43 @@ public OffsetDateTime getDateTime(String dateString) {
// Parse the string to an OffsetDateTime object
return OffsetDateTime.parse(dateString, formatter);
}

// A helper to fallback to a default message if ever the body would be empty or null.
private static String mkMessage(String msg) {
if (msg == null || msg.trim().isEmpty()) {
return "Unknown JIRA API error";
}
return msg;
}

/**
* Helper method to create a DASSdk RuntimeException from an ApiException.
*
* <p>When the SDK exposes more exceptions (e.g. authentication errors), this method can
* investigate the ApiException and create the appropriate SDK exception.
*
* @param e the ApiException to convert
* @return the RuntimeException to throw
*/
protected RuntimeException makeSdkException(ApiException e) {
return switch (e.getCode()) {
case 400 -> new DASSdkInvalidArgumentException(mkMessage(e.getResponseBody()), e);
case 401 -> new DASSdkUnauthenticatedException("unauthorized", e);
case 403 -> new DASSdkPermissionDeniedException("permission denied", e);
default ->
// For now, just create a generic SDK exception with the body of the ApiException
new DASSdkInvalidArgumentException(mkMessage(e.getResponseBody()), e);
};
}

protected RuntimeException makeSdkException(com.rawlabs.das.jira.rest.software.ApiException e) {
return switch (e.getCode()) {
case 400 -> new DASSdkInvalidArgumentException(mkMessage(e.getResponseBody()), e);
case 401 -> new DASSdkUnauthenticatedException("unauthorized", e);
case 403 -> new DASSdkPermissionDeniedException("permission denied", e);
default ->
// For now, just create a generic SDK exception with the body of the ApiException
new DASSdkInvalidArgumentException(mkMessage(e.getResponseBody()), e);
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import com.rawlabs.das.jira.rest.platform.model.SimpleApplicationPropertyBean;
import com.rawlabs.das.jira.tables.*;
import com.rawlabs.das.sdk.DASExecuteResult;
import com.rawlabs.das.sdk.DASSdkException;
import com.rawlabs.protocol.das.v1.query.PathKey;
import com.rawlabs.protocol.das.v1.query.Qual;
import com.rawlabs.protocol.das.v1.query.SortKey;
Expand Down Expand Up @@ -58,7 +57,7 @@ public Row update(Value rowId, Row newValues) {
jiraSettingsApi.setApplicationProperty(id, applicationPropertyBean);
return toRow(applicationProperty, List.of());
} catch (ApiException e) {
throw new DASSdkException(e.getMessage(), e);
throw makeSdkException(e);
}
}

Expand Down Expand Up @@ -89,8 +88,7 @@ public Row next() {
}
};
} catch (ApiException e) {
throw new DASSdkException(
"Failed to fetch advanced settings: %s".formatted(e.getResponseBody()), e);
throw makeSdkException(e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import com.rawlabs.das.jira.tables.results.DASJiraPaginatedResult;
import com.rawlabs.das.jira.tables.results.DASJiraWithParentTableResult;
import com.rawlabs.das.sdk.DASExecuteResult;
import com.rawlabs.das.sdk.DASSdkException;
import com.rawlabs.das.sdk.DASSdkInvalidArgumentException;
import com.rawlabs.protocol.das.v1.query.Qual;
import com.rawlabs.protocol.das.v1.query.SortKey;
import com.rawlabs.protocol.das.v1.tables.Column;
Expand Down Expand Up @@ -50,7 +50,7 @@ public Row update(Value rowId, Row newValues) {
String issueId = (String) extractValueFactory.extractValue(rowId);

if (boardId == null || issueId == null) {
throw new DASSdkException("The only update operation allowed is moving issues to backlog.");
throw new DASSdkInvalidArgumentException("The only update operation allowed is moving issues to backlog.");
}

MoveIssuesToBacklogForBoardRequest moveIssuesToBacklogForBoardRequest =
Expand All @@ -59,7 +59,7 @@ public Row update(Value rowId, Row newValues) {
try {
boardApi.moveIssuesToBoard(boardId, moveIssuesToBacklogForBoardRequest);
} catch (ApiException e) {
throw new DASSdkException(e.getMessage(), e);
throw makeSdkException(e);
}
return newValues.toBuilder()
.addColumns(
Expand Down Expand Up @@ -103,7 +103,7 @@ public DASJiraPage<IssueBean> fetchPage(long offset) {
Long.valueOf(Objects.requireNonNullElse(searchResults.getTotal(), 0)),
searchResults.getNames());
} catch (ApiException e) {
throw new DASSdkException(e.getMessage(), e);
throw makeSdkException(e);
}
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import com.rawlabs.das.jira.tables.results.DASJiraPage;
import com.rawlabs.das.jira.tables.results.DASJiraPaginatedResult;
import com.rawlabs.das.sdk.DASExecuteResult;
import com.rawlabs.das.sdk.DASSdkException;
import com.rawlabs.protocol.das.v1.query.PathKey;
import com.rawlabs.protocol.das.v1.query.Qual;
import com.rawlabs.protocol.das.v1.query.SortKey;
Expand Down Expand Up @@ -62,15 +61,15 @@ public Row insert(Row row) {
GetAllBoards200ResponseValuesInner result = boardApi.createBoard(board);
return toRow(result, null);
} catch (ApiException e) {
throw new DASSdkException(e.getMessage(), e);
throw makeSdkException(e);
}
}

public void delete(Value rowId) {
try {
boardApi.deleteBoard((Long) extractValueFactory.extractValue(rowId));
} catch (ApiException e) {
throw new DASSdkException(e.getMessage(), e);
throw makeSdkException(e);
}
}

Expand Down Expand Up @@ -120,8 +119,7 @@ public DASJiraPage<GetAllBoards200ResponseValuesInner> fetchPage(long offset) {
return new DASJiraPage<>(
getAllBoards200ResponsePage.getValues(), getAllBoards200ResponsePage.getTotal());
} catch (ApiException e) {
throw new DASSdkException(
"Failed to fetch boards: %s".formatted(e.getResponseBody()));
throw makeSdkException(e);
}
}

Expand All @@ -131,33 +129,29 @@ public Row next() {
try {
return toRow(next, columns);
} catch (ApiException e) {
throw new DASSdkException("Failed to fetch board configuration", e);
throw makeSdkException(e);
}
}
};
}
} catch (ApiException e) {
throw new DASSdkException("Failed to fetch advanced settings", e);
throw makeSdkException(e);
}
}

private Row toRow(
GetAllBoards200ResponseValuesInner getAllBoards200ResponseValuesInner, List<String> columns)
throws ApiException {
try {
Row row = getBoardsRow(getAllBoards200ResponseValuesInner, columns);
if (columns == null
|| columns.isEmpty()
|| columns.contains("filter_id")
|| columns.contains("sub_query")) {
GetConfiguration200Response config =
boardApi.getConfiguration(getAllBoards200ResponseValuesInner.getId());
row = addConfigToRow(row, config, columns);
}
return row;
} catch (ApiException e) {
throw new DASSdkException("Failed to fetch board configuration", e);
Row row = getBoardsRow(getAllBoards200ResponseValuesInner, columns);
if (columns == null
|| columns.isEmpty()
|| columns.contains("filter_id")
|| columns.contains("sub_query")) {
GetConfiguration200Response config =
boardApi.getConfiguration(getAllBoards200ResponseValuesInner.getId());
row = addConfigToRow(row, config, columns);
}
return row;
}

private Row getBoardsRow(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import com.rawlabs.das.jira.tables.results.DASJiraPaginatedResult;
import com.rawlabs.das.jira.tables.results.DASJiraWithParentTableResult;
import com.rawlabs.das.sdk.DASExecuteResult;
import com.rawlabs.das.sdk.DASSdkException;
import com.rawlabs.protocol.das.v1.query.PathKey;
import com.rawlabs.protocol.das.v1.query.Qual;
import com.rawlabs.protocol.das.v1.query.SortKey;
Expand Down Expand Up @@ -80,7 +79,7 @@ public Row insert(Row row) {
this.projectComponentsApi.createComponent(createProjectComponent(row));
return toRow(toComponentWithCount(inserted), List.of());
} catch (ApiException e) {
throw new DASSdkException(e.getMessage());
throw makeSdkException(e);
}
}

Expand All @@ -93,7 +92,7 @@ public Row update(Value rowId, Row newValues) {
projectComponentsApi.updateComponent(
(String) extractValueFactory.extractValue(rowId), createProjectComponent(newValues));
} catch (ApiException e) {
throw new RuntimeException(e);
throw makeSdkException(e);
}
return super.update(rowId, newValues);
}
Expand All @@ -102,7 +101,7 @@ public void delete(Value rowId) {
try {
projectComponentsApi.deleteComponent((String) extractValueFactory.extractValue(rowId), null);
} catch (ApiException e) {
throw new RuntimeException(e);
throw makeSdkException(e);
}
}

Expand Down Expand Up @@ -133,8 +132,7 @@ public DASJiraPage<ComponentWithIssueCount> fetchPage(long offset) {
null);
return new DASJiraPage<>(components.getValues(), components.getTotal());
} catch (ApiException e) {
throw new DASSdkException(
"Error fetching components: %s".formatted(e.getResponseBody()), e);
throw makeSdkException(e);
}
}
};
Expand Down Expand Up @@ -259,7 +257,7 @@ public ComponentWithIssueCount toComponentWithCount(ProjectComponent projectComp
realAssigneeType,
projectComponent.getSelf());
} catch (ApiException e) {
throw new DASSdkException(e.getMessage(), e);
throw makeSdkException(e);
}
}

Expand Down
Loading

0 comments on commit a64ff26

Please sign in to comment.