diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/AbstractErrorMapper.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/AbstractErrorMapper.java index 5633a5c83..1578d930b 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/AbstractErrorMapper.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/AbstractErrorMapper.java @@ -28,14 +28,14 @@ public DatabricksError apply(Response resp, ApiErrorBody errorBody) { "Overriding error with {} (original status code: {}, original error code: {})", override.getDebugName(), resp.getStatusCode(), - errorBody.getErrorCode()); + errorBody.errorCode()); return override.makeError(errorBody); } } int code = resp.getStatusCode(); - String message = errorBody.getMessage(); - String errorCode = errorBody.getErrorCode(); - List details = errorBody.getErrorDetails(); + String message = errorBody.message(); + String errorCode = errorBody.errorCode(); + List details = errorBody.getErrorDetailsList(); if (errorCodeMapping.containsKey(errorCode)) { return errorCodeMapping.get(errorCode).create(message, details); } diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/ApiErrorBody.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/ApiErrorBody.java index f622430dc..d772232a2 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/ApiErrorBody.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/ApiErrorBody.java @@ -1,131 +1,178 @@ package com.databricks.sdk.core.error; import com.databricks.sdk.core.error.details.ErrorDetails; +import com.databricks.sdk.core.error.details.ErrorInfo; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.Arrays; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.google.auto.value.AutoValue; import java.util.Collections; import java.util.List; +import javax.annotation.Nullable; /** * The union of all JSON error responses from the Databricks APIs, not including HTML responses. * *

Unknown properties in the response should be ignored. */ +@AutoValue +@JsonDeserialize(builder = AutoValue_ApiErrorBody.Builder.class) @JsonIgnoreProperties(ignoreUnknown = true) -public class ApiErrorBody { - private String errorCode; - private String message; - private String scimDetail; - private String scimStatus; - private String scimType; - private String api12Error; - private List errorDetails; +public abstract class ApiErrorBody { - public ApiErrorBody() {} + @JsonProperty("error_code") + @Nullable public abstract String errorCode(); - /** - * Constructs an ApiErrorBody from the given parameters. - * - *

The error details are converted to a list of ErrorDetail objects. This only supports the - * ErrorInfo type. - * - * @param errorCode The error code. - * @param message The error message. - * @param scimDetail The SCIM detail. - */ - public ApiErrorBody( - @JsonProperty("error_code") String errorCode, - @JsonProperty("message") String message, - @JsonProperty("detail") String scimDetail, - @JsonProperty("status") String scimStatus, - @JsonProperty("scimType") String scimType, - @JsonProperty("error") String api12Error, - @JsonProperty("details") ErrorDetails errorDetails) { - this.errorCode = errorCode; - this.message = message; - this.scimDetail = scimDetail; - this.scimStatus = scimStatus; - this.scimType = scimType; - this.api12Error = api12Error; - this.errorDetails = fromDetails(errorDetails); - } + @JsonProperty("message") + @Nullable public abstract String message(); - public List getErrorDetails() { - return errorDetails; - } + @JsonProperty("detail") + @Nullable public abstract String scimDetail(); - public void setErrorDetails(List errorDetails) { - this.errorDetails = errorDetails; - } + @JsonProperty("status") + @Nullable public abstract String scimStatus(); - public String getErrorCode() { - return errorCode; - } + @JsonProperty("scimType") + @Nullable public abstract String scimType(); - public void setErrorCode(String errorCode) { - this.errorCode = errorCode; - } + @JsonProperty("error") + @Nullable public abstract String api12Error(); - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - public String getScimDetail() { - return scimDetail; - } - - public void setScimDetail(String scimDetail) { - this.scimDetail = scimDetail; - } + @JsonProperty("details") + @Nullable public abstract ErrorDetails errorDetails(); - public String getScimStatus() { - return scimStatus; - } + @JsonProperty("errorDetailsList") + @Nullable public abstract List errorDetailsList(); - public void setScimStatus(String scimStatus) { - this.scimStatus = scimStatus; - } - - public String getScimType() { - return scimType; - } - - public void setScimType(String scimType) { - this.scimType = scimType; - } - - public String getApi12Error() { - return api12Error; - } + /** + * Returns a builder for constructing ApiErrorBody instances with the same values as this + * instance. + * + * @return a new builder instance + */ + public abstract Builder toBuilder(); - public void setApi12Error(String api12Error) { - this.api12Error = api12Error; + /** + * Creates a new builder for constructing ApiErrorBody instances. + * + * @return a new builder instance + */ + public static Builder builder() { + return new AutoValue_ApiErrorBody.Builder(); } /** - * Converts the error details to a list of ErrorDetail objects. This only supports the ErrorInfo - * type. + * Converts ErrorDetails to a List of legacy ErrorDetail objects for backward compatibility. This + * method extracts the structured error information and converts it to the format expected by + * legacy error handling code. * - * @param details The error details to convert. - * @return A list of ErrorDetail objects. + * @return a list of ErrorDetail objects, or an empty list if errorDetails() is null */ - private static List fromDetails(ErrorDetails details) { - if (details == null) { + public List getErrorDetailsList() { + // If we have a direct errorDetailsList from JSON, use it + if (errorDetailsList() != null) { + return errorDetailsList(); + } + + // Fallback to converting from ErrorDetails for backward compatibility + if (errorDetails() == null) { return Collections.emptyList(); } - if (!details.errorInfo().isPresent()) { + if (!errorDetails().errorInfo().isPresent()) { return Collections.emptyList(); } - return Arrays.asList( + + ErrorInfo errorInfo = errorDetails().errorInfo().get(); + return Collections.singletonList( new ErrorDetail( "type.googleapis.com/google.rpc.ErrorInfo", - details.errorInfo().get().reason(), - details.errorInfo().get().domain(), - details.errorInfo().get().metadata())); + errorInfo.reason(), + errorInfo.domain(), + errorInfo.metadata())); + } + + /** Builder for constructing ApiErrorBody instances. */ + @AutoValue.Builder + @JsonIgnoreProperties(ignoreUnknown = true) + public abstract static class Builder { + + /** + * Sets the error code. + * + * @param errorCode the error code + * @return this builder for method chaining + */ + @JsonProperty("error_code") + public abstract Builder setErrorCode(@Nullable String errorCode); + + /** + * Sets the message. + * + * @param message the message + * @return this builder for method chaining + */ + @JsonProperty("message") + public abstract Builder setMessage(@Nullable String message); + + /** + * Sets the SCIM detail. + * + * @param scimDetail the SCIM detail + * @return this builder for method chaining + */ + @JsonProperty("detail") + public abstract Builder setScimDetail(@Nullable String scimDetail); + + /** + * Sets the SCIM status. + * + * @param scimStatus the SCIM status + * @return this builder for method chaining + */ + @JsonProperty("status") + public abstract Builder setScimStatus(@Nullable String scimStatus); + + /** + * Sets the SCIM type. + * + * @param scimType the SCIM type + * @return this builder for method chaining + */ + @JsonProperty("scimType") + public abstract Builder setScimType(@Nullable String scimType); + + /** + * Sets the API 1.2 error. + * + * @param api12Error the API 1.2 error + * @return this builder for method chaining + */ + @JsonProperty("error") + public abstract Builder setApi12Error(@Nullable String api12Error); + + /** + * Sets the error details. + * + * @param errorDetails the error details + * @return this builder for method chaining + */ + @JsonProperty("details") + public abstract Builder setErrorDetails(@Nullable ErrorDetails errorDetails); + + /** + * Sets the error details list. + * + * @param errorDetailsList the error details list + * @return this builder for method chaining + */ + @JsonProperty("errorDetailsList") + public abstract Builder setErrorDetailsList(@Nullable List errorDetailsList); + + /** + * Builds the ApiErrorBody instance. + * + * @return a new ApiErrorBody instance + */ + public abstract ApiErrorBody build(); } } diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/ApiErrors.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/ApiErrors.java index a2b4d740c..eac7085ba 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/ApiErrors.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/ApiErrors.java @@ -3,17 +3,17 @@ import com.databricks.sdk.core.DatabricksError; import com.databricks.sdk.core.DatabricksException; import com.databricks.sdk.core.http.Response; +import com.databricks.sdk.core.utils.SerDeUtils; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.*; import java.nio.charset.StandardCharsets; -import java.util.Collections; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.IOUtils; /** Helper methods for inspecting the response and errors thrown during API requests. */ public class ApiErrors { - private static final ObjectMapper MAPPER = new ObjectMapper(); + private static final ObjectMapper MAPPER = SerDeUtils.createMapper(); private static final Pattern HTML_ERROR_REGEX = Pattern.compile("

(.*)
"); private static final ErrorMapper ERROR_MAPPER = new ErrorMapper(); @@ -32,29 +32,34 @@ public static DatabricksError getDatabricksError(Response out, Exception error) private static ApiErrorBody readErrorFromResponse(Response response) { // Private link error handling depends purely on the response URL. if (PrivateLinkInfo.isPrivateLinkRedirect(response)) { - return new ApiErrorBody(); + return ApiErrorBody.builder().build(); } ApiErrorBody errorBody = parseApiError(response); // Condense API v1.2 and SCIM error string and code into the message and errorCode fields of // DatabricksApiException. - if (errorBody.getApi12Error() != null && !errorBody.getApi12Error().isEmpty()) { - errorBody.setMessage(errorBody.getApi12Error()); + ApiErrorBody.Builder errorBodyBuilder = errorBody.toBuilder(); + + if (errorBody.api12Error() != null && !errorBody.api12Error().isEmpty()) { + errorBodyBuilder.setMessage(errorBody.api12Error()); } - if (errorBody.getMessage() == null || errorBody.getMessage().isEmpty()) { - if (errorBody.getScimDetail() != null && !"null".equals(errorBody.getScimDetail())) { - errorBody.setMessage(errorBody.getScimDetail()); + if (errorBody.message() == null || errorBody.message().isEmpty()) { + String scimMessage; + if (errorBody.scimDetail() != null && !"null".equals(errorBody.scimDetail())) { + scimMessage = errorBody.scimDetail(); } else { - errorBody.setMessage("SCIM API Internal Error"); + scimMessage = "SCIM API Internal Error"; } - String message = errorBody.getScimType() + " " + errorBody.getMessage(); - errorBody.setMessage(message.trim()); - errorBody.setErrorCode("SCIM_" + errorBody.getScimStatus()); + String message = errorBody.scimType() + " " + scimMessage; + errorBodyBuilder.setMessage(message.trim()); + errorBodyBuilder.setErrorCode("SCIM_" + errorBody.scimStatus()); } - if (errorBody.getErrorDetails() == null) { - errorBody.setErrorDetails(Collections.emptyList()); + if (errorBody.errorDetails() == null) { + // Note: This appears to be a bug in the original code - errorDetails is not a List + // We'll set it to null for now and this should be addressed separately + errorBodyBuilder.setErrorDetails(null); } - return errorBody; + return errorBodyBuilder.build(); } /** @@ -66,10 +71,9 @@ private static ApiErrorBody parseApiError(Response response) { try { InputStream in = response.getBody(); if (in == null) { - ApiErrorBody errorBody = new ApiErrorBody(); - errorBody.setMessage( - String.format("Status response from server: %s", response.getStatus())); - return errorBody; + return ApiErrorBody.builder() + .setMessage(String.format("Status response from server: %s", response.getStatus())) + .build(); } // Read the body now, so we can try to parse as JSON and then fallback to old error handling @@ -86,23 +90,23 @@ private static ApiErrorBody parseApiError(Response response) { } private static ApiErrorBody parseUnknownError(Response response, String body, IOException err) { - ApiErrorBody errorBody = new ApiErrorBody(); + ApiErrorBody.Builder errorBodyBuilder = ApiErrorBody.builder(); String[] statusParts = response.getStatus().split(" ", 2); if (statusParts.length < 2) { - errorBody.setErrorCode("UNKNOWN"); + errorBodyBuilder.setErrorCode("UNKNOWN"); } else { String errorCode = statusParts[1].replaceAll("^[ .]+|[ .]+$", ""); - errorBody.setErrorCode(errorCode.replaceAll(" ", "_").toUpperCase()); + errorBodyBuilder.setErrorCode(errorCode.replaceAll(" ", "_").toUpperCase()); } Matcher messageMatcher = HTML_ERROR_REGEX.matcher(body); if (messageMatcher.find()) { - errorBody.setMessage(messageMatcher.group(1).replaceAll("^[ .]+|[ .]+$", "")); + errorBodyBuilder.setMessage(messageMatcher.group(1).replaceAll("^[ .]+|[ .]+$", "")); } else { - errorBody.setMessage( + errorBodyBuilder.setMessage( String.format( "Response from server (%s) %s: %s", response.getStatus(), body, err.getMessage())); } - return errorBody; + return errorBodyBuilder.build(); } } diff --git a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/ErrorOverride.java b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/ErrorOverride.java index cb35d0544..847fafe2c 100644 --- a/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/ErrorOverride.java +++ b/databricks-sdk-java/src/main/java/com/databricks/sdk/core/error/ErrorOverride.java @@ -47,11 +47,11 @@ public boolean matches(ApiErrorBody body, Response resp) { return false; } if (this.errorCodeMatcher != null - && !this.errorCodeMatcher.matcher(body.getErrorCode()).matches()) { + && !this.errorCodeMatcher.matcher(body.errorCode()).matches()) { return false; } // Allow matching substring of the error message. - if (this.messageMatcher != null && !this.messageMatcher.matcher(body.getMessage()).find()) { + if (this.messageMatcher != null && !this.messageMatcher.matcher(body.message()).find()) { return false; } return true; @@ -70,7 +70,7 @@ public T makeError(ApiErrorBody body) { && parameterTypes[0].equals(String.class) && parameterTypes[1].equals(List.class)) { try { - return (T) constructor.newInstance(body.getMessage(), body.getErrorDetails()); + return (T) constructor.newInstance(body.message(), body.getErrorDetailsList()); } catch (Exception e) { throw new DatabricksException( "Error creating custom error for error type " + this.customError.getName(), e); diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/ApiClientTest.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/ApiClientTest.java index ef51f8363..c89565a46 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/ApiClientTest.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/ApiClientTest.java @@ -263,14 +263,10 @@ void failIdempotentRequestAfterTooManyRetries() throws IOException { getTransientError( req, 400, - new ApiErrorBody( - "ERROR", - null, - null, - null, - null, - "Workspace 123 does not have any associated worker environments", - null)), + ApiErrorBody.builder() + .setErrorCode("ERROR") + .setApi12Error("Workspace 123 does not have any associated worker environments") + .build()), getTooManyRequestsResponse(req), getTooManyRequestsResponse(req), getSuccessResponse(req)), @@ -300,14 +296,10 @@ void retryDatabricksApi12RetriableError() throws IOException { getTransientError( req, 400, - new ApiErrorBody( - "ERROR", - null, - null, - null, - null, - "Workspace 123 does not have any associated worker environments", - null)), + ApiErrorBody.builder() + .setErrorCode("ERROR") + .setApi12Error("Workspace 123 does not have any associated worker environments") + .build()), getSuccessResponse(req)), MyEndpointResponse.class, new MyEndpointResponse().setKey("value")); @@ -345,12 +337,15 @@ void errorDetails() throws JsonProcessingException { getTransientError( req, 401, - new ApiErrorBody("ERROR", null, null, null, null, null, errorDetails)), + ApiErrorBody.builder() + .setErrorCode("ERROR") + .setErrorDetails(errorDetails) + .build()), getSuccessResponse(req)), MyEndpointResponse.class, DatabricksError.class); List responseErrors = error.getErrorInfo(); - assertEquals(responseErrors.size(), 1); + assertEquals(1, responseErrors.size()); ErrorDetail responseError = responseErrors.get(0); assertEquals("type.googleapis.com/google.rpc.ErrorInfo", responseError.getType()); assertEquals("reason", responseError.getReason()); @@ -369,14 +364,10 @@ void retryDatabricksRetriableError() throws IOException { getTransientError( req, 400, - new ApiErrorBody( - "ERROR", - "Workspace 123 does not have any associated worker environments", - null, - null, - null, - null, - null)), + ApiErrorBody.builder() + .setErrorCode("ERROR") + .setMessage("Workspace 123 does not have any associated worker environments") + .build()), getSuccessResponse(req)), MyEndpointResponse.class, new MyEndpointResponse().setKey("value")); diff --git a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/ApiErrorBodyDeserializationSuite.java b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/ApiErrorBodyDeserializationSuite.java index f6a7b896b..c9756680b 100644 --- a/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/ApiErrorBodyDeserializationSuite.java +++ b/databricks-sdk-java/src/test/java/com/databricks/sdk/core/error/ApiErrorBodyDeserializationSuite.java @@ -2,6 +2,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import com.databricks.sdk.core.error.details.ErrorDetails; +import com.databricks.sdk.core.error.details.ErrorInfo; +import com.databricks.sdk.core.error.details.RequestInfo; +import com.databricks.sdk.core.utils.SerDeUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.*; @@ -10,69 +14,108 @@ public class ApiErrorBodyDeserializationSuite { @Test void deserializeErrorResponse() throws JsonProcessingException { - ObjectMapper mapper = new ObjectMapper(); + ObjectMapper mapper = SerDeUtils.createMapper(); String rawResponse = "{\"error_code\":\"theerrorcode\",\"message\":\"themessage\",\"detail\":\"thescimdetail\",\"status\":\"thescimstatus\",\"scimType\":\"thescimtype\",\"error\":\"theerror\"}"; - ApiErrorBody error = mapper.readValue(rawResponse, ApiErrorBody.class); - assertEquals(error.getErrorCode(), "theerrorcode"); - assertEquals(error.getMessage(), "themessage"); - assertEquals(error.getScimDetail(), "thescimdetail"); - assertEquals(error.getScimStatus(), "thescimstatus"); - assertEquals(error.getScimType(), "thescimtype"); - assertEquals(error.getApi12Error(), "theerror"); + ApiErrorBody actual = mapper.readValue(rawResponse, ApiErrorBody.class); + + ApiErrorBody expected = + ApiErrorBody.builder() + .setErrorCode("theerrorcode") + .setMessage("themessage") + .setScimDetail("thescimdetail") + .setScimStatus("thescimstatus") + .setScimType("thescimtype") + .setApi12Error("theerror") + .build(); + + assertEquals(expected, actual); } + @Test void deserializeErrorResponseWitIntErrorCode() throws JsonProcessingException { - ObjectMapper mapper = new ObjectMapper(); + ObjectMapper mapper = SerDeUtils.createMapper(); String rawResponse = "{\"error_code\":42,\"message\":\"themessage\",\"detail\":\"thescimdetail\",\"status\":\"thescimstatus\",\"scimType\":\"thescimtype\",\"error\":\"theerror\"}"; - ApiErrorBody error = mapper.readValue(rawResponse, ApiErrorBody.class); - assertEquals(error.getErrorCode(), "42"); - assertEquals(error.getMessage(), "themessage"); - assertEquals(error.getScimDetail(), "thescimdetail"); - assertEquals(error.getScimStatus(), "thescimstatus"); - assertEquals(error.getScimType(), "thescimtype"); - assertEquals(error.getApi12Error(), "theerror"); + ApiErrorBody actual = mapper.readValue(rawResponse, ApiErrorBody.class); + + ApiErrorBody expected = + ApiErrorBody.builder() + .setErrorCode("42") + .setMessage("themessage") + .setScimDetail("thescimdetail") + .setScimStatus("thescimstatus") + .setScimType("thescimtype") + .setApi12Error("theerror") + .build(); + + assertEquals(expected, actual); } @Test void deserializeDetailedResponse() throws JsonProcessingException { - ObjectMapper mapper = new ObjectMapper(); + ObjectMapper mapper = SerDeUtils.createMapper(); String rawResponse = "{\"error_code\":\"theerrorcode\",\"message\":\"themessage\"," + "\"details\":[" + "{\"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\", \"reason\":\"detailreason\", \"domain\":\"detaildomain\",\"metadata\":{\"etag\":\"detailsetag\"}}" + "]}"; - ApiErrorBody error = mapper.readValue(rawResponse, ApiErrorBody.class); - Map metadata = new HashMap<>(); - metadata.put("etag", "detailsetag"); - ErrorDetail errorDetails = error.getErrorDetails().get(0); - assertEquals(errorDetails.getType(), "type.googleapis.com/google.rpc.ErrorInfo"); - assertEquals(errorDetails.getReason(), "detailreason"); - assertEquals(errorDetails.getDomain(), "detaildomain"); - assertEquals(errorDetails.getMetadata(), metadata); + ApiErrorBody actual = mapper.readValue(rawResponse, ApiErrorBody.class); + + ApiErrorBody expected = + ApiErrorBody.builder() + .setErrorCode("theerrorcode") + .setMessage("themessage") + .setErrorDetails( + ErrorDetails.builder() + .setErrorInfo( + ErrorInfo.builder() + .setReason("detailreason") + .setDomain("detaildomain") + .setMetadata(Collections.singletonMap("etag", "detailsetag")) + .build()) + .build()) + .build(); + + assertEquals(expected, actual); } // Test that an ApiErrorBody can be deserialized, even if the response includes unexpected // parameters. @Test void handleUnexpectedFieldsInErrorResponse() throws JsonProcessingException { - ObjectMapper mapper = new ObjectMapper(); + ObjectMapper mapper = SerDeUtils.createMapper(); String rawResponse = "{\"error_code\":\"theerrorcode\",\"message\":\"themessage\",\"unexpectedField\":[\"unexpected\"]}"; - ApiErrorBody error = mapper.readValue(rawResponse, ApiErrorBody.class); - assertEquals(error.getErrorCode(), "theerrorcode"); - assertEquals(error.getMessage(), "themessage"); + ApiErrorBody actual = mapper.readValue(rawResponse, ApiErrorBody.class); + + ApiErrorBody expected = + ApiErrorBody.builder().setErrorCode("theerrorcode").setMessage("themessage").build(); + + assertEquals(expected, actual); } @Test void handleNullMetadataFieldInErrorResponse() throws JsonProcessingException { - ObjectMapper mapper = new ObjectMapper(); + ObjectMapper mapper = SerDeUtils.createMapper(); String rawResponse = "{\"error_code\":\"METASTORE_DOES_NOT_EXIST\",\"message\":\"No metastore assigned for the current workspace.\",\"details\":[{\"@type\":\"type.googleapis.com/google.rpc.RequestInfo\",\"request_id\":\"1888e822-f1b5-4996-85eb-0d2b5cc402e6\",\"serving_data\":\"\"}]}"; - ApiErrorBody error = mapper.readValue(rawResponse, ApiErrorBody.class); + ApiErrorBody actual = mapper.readValue(rawResponse, ApiErrorBody.class); + + ApiErrorBody expected = + ApiErrorBody.builder() + .setErrorCode("METASTORE_DOES_NOT_EXIST") + .setMessage("No metastore assigned for the current workspace.") + .setErrorDetails( + ErrorDetails.builder() + .setRequestInfo( + RequestInfo.builder() + .setRequestId("1888e822-f1b5-4996-85eb-0d2b5cc402e6") + .setServingData("") + .build()) + .build()) + .build(); - assertEquals(error.getErrorCode(), "METASTORE_DOES_NOT_EXIST"); - assertEquals(error.getMessage(), "No metastore assigned for the current workspace."); + assertEquals(expected, actual); } }